mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #3974 from overleaf/ae-refactor-context-hooks-usememo
Refactor functions from hooks into context providers GitOrigin-RevId: f985ec15c16bdb49bedf7b64a0f5fe2853b6bb85
This commit is contained in:
parent
dbc909ae54
commit
13e6166259
3 changed files with 155 additions and 181 deletions
|
@ -124,41 +124,13 @@ export function FileTreeActionableProvider({ hasWritePermissions, children }) {
|
|||
defaultState
|
||||
)
|
||||
|
||||
return (
|
||||
<FileTreeActionableContext.Provider value={{ ...state, dispatch }}>
|
||||
{children}
|
||||
</FileTreeActionableContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
FileTreeActionableProvider.propTypes = {
|
||||
hasWritePermissions: PropTypes.bool.isRequired,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
}
|
||||
|
||||
export function useFileTreeActionable() {
|
||||
const {
|
||||
isDeleting,
|
||||
isRenaming,
|
||||
isMoving,
|
||||
isCreatingFile,
|
||||
isCreatingFolder,
|
||||
inFlight,
|
||||
error,
|
||||
actionedEntities,
|
||||
newFileCreateMode,
|
||||
dispatch,
|
||||
} = useContext(FileTreeActionableContext)
|
||||
const { projectId } = useFileTreeMainContext()
|
||||
const { fileTreeData, dispatchRename, dispatchMove } = useFileTreeMutable()
|
||||
const { selectedEntityIds } = useFileTreeSelectable()
|
||||
|
||||
const startRenaming = useCallback(() => {
|
||||
dispatch({ type: ACTION_TYPES.START_RENAME })
|
||||
}, [dispatch])
|
||||
}, [])
|
||||
|
||||
// update the entity with the new name immediately in the tree, but revert to
|
||||
// the old name if the sync fails
|
||||
|
@ -185,7 +157,7 @@ export function useFileTreeActionable() {
|
|||
}
|
||||
)
|
||||
},
|
||||
[dispatch, dispatchRename, fileTreeData, projectId, selectedEntityIds]
|
||||
[dispatchRename, fileTreeData, projectId, selectedEntityIds]
|
||||
)
|
||||
|
||||
const isDuplicate = useCallback(
|
||||
|
@ -203,9 +175,9 @@ export function useFileTreeActionable() {
|
|||
entityId => findInTreeOrThrow(fileTreeData, entityId).entity
|
||||
)
|
||||
dispatch({ type: ACTION_TYPES.START_DELETE, actionedEntities })
|
||||
}, [dispatch, fileTreeData, selectedEntityIds])
|
||||
}, [fileTreeData, selectedEntityIds])
|
||||
|
||||
// deletes entities in serie. Tree will be updated via the socket event
|
||||
// deletes entities in series. Tree will be updated via the socket event
|
||||
const finishDeleting = useCallback(() => {
|
||||
dispatch({ type: ACTION_TYPES.DELETING })
|
||||
|
||||
|
@ -227,7 +199,7 @@ export function useFileTreeActionable() {
|
|||
// set an error and allow user to retry
|
||||
dispatch({ type: ACTION_TYPES.ERROR, error })
|
||||
})
|
||||
}, [dispatch, fileTreeData, projectId, selectedEntityIds])
|
||||
}, [fileTreeData, projectId, selectedEntityIds])
|
||||
|
||||
// moves entities. Tree is updated immediately and data are sync'd after.
|
||||
const finishMoving = useCallback(
|
||||
|
@ -266,20 +238,20 @@ export function useFileTreeActionable() {
|
|||
dispatch({ type: ACTION_TYPES.ERROR, error })
|
||||
})
|
||||
},
|
||||
[dispatch, dispatchMove, fileTreeData, projectId]
|
||||
[dispatchMove, fileTreeData, projectId]
|
||||
)
|
||||
|
||||
const startCreatingFolder = useCallback(() => {
|
||||
dispatch({ type: ACTION_TYPES.START_CREATE_FOLDER })
|
||||
}, [dispatch])
|
||||
}, [])
|
||||
|
||||
const parentFolderId = useMemo(
|
||||
() => getSelectedParentFolderId(fileTreeData, selectedEntityIds),
|
||||
[fileTreeData, selectedEntityIds]
|
||||
)
|
||||
|
||||
const finishCreatingEntity = useCallback(
|
||||
entity => {
|
||||
const parentFolderId = getSelectedParentFolderId(
|
||||
fileTreeData,
|
||||
selectedEntityIds
|
||||
)
|
||||
|
||||
const error = validateCreate(fileTreeData, parentFolderId, entity)
|
||||
if (error) {
|
||||
return Promise.reject(error)
|
||||
|
@ -287,7 +259,7 @@ export function useFileTreeActionable() {
|
|||
|
||||
return syncCreateEntity(projectId, parentFolderId, entity)
|
||||
},
|
||||
[fileTreeData, projectId, selectedEntityIds]
|
||||
[fileTreeData, parentFolderId, projectId]
|
||||
)
|
||||
|
||||
const finishCreatingFolder = useCallback(
|
||||
|
@ -301,15 +273,12 @@ export function useFileTreeActionable() {
|
|||
dispatch({ type: ACTION_TYPES.ERROR, error })
|
||||
})
|
||||
},
|
||||
[dispatch, finishCreatingEntity]
|
||||
[finishCreatingEntity]
|
||||
)
|
||||
|
||||
const startCreatingFile = useCallback(
|
||||
newFileCreateMode => {
|
||||
const startCreatingFile = useCallback(newFileCreateMode => {
|
||||
dispatch({ type: ACTION_TYPES.START_CREATE_FILE, newFileCreateMode })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
}, [])
|
||||
|
||||
const startCreatingDocOrFile = useCallback(() => {
|
||||
startCreatingFile('doc')
|
||||
|
@ -331,7 +300,7 @@ export function useFileTreeActionable() {
|
|||
dispatch({ type: ACTION_TYPES.ERROR, error })
|
||||
})
|
||||
},
|
||||
[dispatch, finishCreatingEntity]
|
||||
[finishCreatingEntity]
|
||||
)
|
||||
|
||||
const finishCreatingDoc = useCallback(
|
||||
|
@ -352,28 +321,15 @@ export function useFileTreeActionable() {
|
|||
|
||||
const cancel = useCallback(() => {
|
||||
dispatch({ type: ACTION_TYPES.CANCEL })
|
||||
}, [dispatch])
|
||||
}, [])
|
||||
|
||||
const parentFolderId = useMemo(
|
||||
() => getSelectedParentFolderId(fileTreeData, selectedEntityIds),
|
||||
[fileTreeData, selectedEntityIds]
|
||||
)
|
||||
|
||||
return {
|
||||
const value = {
|
||||
canDelete: selectedEntityIds.size > 0,
|
||||
canRename: selectedEntityIds.size === 1,
|
||||
canCreate: selectedEntityIds.size < 2,
|
||||
isDeleting,
|
||||
isMoving,
|
||||
isRenaming,
|
||||
isCreatingFile,
|
||||
isCreatingFolder,
|
||||
inFlight,
|
||||
actionedEntities,
|
||||
error,
|
||||
...state,
|
||||
parentFolderId,
|
||||
isDuplicate,
|
||||
newFileCreateMode,
|
||||
startRenaming,
|
||||
finishRenaming,
|
||||
startDeleting,
|
||||
|
@ -388,6 +344,32 @@ export function useFileTreeActionable() {
|
|||
finishCreatingLinkedFile,
|
||||
cancel,
|
||||
}
|
||||
|
||||
return (
|
||||
<FileTreeActionableContext.Provider value={value}>
|
||||
{children}
|
||||
</FileTreeActionableContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
FileTreeActionableProvider.propTypes = {
|
||||
hasWritePermissions: PropTypes.bool.isRequired,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
}
|
||||
|
||||
export function useFileTreeActionable() {
|
||||
const context = useContext(FileTreeActionableContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useFileTreeActionable is only available inside FileTreeActionableProvider'
|
||||
)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
function getSelectedParentFolderId(fileTreeData, selectedEntityIds) {
|
||||
|
|
|
@ -85,10 +85,62 @@ export const FileTreeMutableProvider = function ({ rootFolder, children }) {
|
|||
}
|
||||
)
|
||||
|
||||
const dispatchCreateFolder = useCallback((parentFolderId, entity) => {
|
||||
entity.type = 'folder'
|
||||
dispatch({
|
||||
type: ACTION_TYPES.CREATE_ENTITY,
|
||||
parentFolderId,
|
||||
entity,
|
||||
})
|
||||
}, [])
|
||||
|
||||
const dispatchCreateDoc = useCallback((parentFolderId, entity) => {
|
||||
entity.type = 'doc'
|
||||
dispatch({
|
||||
type: ACTION_TYPES.CREATE_ENTITY,
|
||||
parentFolderId,
|
||||
entity,
|
||||
})
|
||||
}, [])
|
||||
|
||||
const dispatchCreateFile = useCallback((parentFolderId, entity) => {
|
||||
entity.type = 'fileRef'
|
||||
dispatch({
|
||||
type: ACTION_TYPES.CREATE_ENTITY,
|
||||
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 = {
|
||||
dispatchCreateDoc,
|
||||
dispatchCreateFile,
|
||||
dispatchCreateFolder,
|
||||
dispatchDelete,
|
||||
dispatchMove,
|
||||
dispatchRename,
|
||||
fileCount,
|
||||
fileTreeData,
|
||||
}
|
||||
|
||||
return (
|
||||
<FileTreeMutableContext.Provider
|
||||
value={{ fileTreeData, fileCount, dispatch }}
|
||||
>
|
||||
<FileTreeMutableContext.Provider value={value}>
|
||||
{children}
|
||||
</FileTreeMutableContext.Provider>
|
||||
)
|
||||
|
@ -103,81 +155,15 @@ FileTreeMutableProvider.propTypes = {
|
|||
}
|
||||
|
||||
export function useFileTreeMutable() {
|
||||
const { fileTreeData, fileCount, dispatch } = useContext(
|
||||
FileTreeMutableContext
|
||||
)
|
||||
const context = useContext(FileTreeMutableContext)
|
||||
|
||||
const dispatchCreateFolder = useCallback(
|
||||
(parentFolderId, entity) => {
|
||||
entity.type = 'folder'
|
||||
dispatch({
|
||||
type: ACTION_TYPES.CREATE_ENTITY,
|
||||
parentFolderId,
|
||||
entity,
|
||||
})
|
||||
},
|
||||
[dispatch]
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useFileTreeMutable is only available in FileTreeMutableProvider'
|
||||
)
|
||||
|
||||
const dispatchCreateDoc = useCallback(
|
||||
(parentFolderId, entity) => {
|
||||
entity.type = 'doc'
|
||||
dispatch({
|
||||
type: ACTION_TYPES.CREATE_ENTITY,
|
||||
parentFolderId,
|
||||
entity,
|
||||
})
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const dispatchCreateFile = useCallback(
|
||||
(parentFolderId, entity) => {
|
||||
entity.type = 'fileRef'
|
||||
dispatch({
|
||||
type: ACTION_TYPES.CREATE_ENTITY,
|
||||
parentFolderId,
|
||||
entity,
|
||||
})
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const dispatchRename = useCallback(
|
||||
(id, newName) => {
|
||||
dispatch({
|
||||
type: ACTION_TYPES.RENAME,
|
||||
newName,
|
||||
id,
|
||||
})
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const dispatchDelete = useCallback(
|
||||
id => {
|
||||
dispatch({ type: ACTION_TYPES.DELETE, id })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const dispatchMove = useCallback(
|
||||
(entityId, toFolderId) => {
|
||||
dispatch({ type: ACTION_TYPES.MOVE, entityId, toFolderId })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
return {
|
||||
fileTreeData,
|
||||
fileCount,
|
||||
dispatchRename,
|
||||
dispatchDelete,
|
||||
dispatchMove,
|
||||
dispatchCreateFolder,
|
||||
dispatchCreateDoc,
|
||||
dispatchCreateFile,
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
function filesInFolder({ docs, folders, fileRefs }) {
|
||||
|
|
|
@ -141,10 +141,32 @@ export function FileTreeSelectableProvider({
|
|||
return () => window.removeEventListener('editor.openDoc', handleOpenDoc)
|
||||
}, [fileTreeData])
|
||||
|
||||
const select = useCallback(id => {
|
||||
dispatch({ type: ACTION_TYPES.SELECT, id })
|
||||
}, [])
|
||||
|
||||
const unselect = useCallback(id => {
|
||||
dispatch({ type: ACTION_TYPES.UNSELECT, id })
|
||||
}, [])
|
||||
|
||||
const selectOrMultiSelectEntity = useCallback((id, isMultiSelect) => {
|
||||
const actionType = isMultiSelect
|
||||
? ACTION_TYPES.MULTI_SELECT
|
||||
: ACTION_TYPES.SELECT
|
||||
|
||||
dispatch({ type: actionType, id })
|
||||
}, [])
|
||||
|
||||
const value = {
|
||||
selectedEntityIds,
|
||||
selectedEntityParentIds,
|
||||
select,
|
||||
unselect,
|
||||
selectOrMultiSelectEntity,
|
||||
}
|
||||
|
||||
return (
|
||||
<FileTreeSelectableContext.Provider
|
||||
value={{ selectedEntityIds, selectedEntityParentIds, dispatch }}
|
||||
>
|
||||
<FileTreeSelectableContext.Provider value={value}>
|
||||
{children}
|
||||
</FileTreeSelectableContext.Provider>
|
||||
)
|
||||
|
@ -161,44 +183,43 @@ FileTreeSelectableProvider.propTypes = {
|
|||
}
|
||||
|
||||
export function useSelectableEntity(id) {
|
||||
const { selectedEntityIds, dispatch } = useContext(FileTreeSelectableContext)
|
||||
const { selectedEntityIds, selectOrMultiSelectEntity } = useContext(
|
||||
FileTreeSelectableContext
|
||||
)
|
||||
|
||||
const isSelected = selectedEntityIds.has(id)
|
||||
|
||||
const selectOrMultiSelectEntity = useCallback(
|
||||
const handleEvent = useCallback(
|
||||
ev => {
|
||||
const isMultiSelect = ev.ctrlKey || ev.metaKey
|
||||
const actionType = isMultiSelect
|
||||
? ACTION_TYPES.MULTI_SELECT
|
||||
: ACTION_TYPES.SELECT
|
||||
|
||||
dispatch({ type: actionType, id })
|
||||
selectOrMultiSelectEntity(id, ev.ctrlKey || ev.metaKey)
|
||||
},
|
||||
[dispatch, id]
|
||||
[id, selectOrMultiSelectEntity]
|
||||
)
|
||||
|
||||
const handleClick = useCallback(
|
||||
ev => {
|
||||
selectOrMultiSelectEntity(ev)
|
||||
handleEvent(ev)
|
||||
},
|
||||
[selectOrMultiSelectEntity]
|
||||
[handleEvent]
|
||||
)
|
||||
|
||||
const handleKeyPress = useCallback(
|
||||
ev => {
|
||||
if (ev.key === 'Enter' || ev.key === ' ') {
|
||||
selectOrMultiSelectEntity(ev)
|
||||
handleEvent(ev)
|
||||
}
|
||||
},
|
||||
[selectOrMultiSelectEntity]
|
||||
[handleEvent]
|
||||
)
|
||||
|
||||
const handleContextMenu = useCallback(
|
||||
ev => {
|
||||
// make sure the right-clicked entity gets selected
|
||||
if (!selectedEntityIds.has(id)) selectOrMultiSelectEntity(ev)
|
||||
if (!selectedEntityIds.has(id)) {
|
||||
handleEvent(ev)
|
||||
}
|
||||
},
|
||||
[id, selectOrMultiSelectEntity, selectedEntityIds]
|
||||
[id, handleEvent, selectedEntityIds]
|
||||
)
|
||||
|
||||
const props = useMemo(
|
||||
|
@ -216,28 +237,13 @@ export function useSelectableEntity(id) {
|
|||
}
|
||||
|
||||
export function useFileTreeSelectable() {
|
||||
const { selectedEntityIds, selectedEntityParentIds, dispatch } = useContext(
|
||||
FileTreeSelectableContext
|
||||
)
|
||||
const context = useContext(FileTreeSelectableContext)
|
||||
|
||||
const select = useCallback(
|
||||
id => {
|
||||
dispatch({ type: ACTION_TYPES.SELECT, id })
|
||||
},
|
||||
[dispatch]
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
`useFileTreeSelectable is only available inside FileTreeSelectableProvider`
|
||||
)
|
||||
|
||||
const unselect = useCallback(
|
||||
id => {
|
||||
dispatch({ type: ACTION_TYPES.UNSELECT, id })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
return {
|
||||
selectedEntityIds,
|
||||
selectedEntityParentIds,
|
||||
select,
|
||||
unselect,
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue