overleaf/services/web/frontend/js/features/file-tree/contexts/file-tree-mutable.js
Alf Eaton 7c97f8ab6e Switch to new JSX runtime (#4225)
* Use new JSX runtime and update Babel Node target
* Update .eslintrc
* Remove React imports

GitOrigin-RevId: 559de0267f8f2934c56a860ea8701bb522aa861a
2021-06-24 02:06:59 +00:00

222 lines
4.7 KiB
JavaScript

import {
createContext,
useCallback,
useReducer,
useContext,
useEffect,
} from 'react'
import PropTypes from 'prop-types'
import {
renameInTree,
deleteInTree,
moveInTree,
createEntityInTree,
} from '../util/mutate-in-tree'
const FileTreeMutableContext = createContext()
const ACTION_TYPES = {
RENAME: 'RENAME',
RESET: 'RESET',
DELETE: 'DELETE',
MOVE: 'MOVE',
CREATE_ENTITY: 'CREATE_ENTITY',
}
function fileTreeMutableReducer({ fileTreeData }, action) {
switch (action.type) {
case ACTION_TYPES.RESET: {
const newFileTreeData = action.fileTreeData
return {
fileTreeData: newFileTreeData,
fileCount: countFiles(newFileTreeData),
}
}
case ACTION_TYPES.RENAME: {
const newFileTreeData = renameInTree(fileTreeData, action.id, {
newName: action.newName,
})
return {
fileTreeData: newFileTreeData,
fileCount: countFiles(newFileTreeData),
}
}
case ACTION_TYPES.DELETE: {
const newFileTreeData = deleteInTree(fileTreeData, action.id)
return {
fileTreeData: newFileTreeData,
fileCount: countFiles(newFileTreeData),
}
}
case ACTION_TYPES.MOVE: {
const newFileTreeData = moveInTree(
fileTreeData,
action.entityId,
action.toFolderId
)
return {
fileTreeData: newFileTreeData,
fileCount: countFiles(newFileTreeData),
}
}
case ACTION_TYPES.CREATE_ENTITY: {
const newFileTreeData = createEntityInTree(
fileTreeData,
action.parentFolderId,
action.entity
)
return {
fileTreeData: newFileTreeData,
fileCount: countFiles(newFileTreeData),
}
}
default: {
throw new Error(`Unknown mutable file tree action type: ${action.type}`)
}
}
}
const initialState = rootFolder => ({
fileTreeData: rootFolder[0],
fileCount: countFiles(rootFolder[0]),
})
export const FileTreeMutableProvider = function ({ rootFolder, children }) {
const [{ fileTreeData, fileCount }, dispatch] = useReducer(
fileTreeMutableReducer,
rootFolder,
initialState
)
useEffect(() => {
dispatch({
type: ACTION_TYPES.RESET,
fileTreeData: rootFolder[0],
})
}, [rootFolder])
const dispatchCreateFolder = useCallback((parentFolderId, entity) => {
entity.type = 'folder'
dispatch({
type: ACTION_TYPES.CREATE_ENTITY,
parentFolderId,
entity,
})
}, [])
const dispatchCreateDoc = useCallback((parentFolderId, entity) => {
entity.type = 'doc'
dispatch({
type: ACTION_TYPES.CREATE_ENTITY,
parentFolderId,
entity,
})
}, [])
const dispatchCreateFile = useCallback((parentFolderId, entity) => {
entity.type = 'fileRef'
dispatch({
type: ACTION_TYPES.CREATE_ENTITY,
parentFolderId,
entity,
})
}, [])
const dispatchRename = useCallback((id, newName) => {
dispatch({
type: ACTION_TYPES.RENAME,
newName,
id,
})
}, [])
const dispatchDelete = useCallback(id => {
dispatch({ type: ACTION_TYPES.DELETE, id })
}, [])
const dispatchMove = useCallback((entityId, toFolderId) => {
dispatch({ type: ACTION_TYPES.MOVE, entityId, toFolderId })
}, [])
const value = {
dispatchCreateDoc,
dispatchCreateFile,
dispatchCreateFolder,
dispatchDelete,
dispatchMove,
dispatchRename,
fileCount,
fileTreeData,
}
return (
<FileTreeMutableContext.Provider value={value}>
{children}
</FileTreeMutableContext.Provider>
)
}
FileTreeMutableProvider.propTypes = {
rootFolder: PropTypes.array.isRequired,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
}
export function useFileTreeMutable() {
const context = useContext(FileTreeMutableContext)
if (!context) {
throw new Error(
'useFileTreeMutable is only available in FileTreeMutableProvider'
)
}
return context
}
function filesInFolder({ docs, folders, fileRefs }) {
const files = [...docs, ...fileRefs]
for (const folder of folders) {
files.push(...filesInFolder(folder))
}
return files
}
function countFiles(fileTreeData) {
const files = filesInFolder(fileTreeData)
// count all the non-deleted entities
const value = files.filter(item => !item.deleted).length
const limit = window.ExposedSettings.maxEntitiesPerProject
const status = fileCountStatus(value, limit, Math.ceil(limit / 20))
return { value, status, limit }
}
function fileCountStatus(value, limit, range) {
if (value >= limit) {
return 'error'
}
if (value >= limit - range) {
return 'warning'
}
return 'success'
}