2021-06-23 05:37:08 -04:00
|
|
|
import {
|
2021-03-10 04:11:43 -05:00
|
|
|
createContext,
|
|
|
|
useCallback,
|
|
|
|
useReducer,
|
2021-04-27 03:52:58 -04:00
|
|
|
useContext,
|
2022-01-10 10:47:01 -05:00
|
|
|
useMemo,
|
2022-03-21 10:47:01 -04:00
|
|
|
useState,
|
2024-01-26 04:23:48 -05:00
|
|
|
FC,
|
2024-08-21 07:28:21 -04:00
|
|
|
useEffect,
|
2021-03-10 04:11:43 -05:00
|
|
|
} from 'react'
|
2022-01-10 10:47:01 -05:00
|
|
|
import useScopeValue from '../hooks/use-scope-value'
|
2020-11-26 09:22:30 -05:00
|
|
|
import {
|
|
|
|
renameInTree,
|
|
|
|
deleteInTree,
|
|
|
|
moveInTree,
|
2021-04-27 03:52:58 -04:00
|
|
|
createEntityInTree,
|
2022-01-10 10:47:01 -05:00
|
|
|
} from '../../features/file-tree/util/mutate-in-tree'
|
|
|
|
import { countFiles } from '../../features/file-tree/util/count-in-tree'
|
2022-02-15 08:37:23 -05:00
|
|
|
import useDeepCompareEffect from '../../shared/hooks/use-deep-compare-effect'
|
2023-11-13 06:11:33 -05:00
|
|
|
import { docsInFolder } from '@/features/file-tree/util/docs-in-folder'
|
2024-01-12 05:09:29 -05:00
|
|
|
import useScopeValueSetterOnly from '@/shared/hooks/use-scope-value-setter-only'
|
2024-01-26 04:23:48 -05:00
|
|
|
import { Folder } from '../../../../types/folder'
|
|
|
|
import { Project } from '../../../../types/project'
|
|
|
|
import { MainDocument } from '../../../../types/project-settings'
|
|
|
|
import { FindResult } from '@/features/file-tree/util/path'
|
2024-08-21 07:28:21 -04:00
|
|
|
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
|
2024-01-26 04:23:48 -05:00
|
|
|
|
|
|
|
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
|
2024-08-22 11:00:07 -04:00
|
|
|
fileTreeReadOnly: boolean
|
2024-01-26 04:23:48 -05:00
|
|
|
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',
|
2022-01-10 10:47:01 -05:00
|
|
|
}
|
2024-01-26 04:23:48 -05:00
|
|
|
/* eslint-enable no-unused-vars */
|
2020-11-26 09:22:30 -05:00
|
|
|
|
2024-01-26 04:23:48 -05:00
|
|
|
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
|
|
|
|
}
|
2020-11-26 09:22:30 -05:00
|
|
|
|
2024-01-26 04:23:48 -05:00
|
|
|
function fileTreeMutableReducer(
|
|
|
|
{ fileTreeData }: { fileTreeData: Folder },
|
|
|
|
action: Action
|
|
|
|
) {
|
2020-11-26 09:22:30 -05:00
|
|
|
switch (action.type) {
|
2021-05-18 06:58:53 -04:00
|
|
|
case ACTION_TYPES.RESET: {
|
|
|
|
const newFileTreeData = action.fileTreeData
|
|
|
|
|
|
|
|
return {
|
|
|
|
fileTreeData: newFileTreeData,
|
|
|
|
fileCount: countFiles(newFileTreeData),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-18 05:52:36 -04:00
|
|
|
case ACTION_TYPES.RENAME: {
|
|
|
|
const newFileTreeData = renameInTree(fileTreeData, action.id, {
|
2021-04-27 03:52:58 -04:00
|
|
|
newName: action.newName,
|
2021-03-18 05:52:36 -04:00
|
|
|
})
|
|
|
|
|
2020-11-26 09:22:30 -05:00
|
|
|
return {
|
2021-03-18 05:52:36 -04:00
|
|
|
fileTreeData: newFileTreeData,
|
2021-04-27 03:52:58 -04:00
|
|
|
fileCount: countFiles(newFileTreeData),
|
2020-11-26 09:22:30 -05:00
|
|
|
}
|
2021-03-18 05:52:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
case ACTION_TYPES.DELETE: {
|
|
|
|
const newFileTreeData = deleteInTree(fileTreeData, action.id)
|
|
|
|
|
2020-11-26 09:22:30 -05:00
|
|
|
return {
|
2021-03-18 05:52:36 -04:00
|
|
|
fileTreeData: newFileTreeData,
|
2021-04-27 03:52:58 -04:00
|
|
|
fileCount: countFiles(newFileTreeData),
|
2020-11-26 09:22:30 -05:00
|
|
|
}
|
2021-03-18 05:52:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
case ACTION_TYPES.MOVE: {
|
|
|
|
const newFileTreeData = moveInTree(
|
|
|
|
fileTreeData,
|
|
|
|
action.entityId,
|
|
|
|
action.toFolderId
|
|
|
|
)
|
|
|
|
|
2020-11-26 09:22:30 -05:00
|
|
|
return {
|
2021-03-18 05:52:36 -04:00
|
|
|
fileTreeData: newFileTreeData,
|
2021-04-27 03:52:58 -04:00
|
|
|
fileCount: countFiles(newFileTreeData),
|
2020-11-26 09:22:30 -05:00
|
|
|
}
|
2021-03-18 05:52:36 -04:00
|
|
|
}
|
|
|
|
|
2022-01-10 10:46:46 -05:00
|
|
|
case ACTION_TYPES.CREATE: {
|
2021-03-18 05:52:36 -04:00
|
|
|
const newFileTreeData = createEntityInTree(
|
|
|
|
fileTreeData,
|
|
|
|
action.parentFolderId,
|
|
|
|
action.entity
|
|
|
|
)
|
|
|
|
|
2020-11-26 09:22:30 -05:00
|
|
|
return {
|
2021-03-18 05:52:36 -04:00
|
|
|
fileTreeData: newFileTreeData,
|
2021-04-27 03:52:58 -04:00
|
|
|
fileCount: countFiles(newFileTreeData),
|
2020-11-26 09:22:30 -05:00
|
|
|
}
|
2021-03-18 05:52:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
default: {
|
2024-01-26 04:23:48 -05:00
|
|
|
throw new Error(
|
|
|
|
`Unknown mutable file tree action type: ${(action as Action).type}`
|
|
|
|
)
|
2021-03-18 05:52:36 -04:00
|
|
|
}
|
2020-11-26 09:22:30 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-26 04:23:48 -05:00
|
|
|
const initialState = (rootFolder?: Folder[]) => {
|
2022-01-10 10:47:01 -05:00
|
|
|
const fileTreeData = rootFolder?.[0]
|
|
|
|
return {
|
|
|
|
fileTreeData,
|
|
|
|
fileCount: countFiles(fileTreeData),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-26 04:23:48 -05:00
|
|
|
export function useFileTreeData() {
|
2022-01-10 10:47:01 -05:00
|
|
|
const context = useContext(FileTreeDataContext)
|
|
|
|
|
|
|
|
if (!context) {
|
|
|
|
throw new Error(
|
|
|
|
'useFileTreeData is only available inside FileTreeDataProvider'
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return context
|
|
|
|
}
|
2021-05-18 06:58:53 -04:00
|
|
|
|
2024-01-26 04:23:48 -05:00
|
|
|
export const FileTreeDataProvider: FC = ({ children }) => {
|
|
|
|
const [project] = useScopeValue<Project>('project')
|
2024-01-12 05:09:29 -05:00
|
|
|
const [openDocId] = useScopeValue('editor.open_doc_id')
|
|
|
|
const [, setOpenDocName] = useScopeValueSetterOnly('editor.open_doc_name')
|
2024-08-22 11:00:07 -04:00
|
|
|
const [permissionsLevel] = useScopeValue('permissionsLevel')
|
2024-08-21 07:28:21 -04:00
|
|
|
const { fileTreeFromHistory, snapshot, snapshotVersion } =
|
|
|
|
useSnapshotContext()
|
2024-08-22 11:00:07 -04:00
|
|
|
const fileTreeReadOnly =
|
|
|
|
permissionsLevel === 'readOnly' || fileTreeFromHistory
|
2024-08-21 07:28:21 -04:00
|
|
|
|
|
|
|
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])
|
2022-01-10 10:47:01 -05:00
|
|
|
|
2024-08-21 07:28:21 -04:00
|
|
|
useEffect(() => {
|
|
|
|
if (!fileTreeFromHistory || !snapshot) return
|
|
|
|
setRootFolder([buildFileTree(snapshot)])
|
|
|
|
}, [fileTreeFromHistory, snapshot, snapshotVersion])
|
2022-01-10 10:46:46 -05:00
|
|
|
|
2021-03-18 05:52:36 -04:00
|
|
|
const [{ fileTreeData, fileCount }, dispatch] = useReducer(
|
|
|
|
fileTreeMutableReducer,
|
2021-05-18 06:58:53 -04:00
|
|
|
rootFolder,
|
|
|
|
initialState
|
2021-03-18 05:52:36 -04:00
|
|
|
)
|
2020-11-26 09:22:30 -05:00
|
|
|
|
2024-01-26 04:23:48 -05:00
|
|
|
const [selectedEntities, setSelectedEntities] = useState<FindResult[]>([])
|
2022-03-21 10:47:01 -04:00
|
|
|
|
2023-11-13 06:11:33 -05:00
|
|
|
const docs = useMemo(
|
|
|
|
() => (fileTreeData ? docsInFolder(fileTreeData) : undefined),
|
|
|
|
[fileTreeData]
|
|
|
|
)
|
|
|
|
|
2022-02-15 08:37:23 -05:00
|
|
|
useDeepCompareEffect(() => {
|
2021-05-18 06:58:53 -04:00
|
|
|
dispatch({
|
|
|
|
type: ACTION_TYPES.RESET,
|
2022-01-10 10:47:01 -05:00
|
|
|
fileTreeData: rootFolder?.[0],
|
2021-05-18 06:58:53 -04:00
|
|
|
})
|
|
|
|
}, [rootFolder])
|
|
|
|
|
2021-05-04 07:36:22 -04:00
|
|
|
const dispatchCreateFolder = useCallback((parentFolderId, entity) => {
|
|
|
|
entity.type = 'folder'
|
|
|
|
dispatch({
|
2022-01-10 10:46:46 -05:00
|
|
|
type: ACTION_TYPES.CREATE,
|
2021-05-04 07:36:22 -04:00
|
|
|
parentFolderId,
|
|
|
|
entity,
|
|
|
|
})
|
|
|
|
}, [])
|
|
|
|
|
2024-01-26 04:23:48 -05:00
|
|
|
const dispatchCreateDoc = useCallback(
|
|
|
|
(parentFolderId: string, entity: any) => {
|
|
|
|
entity.type = 'doc'
|
|
|
|
dispatch({
|
|
|
|
type: ACTION_TYPES.CREATE,
|
|
|
|
parentFolderId,
|
|
|
|
entity,
|
|
|
|
})
|
|
|
|
},
|
|
|
|
[]
|
|
|
|
)
|
2021-05-04 07:36:22 -04:00
|
|
|
|
2024-01-26 04:23:48 -05:00
|
|
|
const dispatchCreateFile = useCallback(
|
|
|
|
(parentFolderId: string, entity: any) => {
|
|
|
|
entity.type = 'fileRef'
|
|
|
|
dispatch({
|
|
|
|
type: ACTION_TYPES.CREATE,
|
|
|
|
parentFolderId,
|
|
|
|
entity,
|
|
|
|
})
|
|
|
|
},
|
|
|
|
[]
|
|
|
|
)
|
2021-05-04 07:36:22 -04:00
|
|
|
|
2024-01-12 05:09:29 -05:00
|
|
|
const dispatchRename = useCallback(
|
2024-01-26 04:23:48 -05:00
|
|
|
(id: string, newName: string) => {
|
2024-01-12 05:09:29 -05:00
|
|
|
dispatch({
|
|
|
|
type: ACTION_TYPES.RENAME,
|
|
|
|
newName,
|
|
|
|
id,
|
|
|
|
})
|
|
|
|
if (id === openDocId) {
|
|
|
|
setOpenDocName(newName)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[openDocId, setOpenDocName]
|
|
|
|
)
|
2021-05-04 07:36:22 -04:00
|
|
|
|
2024-01-26 04:23:48 -05:00
|
|
|
const dispatchDelete = useCallback((id: string) => {
|
2021-05-04 07:36:22 -04:00
|
|
|
dispatch({ type: ACTION_TYPES.DELETE, id })
|
|
|
|
}, [])
|
|
|
|
|
2024-01-26 04:23:48 -05:00
|
|
|
const dispatchMove = useCallback((entityId: string, toFolderId: string) => {
|
2021-05-04 07:36:22 -04:00
|
|
|
dispatch({ type: ACTION_TYPES.MOVE, entityId, toFolderId })
|
|
|
|
}, [])
|
|
|
|
|
2022-01-10 10:47:01 -05:00
|
|
|
const value = useMemo(() => {
|
|
|
|
return {
|
|
|
|
dispatchCreateDoc,
|
|
|
|
dispatchCreateFile,
|
|
|
|
dispatchCreateFolder,
|
|
|
|
dispatchDelete,
|
|
|
|
dispatchMove,
|
|
|
|
dispatchRename,
|
|
|
|
fileCount,
|
|
|
|
fileTreeData,
|
2024-08-22 11:00:07 -04:00
|
|
|
fileTreeReadOnly,
|
2022-01-10 10:47:01 -05:00
|
|
|
hasFolders: fileTreeData?.folders.length > 0,
|
2022-03-21 10:47:01 -04:00
|
|
|
selectedEntities,
|
|
|
|
setSelectedEntities,
|
2023-11-13 06:11:33 -05:00
|
|
|
docs,
|
2022-01-10 10:47:01 -05:00
|
|
|
}
|
|
|
|
}, [
|
2021-05-04 07:36:22 -04:00
|
|
|
dispatchCreateDoc,
|
|
|
|
dispatchCreateFile,
|
|
|
|
dispatchCreateFolder,
|
|
|
|
dispatchDelete,
|
|
|
|
dispatchMove,
|
|
|
|
dispatchRename,
|
|
|
|
fileCount,
|
|
|
|
fileTreeData,
|
2024-08-22 11:00:07 -04:00
|
|
|
fileTreeReadOnly,
|
2022-03-21 10:47:01 -04:00
|
|
|
selectedEntities,
|
|
|
|
setSelectedEntities,
|
2023-11-13 06:11:33 -05:00
|
|
|
docs,
|
2022-01-10 10:47:01 -05:00
|
|
|
])
|
2021-05-04 07:36:22 -04:00
|
|
|
|
2020-11-26 09:22:30 -05:00
|
|
|
return (
|
2022-01-10 10:47:01 -05:00
|
|
|
<FileTreeDataContext.Provider value={value}>
|
2020-11-26 09:22:30 -05:00
|
|
|
{children}
|
2022-01-10 10:47:01 -05:00
|
|
|
</FileTreeDataContext.Provider>
|
2020-11-26 09:22:30 -05:00
|
|
|
)
|
|
|
|
}
|