import React, { createContext, useCallback, useReducer, useContext, } 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', DELETE: 'DELETE', MOVE: 'MOVE', CREATE_ENTITY: 'CREATE_ENTITY', } function fileTreeMutableReducer({ fileTreeData }, action) { switch (action.type) { 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}`) } } } export const FileTreeMutableProvider = function ({ rootFolder, children }) { const [{ fileTreeData, fileCount }, dispatch] = useReducer( fileTreeMutableReducer, { fileTreeData: rootFolder[0], fileCount: countFiles(rootFolder[0]), } ) return ( {children} ) } FileTreeMutableProvider.propTypes = { rootFolder: PropTypes.array.isRequired, children: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.node), PropTypes.node, ]).isRequired, } export function useFileTreeMutable() { const { fileTreeData, fileCount, dispatch } = useContext( FileTreeMutableContext ) const dispatchCreateFolder = useCallback( (parentFolderId, entity) => { entity.type = 'folder' dispatch({ type: ACTION_TYPES.CREATE_ENTITY, parentFolderId, entity, }) }, [dispatch] ) const dispatchCreateDoc = useCallback( (parentFolderId, entity) => { entity.type = 'doc' dispatch({ type: ACTION_TYPES.CREATE_ENTITY, parentFolderId, entity, }) }, [dispatch] ) const dispatchCreateFile = useCallback( (parentFolderId, entity) => { entity.type = 'fileRef' dispatch({ type: ACTION_TYPES.CREATE_ENTITY, parentFolderId, entity, }) }, [dispatch] ) const dispatchRename = useCallback( (id, newName) => { dispatch({ type: ACTION_TYPES.RENAME, newName, id, }) }, [dispatch] ) const dispatchDelete = useCallback( id => { dispatch({ type: ACTION_TYPES.DELETE, id }) }, [dispatch] ) const dispatchMove = useCallback( (entityId, toFolderId) => { dispatch({ type: ACTION_TYPES.MOVE, entityId, toFolderId }) }, [dispatch] ) return { fileTreeData, fileCount, dispatchRename, dispatchDelete, dispatchMove, dispatchCreateFolder, dispatchCreateDoc, dispatchCreateFile, } } 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' }