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:
Alf Eaton 2021-05-04 12:36:22 +01:00 committed by Copybot
parent dbc909ae54
commit 13e6166259
3 changed files with 155 additions and 181 deletions

View file

@ -124,41 +124,13 @@ export function FileTreeActionableProvider({ hasWritePermissions, children }) {
defaultState 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 { projectId } = useFileTreeMainContext()
const { fileTreeData, dispatchRename, dispatchMove } = useFileTreeMutable() const { fileTreeData, dispatchRename, dispatchMove } = useFileTreeMutable()
const { selectedEntityIds } = useFileTreeSelectable() const { selectedEntityIds } = useFileTreeSelectable()
const startRenaming = useCallback(() => { const startRenaming = useCallback(() => {
dispatch({ type: ACTION_TYPES.START_RENAME }) dispatch({ type: ACTION_TYPES.START_RENAME })
}, [dispatch]) }, [])
// update the entity with the new name immediately in the tree, but revert to // update the entity with the new name immediately in the tree, but revert to
// the old name if the sync fails // 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( const isDuplicate = useCallback(
@ -203,9 +175,9 @@ export function useFileTreeActionable() {
entityId => findInTreeOrThrow(fileTreeData, entityId).entity entityId => findInTreeOrThrow(fileTreeData, entityId).entity
) )
dispatch({ type: ACTION_TYPES.START_DELETE, actionedEntities }) 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(() => { const finishDeleting = useCallback(() => {
dispatch({ type: ACTION_TYPES.DELETING }) dispatch({ type: ACTION_TYPES.DELETING })
@ -227,7 +199,7 @@ export function useFileTreeActionable() {
// set an error and allow user to retry // set an error and allow user to retry
dispatch({ type: ACTION_TYPES.ERROR, error }) 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. // moves entities. Tree is updated immediately and data are sync'd after.
const finishMoving = useCallback( const finishMoving = useCallback(
@ -266,20 +238,20 @@ export function useFileTreeActionable() {
dispatch({ type: ACTION_TYPES.ERROR, error }) dispatch({ type: ACTION_TYPES.ERROR, error })
}) })
}, },
[dispatch, dispatchMove, fileTreeData, projectId] [dispatchMove, fileTreeData, projectId]
) )
const startCreatingFolder = useCallback(() => { const startCreatingFolder = useCallback(() => {
dispatch({ type: ACTION_TYPES.START_CREATE_FOLDER }) dispatch({ type: ACTION_TYPES.START_CREATE_FOLDER })
}, [dispatch]) }, [])
const parentFolderId = useMemo(
() => getSelectedParentFolderId(fileTreeData, selectedEntityIds),
[fileTreeData, selectedEntityIds]
)
const finishCreatingEntity = useCallback( const finishCreatingEntity = useCallback(
entity => { entity => {
const parentFolderId = getSelectedParentFolderId(
fileTreeData,
selectedEntityIds
)
const error = validateCreate(fileTreeData, parentFolderId, entity) const error = validateCreate(fileTreeData, parentFolderId, entity)
if (error) { if (error) {
return Promise.reject(error) return Promise.reject(error)
@ -287,7 +259,7 @@ export function useFileTreeActionable() {
return syncCreateEntity(projectId, parentFolderId, entity) return syncCreateEntity(projectId, parentFolderId, entity)
}, },
[fileTreeData, projectId, selectedEntityIds] [fileTreeData, parentFolderId, projectId]
) )
const finishCreatingFolder = useCallback( const finishCreatingFolder = useCallback(
@ -301,15 +273,12 @@ export function useFileTreeActionable() {
dispatch({ type: ACTION_TYPES.ERROR, error }) dispatch({ type: ACTION_TYPES.ERROR, error })
}) })
}, },
[dispatch, finishCreatingEntity] [finishCreatingEntity]
) )
const startCreatingFile = useCallback( const startCreatingFile = useCallback(newFileCreateMode => {
newFileCreateMode => {
dispatch({ type: ACTION_TYPES.START_CREATE_FILE, newFileCreateMode }) dispatch({ type: ACTION_TYPES.START_CREATE_FILE, newFileCreateMode })
}, }, [])
[dispatch]
)
const startCreatingDocOrFile = useCallback(() => { const startCreatingDocOrFile = useCallback(() => {
startCreatingFile('doc') startCreatingFile('doc')
@ -331,7 +300,7 @@ export function useFileTreeActionable() {
dispatch({ type: ACTION_TYPES.ERROR, error }) dispatch({ type: ACTION_TYPES.ERROR, error })
}) })
}, },
[dispatch, finishCreatingEntity] [finishCreatingEntity]
) )
const finishCreatingDoc = useCallback( const finishCreatingDoc = useCallback(
@ -352,28 +321,15 @@ export function useFileTreeActionable() {
const cancel = useCallback(() => { const cancel = useCallback(() => {
dispatch({ type: ACTION_TYPES.CANCEL }) dispatch({ type: ACTION_TYPES.CANCEL })
}, [dispatch]) }, [])
const parentFolderId = useMemo( const value = {
() => getSelectedParentFolderId(fileTreeData, selectedEntityIds),
[fileTreeData, selectedEntityIds]
)
return {
canDelete: selectedEntityIds.size > 0, canDelete: selectedEntityIds.size > 0,
canRename: selectedEntityIds.size === 1, canRename: selectedEntityIds.size === 1,
canCreate: selectedEntityIds.size < 2, canCreate: selectedEntityIds.size < 2,
isDeleting, ...state,
isMoving,
isRenaming,
isCreatingFile,
isCreatingFolder,
inFlight,
actionedEntities,
error,
parentFolderId, parentFolderId,
isDuplicate, isDuplicate,
newFileCreateMode,
startRenaming, startRenaming,
finishRenaming, finishRenaming,
startDeleting, startDeleting,
@ -388,6 +344,32 @@ export function useFileTreeActionable() {
finishCreatingLinkedFile, finishCreatingLinkedFile,
cancel, 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) { function getSelectedParentFolderId(fileTreeData, selectedEntityIds) {

View file

@ -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 ( return (
<FileTreeMutableContext.Provider <FileTreeMutableContext.Provider value={value}>
value={{ fileTreeData, fileCount, dispatch }}
>
{children} {children}
</FileTreeMutableContext.Provider> </FileTreeMutableContext.Provider>
) )
@ -103,81 +155,15 @@ FileTreeMutableProvider.propTypes = {
} }
export function useFileTreeMutable() { export function useFileTreeMutable() {
const { fileTreeData, fileCount, dispatch } = useContext( const context = useContext(FileTreeMutableContext)
FileTreeMutableContext
)
const dispatchCreateFolder = useCallback( if (!context) {
(parentFolderId, entity) => { throw new Error(
entity.type = 'folder' 'useFileTreeMutable is only available in FileTreeMutableProvider'
dispatch({
type: ACTION_TYPES.CREATE_ENTITY,
parentFolderId,
entity,
})
},
[dispatch]
) )
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 }) { function filesInFolder({ docs, folders, fileRefs }) {

View file

@ -141,10 +141,32 @@ export function FileTreeSelectableProvider({
return () => window.removeEventListener('editor.openDoc', handleOpenDoc) return () => window.removeEventListener('editor.openDoc', handleOpenDoc)
}, [fileTreeData]) }, [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 ( return (
<FileTreeSelectableContext.Provider <FileTreeSelectableContext.Provider value={value}>
value={{ selectedEntityIds, selectedEntityParentIds, dispatch }}
>
{children} {children}
</FileTreeSelectableContext.Provider> </FileTreeSelectableContext.Provider>
) )
@ -161,44 +183,43 @@ FileTreeSelectableProvider.propTypes = {
} }
export function useSelectableEntity(id) { export function useSelectableEntity(id) {
const { selectedEntityIds, dispatch } = useContext(FileTreeSelectableContext) const { selectedEntityIds, selectOrMultiSelectEntity } = useContext(
FileTreeSelectableContext
)
const isSelected = selectedEntityIds.has(id) const isSelected = selectedEntityIds.has(id)
const selectOrMultiSelectEntity = useCallback( const handleEvent = useCallback(
ev => { ev => {
const isMultiSelect = ev.ctrlKey || ev.metaKey selectOrMultiSelectEntity(id, ev.ctrlKey || ev.metaKey)
const actionType = isMultiSelect
? ACTION_TYPES.MULTI_SELECT
: ACTION_TYPES.SELECT
dispatch({ type: actionType, id })
}, },
[dispatch, id] [id, selectOrMultiSelectEntity]
) )
const handleClick = useCallback( const handleClick = useCallback(
ev => { ev => {
selectOrMultiSelectEntity(ev) handleEvent(ev)
}, },
[selectOrMultiSelectEntity] [handleEvent]
) )
const handleKeyPress = useCallback( const handleKeyPress = useCallback(
ev => { ev => {
if (ev.key === 'Enter' || ev.key === ' ') { if (ev.key === 'Enter' || ev.key === ' ') {
selectOrMultiSelectEntity(ev) handleEvent(ev)
} }
}, },
[selectOrMultiSelectEntity] [handleEvent]
) )
const handleContextMenu = useCallback( const handleContextMenu = useCallback(
ev => { ev => {
// make sure the right-clicked entity gets selected // 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( const props = useMemo(
@ -216,28 +237,13 @@ export function useSelectableEntity(id) {
} }
export function useFileTreeSelectable() { export function useFileTreeSelectable() {
const { selectedEntityIds, selectedEntityParentIds, dispatch } = useContext( const context = useContext(FileTreeSelectableContext)
FileTreeSelectableContext
)
const select = useCallback( if (!context) {
id => { throw new Error(
dispatch({ type: ACTION_TYPES.SELECT, id }) `useFileTreeSelectable is only available inside FileTreeSelectableProvider`
},
[dispatch]
) )
const unselect = useCallback(
id => {
dispatch({ type: ACTION_TYPES.UNSELECT, id })
},
[dispatch]
)
return {
selectedEntityIds,
selectedEntityParentIds,
select,
unselect,
} }
return context
} }