| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- import {
- createContext,
- useCallback,
- useReducer,
- useContext,
- useMemo,
- useState,
- FC,
- useEffect,
- } 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'
- import {
- StubSnapshotUtils,
- useSnapshotContext,
- } from '@/features/ide-react/context/snapshot-context'
- import importOverleafModules from '../../../macros/import-overleaf-module.macro'
- const { buildFileTree, createFolder } =
- (importOverleafModules('snapshotUtils')[0]
- ?.import as typeof StubSnapshotUtils) || StubSnapshotUtils
- 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>('project')
- const [openDocId] = useScopeValue('editor.open_doc_id')
- const [, setOpenDocName] = useScopeValueSetterOnly('editor.open_doc_name')
- const { fileTreeFromHistory, snapshot, snapshotVersion } =
- useSnapshotContext()
- const [rootFolder, setRootFolder] = useState(project?.rootFolder)
- useEffect(() => {
- if (fileTreeFromHistory) return
- setRootFolder(project?.rootFolder)
- }, [project, fileTreeFromHistory])
- useEffect(() => {
- if (!fileTreeFromHistory) return
- if (!rootFolder || rootFolder?.[0]?._id) {
- // Init or replace mongo rootFolder with stub while we load the snapshot.
- // In the future, project:joined should only fire once the snapshot is ready.
- setRootFolder([createFolder('', '')])
- }
- }, [fileTreeFromHistory, rootFolder])
- useEffect(() => {
- if (!fileTreeFromHistory || !snapshot) return
- setRootFolder([buildFileTree(snapshot)])
- }, [fileTreeFromHistory, snapshot, snapshotVersion])
- const [{ fileTreeData, fileCount }, dispatch] = useReducer(
- fileTreeMutableReducer,
- rootFolder,
- initialState
- )
- const [selectedEntities, setSelectedEntities] = useState<FindResult[]>([])
- 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 (
- <FileTreeDataContext.Provider value={value}>
- {children}
- </FileTreeDataContext.Provider>
- )
- }
|