Streamline the project references context provider (#19387)

GitOrigin-RevId: 9af00459ec121a605b84809b349a0bc258896048
This commit is contained in:
Alf Eaton 2024-07-15 10:36:39 +01:00 committed by Copybot
parent a55d9fcf38
commit 66c55b0647
22 changed files with 55 additions and 144 deletions

View file

@ -10,7 +10,6 @@ import { FC } from 'react'
// FileTreeMutable: provides entities mutation operations
// FileTreeSelectable: handles selection and multi-selection
const FileTreeContext: FC<{
reindexReferences: () => void
refProviders: Record<string, boolean>
setRefProviderEnabled: (provider: string, value: boolean) => void
setStartedFreeTrial: (value: boolean) => void
@ -18,7 +17,6 @@ const FileTreeContext: FC<{
fileTreeContainer?: HTMLDivElement
}> = ({
refProviders,
reindexReferences,
setRefProviderEnabled,
setStartedFreeTrial,
onSelect,
@ -30,10 +28,9 @@ const FileTreeContext: FC<{
refProviders={refProviders}
setRefProviderEnabled={setRefProviderEnabled}
setStartedFreeTrial={setStartedFreeTrial}
reindexReferences={reindexReferences}
>
<FileTreeSelectableProvider onSelect={onSelect}>
<FileTreeActionableProvider reindexReferences={reindexReferences}>
<FileTreeActionableProvider>
<FileTreeDraggableProvider fileTreeContainer={fileTreeContainer}>
{children}
</FileTreeDraggableProvider>

View file

@ -26,11 +26,9 @@ const FileTreeRoot = React.memo<{
isConnected: boolean
setRefProviderEnabled: () => void
setStartedFreeTrial: () => void
reindexReferences: () => void
refProviders: Record<string, boolean>
}>(function FileTreeRoot({
refProviders,
reindexReferences,
setRefProviderEnabled,
setStartedFreeTrial,
onSelect,
@ -95,7 +93,6 @@ const FileTreeRoot = React.memo<{
refProviders={refProviders}
setRefProviderEnabled={setRefProviderEnabled}
setStartedFreeTrial={setStartedFreeTrial}
reindexReferences={reindexReferences}
onSelect={onSelect}
fileTreeContainer={fileTreeContainer}
>

View file

@ -33,6 +33,7 @@ import {
DuplicateFilenameMoveError,
} from '../errors'
import { Folder } from '../../../../../types/folder'
import { useReferencesContext } from '@/features/ide-react/context/references-context'
type DroppedFile = File & {
relativePath?: string
@ -216,11 +217,10 @@ function fileTreeActionableReducer(state: State, action: Action) {
}
}
export const FileTreeActionableProvider: FC<{
reindexReferences: () => void
}> = ({ reindexReferences, children }) => {
export const FileTreeActionableProvider: FC = ({ children }) => {
const { _id: projectId } = useProjectContext()
const { permissionsLevel } = useEditorContext()
const { indexAllReferences } = useReferencesContext()
const [state, dispatch] = useReducer(
permissionsLevel === 'readOnly'
@ -305,7 +305,7 @@ export const FileTreeActionableProvider: FC<{
// @ts-ignore (TODO: improve mapSeries types)
.then(() => {
if (shouldReindexReferences) {
reindexReferences()
indexAllReferences(true)
}
dispatch({ type: ACTION_TYPES.CLEAR })
})
@ -314,7 +314,7 @@ export const FileTreeActionableProvider: FC<{
dispatch({ type: ACTION_TYPES.ERROR, error })
})
)
}, [fileTreeData, projectId, selectedEntityIds, reindexReferences])
}, [fileTreeData, projectId, selectedEntityIds, indexAllReferences])
// moves entities. Tree is updated immediately and data are sync'd after.
const finishMoving = useCallback(

View file

@ -5,7 +5,6 @@ type ContextMenuCoords = { top: number; left: number }
const FileTreeMainContext = createContext<
| {
refProviders: object
reindexReferences: () => void
setRefProviderEnabled: (provider: string, value: boolean) => void
setStartedFreeTrial: (value: boolean) => void
contextMenuCoords: ContextMenuCoords | null
@ -27,13 +26,11 @@ export function useFileTreeMainContext() {
}
export const FileTreeMainProvider: FC<{
reindexReferences: () => void
refProviders: object
setRefProviderEnabled: (provider: string, value: boolean) => void
setStartedFreeTrial: (value: boolean) => void
}> = ({
refProviders,
reindexReferences,
setRefProviderEnabled,
setStartedFreeTrial,
children,
@ -45,7 +42,6 @@ export const FileTreeMainProvider: FC<{
<FileTreeMainContext.Provider
value={{
refProviders,
reindexReferences,
setRefProviderEnabled,
setStartedFreeTrial,
contextMenuCoords,

View file

@ -1,6 +1,5 @@
import React, { memo, useCallback, useState } from 'react'
import { useUserContext } from '@/shared/context/user-context'
import { useReferencesContext } from '@/features/ide-react/context/references-context'
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
import { RefProviders } from '../../../../../types/user'
@ -9,7 +8,6 @@ import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-o
export const FileTree = memo(function FileTree() {
const user = useUserContext()
const { indexAllReferences } = useReferencesContext()
const { setStartedFreeTrial } = useIdeReactContext()
const { isConnected, connectionState } = useConnectionContext()
const { handleFileTreeInit, handleFileTreeSelect, handleFileTreeDelete } =
@ -19,10 +17,6 @@ export const FileTree = memo(function FileTree() {
() => user.refProviders || {}
)
function reindexReferences() {
indexAllReferences(true)
}
const setRefProviderEnabled = useCallback(
(provider: keyof RefProviders, value = true) => {
setRefProviders(refProviders => ({ ...refProviders, [provider]: value }))
@ -33,7 +27,6 @@ export const FileTree = memo(function FileTree() {
return (
<FileTreeRoot
refProviders={refProviders}
reindexReferences={reindexReferences}
setRefProviderEnabled={setRefProviderEnabled}
setStartedFreeTrial={setStartedFreeTrial}
isConnected={isConnected || connectionState.reconnectAt !== null}

View file

@ -22,7 +22,6 @@ import { populateEditorScope } from '@/features/ide-react/scope-adapters/editor-
import { postJSON } from '@/infrastructure/fetch-json'
import { EventLog } from '@/features/ide-react/editor/event-log'
import { populateOnlineUsersScope } from '@/features/ide-react/context/online-users-context'
import { populateReferenceScope } from '@/features/ide-react/context/references-context'
import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter'
import getMeta from '@/utils/meta'
@ -77,7 +76,6 @@ export function createReactScopeValueStore(projectId: string) {
populateProjectScope(scopeStore)
populatePdfScope(scopeStore)
populateOnlineUsersScope(scopeStore)
populateReferenceScope(scopeStore)
populateReviewPanelScope(scopeStore)
scopeStore.allowNonExistentPath('hasLintingError')

View file

@ -10,64 +10,32 @@ import {
} from 'react'
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
import _ from 'lodash'
import { postJSON } from '@/infrastructure/fetch-json'
import { ShareJsDoc } from '@/features/ide-react/editor/share-js-doc'
import useScopeValue from '@/shared/hooks/use-scope-value'
import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store'
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
import { findDocEntityById } from '@/features/ide-react/util/find-doc-entity-by-id'
import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter'
import { debugConsole } from '@/utils/debugging'
type References = {
keys: string[]
}
type ReferencesContextValue = {
indexReferencesIfDocModified: (
doc: ShareJsDoc,
shouldBroadcast: boolean
) => void
export const ReferencesContext = createContext<
| {
referenceKeys: Set<string>
indexAllReferences: (shouldBroadcast: boolean) => void
}
type IndexReferencesResponse = References
const ReferencesContext = createContext<ReferencesContextValue | undefined>(
undefined
)
export function populateReferenceScope(store: ReactScopeValueStore) {
store.set('$root._references', { keys: [] })
}
| undefined
>(undefined)
export const ReferencesProvider: FC = ({ children }) => {
const { fileTreeData } = useFileTreeData()
const { eventEmitter, projectId } = useIdeReactContext()
const { socket } = useConnectionContext()
const [references, setReferences] =
useScopeValue<References>('$root._references')
const [referenceKeys, setReferenceKeys] = useState(new Set<string>())
const [existingIndexHash, setExistingIndexHash] = useState<
Record<string, { hash: string; timestamp: number }>
>({})
const storeReferencesKeys = useCallback(
(newKeys: string[], replaceExistingKeys: boolean) => {
const oldKeys = references.keys
const keys = replaceExistingKeys ? newKeys : _.union(oldKeys, newKeys)
window.dispatchEvent(
new CustomEvent('project:references', {
detail: keys,
})
)
setReferences({ keys })
},
[references.keys, setReferences]
)
const indexAllReferences = useCallback(
(shouldBroadcast: boolean) => {
postJSON(`/project/${projectId}/references/indexAll`, {
@ -75,15 +43,15 @@ export const ReferencesProvider: FC = ({ children }) => {
shouldBroadcast,
},
})
.then((response: IndexReferencesResponse) => {
storeReferencesKeys(response.keys, true)
.then((response: { keys: string[] }) => {
setReferenceKeys(new Set(response.keys))
})
.catch(error => {
// allow the request to fail
debugConsole.error(error)
})
},
[projectId, storeReferencesKeys]
[projectId]
)
const indexReferencesIfDocModified = useCallback(
@ -132,25 +100,15 @@ export const ReferencesProvider: FC = ({ children }) => {
}
}, [eventEmitter, fileTreeData, indexReferencesIfDocModified])
useEffect(() => {
const handleShouldReindex = () => {
indexAllReferences(true)
}
eventEmitter.on('references:should-reindex', handleShouldReindex)
return () => {
eventEmitter.off('references:should-reindex', handleShouldReindex)
}
}, [eventEmitter, indexAllReferences])
useEffect(() => {
const handleProjectJoined = () => {
// We only need to grab the references when the editor first loads,
// not on every reconnect
socket.on('references:keys:updated', (keys, allDocs) =>
storeReferencesKeys(keys, allDocs)
socket.on('references:keys:updated', (keys, allDocs) => {
setReferenceKeys(oldKeys =>
allDocs ? new Set(keys) : new Set([...oldKeys, ...keys])
)
})
indexAllReferences(false)
}
@ -159,14 +117,14 @@ export const ReferencesProvider: FC = ({ children }) => {
return () => {
eventEmitter.off('project:joined', handleProjectJoined)
}
}, [eventEmitter, indexAllReferences, socket, storeReferencesKeys])
}, [eventEmitter, indexAllReferences, socket])
const value = useMemo<ReferencesContextValue>(
const value = useMemo(
() => ({
indexReferencesIfDocModified,
referenceKeys,
indexAllReferences,
}),
[indexReferencesIfDocModified, indexAllReferences]
[indexAllReferences, referenceKeys]
)
return (
@ -176,7 +134,7 @@ export const ReferencesProvider: FC = ({ children }) => {
)
}
export function useReferencesContext(): ReferencesContextValue {
export function useReferencesContext() {
const context = useContext(ReferencesContext)
if (!context) {

View file

@ -20,7 +20,6 @@ export type IdeEvents = {
'cursor:editor:syncToPdf': []
'scroll:editor:update': []
'comment:start_adding': []
'references:should-reindex': []
'history:toggle': []
'entity:deleted': [entity: FileTreeFindResult]
}

View file

@ -24,7 +24,7 @@ type Metadata = {
labels: Set<string>
packageNames: Set<string>
commands: Command[]
references: string[]
referenceKeys: Set<string>
fileTreeData: Folder
}

View file

@ -60,6 +60,7 @@ import { useLayoutContext } from '@/shared/context/layout-context'
import { debugConsole } from '@/utils/debugging'
import { useMetadataContext } from '@/features/ide-react/context/metadata-context'
import { useUserContext } from '@/shared/context/user-context'
import { useReferencesContext } from '@/features/ide-react/context/references-context'
function useCodeMirrorScope(view: EditorView) {
const { fileTreeData } = useFileTreeData()
@ -107,7 +108,7 @@ function useCodeMirrorScope(view: EditorView) {
const [visual] = useScopeValue<boolean>('editor.showVisual')
const [references] = useScopeValue<{ keys: string[] }>('$root._references')
const { referenceKeys } = useReferencesContext()
// build the translation phrases
const phrases = usePhrases()
@ -214,7 +215,7 @@ function useCodeMirrorScope(view: EditorView) {
// TODO: read this data from the scope?
const metadataRef = useRef({
...metadata,
references: references.keys,
referenceKeys,
fileTreeData,
})
@ -226,13 +227,9 @@ function useCodeMirrorScope(view: EditorView) {
// listen to project reference keys updates
useEffect(() => {
const listener = (event: Event) => {
metadataRef.current.references = (event as CustomEvent<string[]>).detail
metadataRef.current.referenceKeys = referenceKeys
view.dispatch(setMetadata(metadataRef.current))
}
window.addEventListener('project:references', listener)
return () => window.removeEventListener('project:references', listener)
}, [view])
}, [view, referenceKeys])
// listen to project root folder updates
useEffect(() => {

View file

@ -16,10 +16,10 @@ export function buildReferenceCompletions(
return
}
for (const reference of metadata.references) {
for (const referenceKey of metadata.referenceKeys) {
completions.references.push({
type: 'reference',
label: reference,
label: referenceKey,
extend: extendRequiredParameter,
})
}

View file

@ -97,11 +97,6 @@ const initialize = () => {
//
},
$broadcast: () => {},
$root: {
_references: {
keys: ['bibkeyExample'],
},
},
ui: {
chatOpen: true,
pdfLayout: 'flat',

View file

@ -155,9 +155,6 @@ export default {
console.log('started free trial')
},
refProviders: {},
reindexReferences: () => {
console.log('reindex references')
},
setRefProviderEnabled: provider => {
console.log(`ref provider ${provider} enabled`)
},

View file

@ -7,9 +7,6 @@ import PropTypes from 'prop-types'
const defaultFileTreeContextProps = {
refProviders: { mendeley: false, zotero: false },
reindexReferences: () => {
console.log('reindex references')
},
setRefProviderEnabled: provider => {
console.log(`ref provider ${provider} enabled`)
},

View file

@ -30,7 +30,6 @@ describe('<FileTreeRoot/>', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}
@ -73,7 +72,6 @@ describe('<FileTreeRoot/>', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}
@ -117,7 +115,6 @@ describe('<FileTreeRoot/>', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}
@ -154,7 +151,6 @@ describe('<FileTreeRoot/>', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub().as('onSelect')}
@ -202,7 +198,6 @@ describe('<FileTreeRoot/>', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}
@ -247,7 +242,6 @@ describe('<FileTreeRoot/>', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}
@ -318,7 +312,6 @@ describe('<FileTreeRoot/>', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}

View file

@ -27,7 +27,6 @@ describe('FileTree Context Menu Flow', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}
@ -64,7 +63,6 @@ describe('FileTree Context Menu Flow', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}
@ -101,7 +99,6 @@ describe('FileTree Context Menu Flow', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}

View file

@ -30,7 +30,6 @@ describe('FileTree Create Folder Flow', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}
@ -100,7 +99,6 @@ describe('FileTree Create Folder Flow', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}
@ -177,7 +175,6 @@ describe('FileTree Create Folder Flow', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}
@ -244,7 +241,6 @@ describe('FileTree Create Folder Flow', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}

View file

@ -35,7 +35,6 @@ describe('FileTree Delete Entity Flow', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub().as('reindexReferences')}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}
@ -84,7 +83,6 @@ describe('FileTree Delete Entity Flow', function () {
).should('not.exist')
cy.get('@deleteDoc.all').should('have.length', 1)
cy.get('@reindexReferences').should('not.have.been.called')
})
it('continues delete on 404s', function () {
@ -162,7 +160,6 @@ describe('FileTree Delete Entity Flow', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub().as('reindexReferences')}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}
@ -226,7 +223,6 @@ describe('FileTree Delete Entity Flow', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub().as('reindexReferences')}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub()}
@ -282,7 +278,6 @@ describe('FileTree Delete Entity Flow', function () {
cy.get('@deleteDoc.all').should('have.length', 1)
cy.get('@deleteFile.all').should('have.length', 1)
cy.get('@reindexReferences').should('have.been.calledOnce')
})
})
})

View file

@ -42,7 +42,6 @@ describe('FileTree Rename Entity Flow', function () {
>
<FileTreeRoot
refProviders={{}}
reindexReferences={cy.stub()}
setRefProviderEnabled={cy.stub()}
setStartedFreeTrial={cy.stub()}
onSelect={cy.stub().as('onSelect')}

View file

@ -12,7 +12,6 @@ export const FileTreeProvider: FC<{
if (propsRef.current === undefined) {
propsRef.current = {
reindexReferences: cy.stub().as('reindexReferences'),
setRefProviderEnabled: cy.stub().as('setRefProviderEnabled'),
setStartedFreeTrial: cy.stub().as('setStartedFreeTrial'),
onSelect: cy.stub(),

View file

@ -8,6 +8,7 @@ import { User, UserId } from '../../../../../types/user'
import { TestContainer } from '../helpers/test-container'
import { FC } from 'react'
import { MetadataContext } from '@/features/ide-react/context/metadata-context'
import { ReferencesContext } from '@/features/ide-react/context/references-context'
describe('autocomplete', { scrollBehavior: false }, function () {
beforeEach(function () {
@ -62,7 +63,6 @@ describe('autocomplete', { scrollBehavior: false }, function () {
]
const scope = mockScope()
scope.$root._references.keys = ['foo']
scope.project.rootFolder = rootFolder
cy.mount(
@ -209,7 +209,6 @@ describe('autocomplete', { scrollBehavior: false }, function () {
]
const scope = mockScope()
scope.$root._references.keys = ['foo']
cy.mount(
<TestContainer>
@ -373,11 +372,27 @@ describe('autocomplete', { scrollBehavior: false }, function () {
]
const scope = mockScope()
scope.$root._references.keys = ['ref-1', 'ref-2', 'ref-3']
const ReferencesProvider: FC = ({ children }) => {
return (
<ReferencesContext.Provider
value={{
referenceKeys: new Set(['ref-1', 'ref-2', 'ref-3']),
indexAllReferences: cy.stub(),
}}
>
{children}
</ReferencesContext.Provider>
)
}
cy.mount(
<TestContainer>
<EditorProviders scope={scope} rootFolder={rootFolder as any}>
<EditorProviders
scope={scope}
providers={{ ReferencesProvider }}
rootFolder={rootFolder as any}
>
<CodeMirrorEditor />
</EditorProviders>
</TestContainer>
@ -428,7 +443,6 @@ describe('autocomplete', { scrollBehavior: false }, function () {
]
const scope = mockScope()
scope.$root._references.keys = ['foo']
scope.project.rootFolder = rootFolder
cy.mount(
@ -893,7 +907,6 @@ describe('autocomplete', { scrollBehavior: false }, function () {
]
const scope = mockScope()
scope.$root._references.keys = ['foo']
scope.project.rootFolder = rootFolder
cy.mount(

View file

@ -80,10 +80,5 @@ export const mockScope = (content?: string) => {
$on: cy.stub().log(false),
$broadcast: cy.stub().log(false),
$emit: cy.stub().log(false),
$root: {
_references: {
keys: ['foo'],
},
},
}
}