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
)
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 => {
dispatch({ type: ACTION_TYPES.START_CREATE_FILE, newFileCreateMode })
},
[dispatch]
)
const startCreatingFile = useCallback(newFileCreateMode => {
dispatch({ type: ACTION_TYPES.START_CREATE_FILE, newFileCreateMode })
}, [])
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) {

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 (
<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]
)
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,
if (!context) {
throw new Error(
'useFileTreeMutable is only available in FileTreeMutableProvider'
)
}
return context
}
function filesInFolder({ docs, folders, fileRefs }) {

View file

@ -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]
)
const unselect = useCallback(
id => {
dispatch({ type: ACTION_TYPES.UNSELECT, id })
},
[dispatch]
)
return {
selectedEntityIds,
selectedEntityParentIds,
select,
unselect,
if (!context) {
throw new Error(
`useFileTreeSelectable is only available inside FileTreeSelectableProvider`
)
}
return context
}