mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-02 13:20:23 -05:00
0cde5be165
Convert React context providers to TypeScript [don't squash!] GitOrigin-RevId: d92a91798286978410956ab791d73c17c5086d86
285 lines
6.6 KiB
TypeScript
285 lines
6.6 KiB
TypeScript
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>('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<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>
|
|
)
|
|
}
|