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
|
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) {
|
||||||
|
|
|
@ -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 }) {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue