Merge pull request #6210 from overleaf/ta-file-tree-data-in-context
Remove rootFolder from EditorContext GitOrigin-RevId: 827b046046265ad2418eb91d5a69eae02aa8b5a4
This commit is contained in:
@@ -34,9 +34,6 @@ EditorContext.Provider.propTypes = {
|
||||
insertSymbol: PropTypes.func,
|
||||
isProjectOwner: PropTypes.bool,
|
||||
isRestrictedTokenMember: PropTypes.bool,
|
||||
rootFolder: PropTypes.shape({
|
||||
children: PropTypes.arrayOf(PropTypes.shape({ type: PropTypes.string })),
|
||||
}),
|
||||
permissionsLevel: PropTypes.oneOf(['readOnly', 'readAndWrite', 'owner']),
|
||||
}),
|
||||
}
|
||||
@@ -73,7 +70,6 @@ export function EditorProvider({ children, settings }) {
|
||||
|
||||
const [loading] = useScopeValue('state.loading')
|
||||
const [projectName, setProjectName] = useScopeValue('project.name')
|
||||
const [rootFolder] = useScopeValue('rootFolder', true)
|
||||
const [permissionsLevel] = useScopeValue('permissionsLevel')
|
||||
const [showSymbolPalette] = useScopeValue('editor.showSymbolPalette')
|
||||
const [toggleSymbolPalette] = useScopeValue('editor.toggleSymbolPalette')
|
||||
@@ -135,7 +131,6 @@ export function EditorProvider({ children, settings }) {
|
||||
permissionsLevel,
|
||||
isProjectOwner: owner?._id === window.user.id,
|
||||
isRestrictedTokenMember: window.isRestrictedTokenMember,
|
||||
rootFolder,
|
||||
showSymbolPalette,
|
||||
toggleSymbolPalette,
|
||||
insertSymbol,
|
||||
@@ -147,7 +142,6 @@ export function EditorProvider({ children, settings }) {
|
||||
renameProject,
|
||||
permissionsLevel,
|
||||
owner?._id,
|
||||
rootFolder,
|
||||
showSymbolPalette,
|
||||
toggleSymbolPalette,
|
||||
insertSymbol,
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useReducer,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
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'
|
||||
|
||||
const FileTreeDataContext = createContext()
|
||||
|
||||
const fileTreeDataPropType = PropTypes.shape({
|
||||
_id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
docs: PropTypes.array.isRequired,
|
||||
fileRefs: PropTypes.array.isRequired,
|
||||
folders: PropTypes.array.isRequired,
|
||||
})
|
||||
|
||||
FileTreeDataContext.Provider.propTypes = {
|
||||
value: PropTypes.shape({
|
||||
// fileTreeData is the up-to-date representation of the files list, updated
|
||||
// by the file tree
|
||||
fileTreeData: fileTreeDataPropType,
|
||||
hasFolders: PropTypes.bool,
|
||||
}),
|
||||
}
|
||||
|
||||
const ACTION_TYPES = {
|
||||
RENAME: 'RENAME',
|
||||
RESET: 'RESET',
|
||||
DELETE: 'DELETE',
|
||||
MOVE: 'MOVE',
|
||||
CREATE: 'CREATE',
|
||||
}
|
||||
|
||||
function fileTreeMutableReducer({ fileTreeData }, 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.type}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initialState = rootFolder => {
|
||||
const fileTreeData = rootFolder?.[0]
|
||||
return {
|
||||
fileTreeData,
|
||||
fileCount: countFiles(fileTreeData),
|
||||
}
|
||||
}
|
||||
|
||||
export function useFileTreeData(propTypes) {
|
||||
const context = useContext(FileTreeDataContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useFileTreeData is only available inside FileTreeDataProvider'
|
||||
)
|
||||
}
|
||||
|
||||
PropTypes.checkPropTypes(
|
||||
propTypes,
|
||||
context,
|
||||
'data',
|
||||
'FileTreeDataContext.Provider'
|
||||
)
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
export function FileTreeDataProvider({ children }) {
|
||||
const [project] = useScopeValue('project', true)
|
||||
|
||||
const { rootFolder } = project || {}
|
||||
|
||||
const [{ fileTreeData, fileCount }, dispatch] = useReducer(
|
||||
fileTreeMutableReducer,
|
||||
rootFolder,
|
||||
initialState
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
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, entity) => {
|
||||
entity.type = 'doc'
|
||||
dispatch({
|
||||
type: ACTION_TYPES.CREATE,
|
||||
parentFolderId,
|
||||
entity,
|
||||
})
|
||||
}, [])
|
||||
|
||||
const dispatchCreateFile = useCallback((parentFolderId, entity) => {
|
||||
entity.type = 'fileRef'
|
||||
dispatch({
|
||||
type: ACTION_TYPES.CREATE,
|
||||
parentFolderId,
|
||||
entity,
|
||||
})
|
||||
}, [])
|
||||
|
||||
const dispatchRename = useCallback((id, newName) => {
|
||||
dispatch({
|
||||
type: ACTION_TYPES.RENAME,
|
||||
newName,
|
||||
id,
|
||||
})
|
||||
}, [])
|
||||
|
||||
const dispatchDelete = useCallback(id => {
|
||||
dispatch({ type: ACTION_TYPES.DELETE, id })
|
||||
}, [])
|
||||
|
||||
const dispatchMove = useCallback((entityId, toFolderId) => {
|
||||
dispatch({ type: ACTION_TYPES.MOVE, entityId, toFolderId })
|
||||
}, [])
|
||||
|
||||
const value = useMemo(() => {
|
||||
return {
|
||||
dispatchCreateDoc,
|
||||
dispatchCreateFile,
|
||||
dispatchCreateFolder,
|
||||
dispatchDelete,
|
||||
dispatchMove,
|
||||
dispatchRename,
|
||||
fileCount,
|
||||
fileTreeData,
|
||||
hasFolders: fileTreeData?.folders.length > 0,
|
||||
}
|
||||
}, [
|
||||
dispatchCreateDoc,
|
||||
dispatchCreateFile,
|
||||
dispatchCreateFolder,
|
||||
dispatchDelete,
|
||||
dispatchMove,
|
||||
dispatchRename,
|
||||
fileCount,
|
||||
fileTreeData,
|
||||
])
|
||||
|
||||
return (
|
||||
<FileTreeDataContext.Provider value={value}>
|
||||
{children}
|
||||
</FileTreeDataContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
FileTreeDataProvider.propTypes = {
|
||||
children: PropTypes.any,
|
||||
}
|
||||
@@ -1,17 +1,9 @@
|
||||
import { createContext, useContext } from 'react'
|
||||
import { createContext, useContext, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import useScopeValue from '../hooks/use-scope-value'
|
||||
|
||||
const ProjectContext = createContext()
|
||||
|
||||
const fileTreeDataPropType = PropTypes.shape({
|
||||
_id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
docs: PropTypes.array.isRequired,
|
||||
fileRefs: PropTypes.array.isRequired,
|
||||
folders: PropTypes.array.isRequired,
|
||||
})
|
||||
|
||||
ProjectContext.Provider.propTypes = {
|
||||
value: PropTypes.shape({
|
||||
_id: PropTypes.string.isRequired,
|
||||
@@ -44,7 +36,6 @@ ProjectContext.Provider.propTypes = {
|
||||
_id: PropTypes.string.isRequired,
|
||||
email: PropTypes.string.isRequired,
|
||||
}),
|
||||
rootFolder: PropTypes.arrayOf(fileTreeDataPropType),
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -67,13 +58,24 @@ export function useProjectContext(propTypes) {
|
||||
return context
|
||||
}
|
||||
|
||||
// when the provider is created the project is still not added to the Angular
|
||||
// scope. A few props are populated to prevent errors in existing React
|
||||
// components
|
||||
const projectFallback = {
|
||||
_id: window.project_id,
|
||||
name: '',
|
||||
features: {},
|
||||
}
|
||||
|
||||
export function ProjectProvider({ children }) {
|
||||
const [project] = useScopeValue('project', true)
|
||||
|
||||
// when the provider is created the project is still not added to the Angular scope.
|
||||
// Name is also populated to prevent errors in existing React components
|
||||
const value = project || { _id: window.project_id, name: '', features: {} }
|
||||
|
||||
const value = useMemo(() => {
|
||||
return {
|
||||
...projectFallback,
|
||||
...project,
|
||||
}
|
||||
}, [project])
|
||||
return (
|
||||
<ProjectContext.Provider value={value}>{children}</ProjectContext.Provider>
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ import { DetachProvider } from './detach-context'
|
||||
import { ChatProvider } from '../../features/chat/context/chat-context'
|
||||
import { ProjectProvider } from './project-context'
|
||||
import { SplitTestProvider } from './split-test-context'
|
||||
import { FileTreeDataProvider } from './file-tree-data-context'
|
||||
|
||||
export function ContextRoot({ children, ide, settings }) {
|
||||
return (
|
||||
@@ -17,15 +18,17 @@ export function ContextRoot({ children, ide, settings }) {
|
||||
<IdeProvider ide={ide}>
|
||||
<UserProvider>
|
||||
<ProjectProvider>
|
||||
<EditorProvider settings={settings}>
|
||||
<DetachProvider>
|
||||
<LayoutProvider>
|
||||
<CompileProvider>
|
||||
<ChatProvider>{children}</ChatProvider>
|
||||
</CompileProvider>
|
||||
</LayoutProvider>
|
||||
</DetachProvider>
|
||||
</EditorProvider>
|
||||
<FileTreeDataProvider>
|
||||
<EditorProvider settings={settings}>
|
||||
<DetachProvider>
|
||||
<LayoutProvider>
|
||||
<CompileProvider>
|
||||
<ChatProvider>{children}</ChatProvider>
|
||||
</CompileProvider>
|
||||
</LayoutProvider>
|
||||
</DetachProvider>
|
||||
</EditorProvider>
|
||||
</FileTreeDataProvider>
|
||||
</ProjectProvider>
|
||||
</UserProvider>
|
||||
</IdeProvider>
|
||||
|
||||
Reference in New Issue
Block a user