import { createContext, useCallback, useReducer, useContext, useMemo, useState, FC, } from 'react' import useScopeValue from '../hooks/use-scope-value' import { renameInTree, deleteInTree, moveInTree, createEntityInTree, } from '../../features/file-tree/util/mutate-in-tree' import { countFiles } from '../../features/file-tree/util/count-in-tree' import useDeepCompareEffect from '../../shared/hooks/use-deep-compare-effect' import { docsInFolder } from '@/features/file-tree/util/docs-in-folder' import useScopeValueSetterOnly from '@/shared/hooks/use-scope-value-setter-only' import { Folder } from '../../../../types/folder' import { Project } from '../../../../types/project' import { MainDocument } from '../../../../types/project-settings' import { FindResult } from '@/features/file-tree/util/path' const FileTreeDataContext = createContext< | { // fileTreeData is the up-to-date representation of the files list, updated // by the file tree fileTreeData: Folder fileCount: { value: number; status: string; limit: number } | number hasFolders: boolean selectedEntities: FindResult[] setSelectedEntities: (selectedEntities: FindResult[]) => void dispatchRename: (id: string, name: string) => void dispatchMove: (id: string, target: string) => void dispatchDelete: (id: string) => void dispatchCreateFolder: (name: string, folder: any) => void dispatchCreateDoc: (name: string, doc: any) => void dispatchCreateFile: (name: string, file: any) => void docs?: MainDocument[] } | undefined >(undefined) /* eslint-disable no-unused-vars */ enum ACTION_TYPES { RENAME = 'RENAME', RESET = 'RESET', DELETE = 'DELETE', MOVE = 'MOVE', CREATE = 'CREATE', } /* eslint-enable no-unused-vars */ type Action = | { type: ACTION_TYPES.RESET fileTreeData?: Folder } | { type: ACTION_TYPES.RENAME id: string newName: string } | { type: ACTION_TYPES.DELETE id: string } | { type: ACTION_TYPES.MOVE entityId: string toFolderId: string } | { type: typeof ACTION_TYPES.CREATE parentFolderId: string entity: any // TODO } function fileTreeMutableReducer( { fileTreeData }: { fileTreeData: Folder }, action: 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: { 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 as Action).type}` ) } } } const initialState = (rootFolder?: Folder[]) => { const fileTreeData = rootFolder?.[0] return { fileTreeData, fileCount: countFiles(fileTreeData), } } export function useFileTreeData() { const context = useContext(FileTreeDataContext) if (!context) { throw new Error( 'useFileTreeData is only available inside FileTreeDataProvider' ) } return context } export const FileTreeDataProvider: FC = ({ children }) => { const [project] = useScopeValue('project') const [openDocId] = useScopeValue('editor.open_doc_id') const [, setOpenDocName] = useScopeValueSetterOnly('editor.open_doc_name') const { rootFolder } = project || {} const [{ fileTreeData, fileCount }, dispatch] = useReducer( fileTreeMutableReducer, rootFolder, initialState ) const [selectedEntities, setSelectedEntities] = useState([]) const docs = useMemo( () => (fileTreeData ? docsInFolder(fileTreeData) : undefined), [fileTreeData] ) useDeepCompareEffect(() => { dispatch({ type: ACTION_TYPES.RESET, fileTreeData: rootFolder?.[0], }) }, [rootFolder]) const dispatchCreateFolder = useCallback((parentFolderId, entity) => { entity.type = 'folder' dispatch({ type: ACTION_TYPES.CREATE, parentFolderId, entity, }) }, []) const dispatchCreateDoc = useCallback( (parentFolderId: string, entity: any) => { entity.type = 'doc' dispatch({ type: ACTION_TYPES.CREATE, parentFolderId, entity, }) }, [] ) const dispatchCreateFile = useCallback( (parentFolderId: string, entity: any) => { entity.type = 'fileRef' dispatch({ type: ACTION_TYPES.CREATE, parentFolderId, entity, }) }, [] ) const dispatchRename = useCallback( (id: string, newName: string) => { dispatch({ type: ACTION_TYPES.RENAME, newName, id, }) if (id === openDocId) { setOpenDocName(newName) } }, [openDocId, setOpenDocName] ) const dispatchDelete = useCallback((id: string) => { dispatch({ type: ACTION_TYPES.DELETE, id }) }, []) const dispatchMove = useCallback((entityId: string, toFolderId: string) => { dispatch({ type: ACTION_TYPES.MOVE, entityId, toFolderId }) }, []) const value = useMemo(() => { return { dispatchCreateDoc, dispatchCreateFile, dispatchCreateFolder, dispatchDelete, dispatchMove, dispatchRename, fileCount, fileTreeData, hasFolders: fileTreeData?.folders.length > 0, selectedEntities, setSelectedEntities, docs, } }, [ dispatchCreateDoc, dispatchCreateFile, dispatchCreateFolder, dispatchDelete, dispatchMove, dispatchRename, fileCount, fileTreeData, selectedEntities, setSelectedEntities, docs, ]) return ( {children} ) }