From 6f34a84ebd5a16d3e57440817a5d759b12ac5ff6 Mon Sep 17 00:00:00 2001 From: Tim Down <158919+timdown@users.noreply.github.com> Date: Mon, 13 Nov 2023 11:11:33 +0000 Subject: [PATCH] Merge pull request #15684 from overleaf/td-ide-page-main-doc-left-menu Add docs to FileTreeDataContext to replace 'docs' scope value in React code GitOrigin-RevId: 430f795eb0cd17f0f4fab9c61e46fb04ff3030b3 --- .../components/settings/settings-document.tsx | 6 +-- .../features/file-tree/util/docs-in-folder.ts | 31 ++++++++++++++ .../ide-react/context/ide-react-context.tsx | 5 --- .../hooks/use-review-panel-state.ts | 3 -- .../review-panel-context-adapter.ts | 1 - .../review-panel/overview-container.tsx | 5 ++- .../toolbar/resolved-comments-dropdown.tsx | 9 ++++- .../hooks/use-angular-review-panel-state.ts | 3 -- .../review-panel/types/review-panel-state.ts | 6 +-- .../shared/context/file-tree-data-context.jsx | 8 ++++ .../editor-left-menu.spec.tsx | 40 +++++++++---------- .../settings/settings-document.test.tsx | 28 ++++++------- .../review-panel/review-panel.spec.tsx | 3 ++ services/web/types/project-settings.ts | 2 - 14 files changed, 90 insertions(+), 60 deletions(-) create mode 100644 services/web/frontend/js/features/file-tree/util/docs-in-folder.ts diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-document.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-document.tsx index 465b8be083..9c9356b7ff 100644 --- a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-document.tsx +++ b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-document.tsx @@ -2,16 +2,16 @@ import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import isValidTeXFile from '../../../../main/is-valid-tex-file' import { useEditorContext } from '../../../../shared/context/editor-context' -import useScopeValue from '../../../../shared/hooks/use-scope-value' import { useProjectSettingsContext } from '../../context/project-settings-context' import SettingsMenuSelect from './settings-menu-select' import type { Option } from './settings-menu-select' -import type { MainDocument } from '../../../../../../types/project-settings' +import { useFileTreeData } from '@/shared/context/file-tree-data-context' +import { MainDocument } from '../../../../../../types/project-settings' export default function SettingsDocument() { const { t } = useTranslation() const { permissionsLevel } = useEditorContext() - const [docs] = useScopeValue('docs') + const docs: MainDocument[] = useFileTreeData().docs const { rootDocId, setRootDocId } = useProjectSettingsContext() const validDocsOptions = useMemo(() => { diff --git a/services/web/frontend/js/features/file-tree/util/docs-in-folder.ts b/services/web/frontend/js/features/file-tree/util/docs-in-folder.ts new file mode 100644 index 0000000000..06c62db133 --- /dev/null +++ b/services/web/frontend/js/features/file-tree/util/docs-in-folder.ts @@ -0,0 +1,31 @@ +import { Folder } from '../../../../../types/folder' +import { DocId, MainDocument } from '../../../../../types/project-settings' + +function findAllDocsInFolder(folder: Folder, path = '') { + const docs = folder.docs.map(doc => ({ + doc: { id: doc._id as DocId, name: doc.name }, + path: path + doc.name, + })) + for (const subFolder of folder.folders) { + docs.push(...findAllDocsInFolder(subFolder, `${path}${subFolder.name}/`)) + } + return docs +} + +export function docsInFolder(folder: Folder) { + const docsInTree = findAllDocsInFolder(folder) + docsInTree.sort(function (a, b) { + const aDepth = (a.path.match(/\//g) || []).length + const bDepth = (b.path.match(/\//g) || []).length + if (aDepth - bDepth !== 0) { + return -(aDepth - bDepth) // Deeper path == folder first + } else if (a.path < b.path) { + return -1 + } else if (a.path > b.path) { + return 1 + } else { + return 0 + } + }) + return docsInTree +} diff --git a/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx b/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx index b19d39b393..096f7506db 100644 --- a/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx @@ -56,10 +56,6 @@ function populatePdfScope(store: ReactScopeValueStore) { store.allowNonExistentPath('pdf', true) } -function populateFileTreeScope(store: ReactScopeValueStore) { - store.set('docs', []) -} - function createReactScopeValueStore(projectId: string) { const scopeStore = new ReactScopeValueStore() @@ -76,7 +72,6 @@ function createReactScopeValueStore(projectId: string) { populateSettingsScope(scopeStore) populateOnlineUsersScope(scopeStore) populateReferenceScope(scopeStore) - populateFileTreeScope(scopeStore) populateReviewPanelScope(scopeStore) scopeStore.allowNonExistentPath('hasLintingError') diff --git a/services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts b/services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts index eb4947b23d..d5c4fa0cbc 100644 --- a/services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts +++ b/services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts @@ -33,7 +33,6 @@ function useReviewPanelState(): ReviewPanelStateReactIde { 'reviewPanel.commentThreads', true ) - const [docs] = useScopeValue>('docs') const [entries] = useScopeValue>( 'reviewPanel.entries', true @@ -156,7 +155,6 @@ function useReviewPanelState(): ReviewPanelStateReactIde { () => ({ collapsed, commentThreads, - docs, entries, entryHover, isAddingComment, @@ -183,7 +181,6 @@ function useReviewPanelState(): ReviewPanelStateReactIde { [ collapsed, commentThreads, - docs, entries, entryHover, isAddingComment, diff --git a/services/web/frontend/js/features/ide-react/scope-adapters/review-panel-context-adapter.ts b/services/web/frontend/js/features/ide-react/scope-adapters/review-panel-context-adapter.ts index 4577284f53..c82cb21a3d 100644 --- a/services/web/frontend/js/features/ide-react/scope-adapters/review-panel-context-adapter.ts +++ b/services/web/frontend/js/features/ide-react/scope-adapters/review-panel-context-adapter.ts @@ -6,7 +6,6 @@ export default function populateReviewPanelScope(store: ReactScopeValueStore) { store.set('reviewPanel.overview.loading', false) store.set('reviewPanel.nVisibleSelectedChanges', 0) store.set('reviewPanel.commentThreads', {}) - store.set('docs', undefined) store.set('reviewPanel.entries', {}) store.set('loadingThreads', true) store.set('permissions', { diff --git a/services/web/frontend/js/features/source-editor/components/review-panel/overview-container.tsx b/services/web/frontend/js/features/source-editor/components/review-panel/overview-container.tsx index 32bdb3f628..073e9afc71 100644 --- a/services/web/frontend/js/features/source-editor/components/review-panel/overview-container.tsx +++ b/services/web/frontend/js/features/source-editor/components/review-panel/overview-container.tsx @@ -5,9 +5,12 @@ import Nav from './nav' import Icon from '../../../../shared/components/icon' import OverviewFile from './overview-file' import { useReviewPanelValueContext } from '../../context/review-panel/review-panel-context' +import { useFileTreeData } from '@/shared/context/file-tree-data-context' +import { MainDocument } from '../../../../../../types/project-settings' function OverviewContainer() { - const { loading, docs } = useReviewPanelValueContext() + const { loading } = useReviewPanelValueContext() + const docs: MainDocument[] = useFileTreeData().docs return ( diff --git a/services/web/frontend/js/features/source-editor/components/review-panel/toolbar/resolved-comments-dropdown.tsx b/services/web/frontend/js/features/source-editor/components/review-panel/toolbar/resolved-comments-dropdown.tsx index a18c4edee6..896e0a5a64 100644 --- a/services/web/frontend/js/features/source-editor/components/review-panel/toolbar/resolved-comments-dropdown.tsx +++ b/services/web/frontend/js/features/source-editor/components/review-panel/toolbar/resolved-comments-dropdown.tsx @@ -13,8 +13,12 @@ import { ThreadId, } from '../../../../../../../types/review-panel/review-panel' import { ReviewPanelResolvedCommentThread } from '../../../../../../../types/review-panel/comment-thread' -import { DocId } from '../../../../../../../types/project-settings' +import { + DocId, + MainDocument, +} from '../../../../../../../types/project-settings' import { ReviewPanelEntry } from '../../../../../../../types/review-panel/entry' +import { useFileTreeData } from '@/shared/context/file-tree-data-context' export interface FilteredResolvedComments extends ReviewPanelResolvedCommentThread { @@ -29,8 +33,9 @@ function ResolvedCommentsDropdown() { const { t } = useTranslation() const [isOpen, setIsOpen] = useState(false) const [isLoading, setIsLoading] = useState(false) - const { docs, commentThreads, resolvedComments, permissions } = + const { commentThreads, resolvedComments, permissions } = useReviewPanelValueContext() + const docs: MainDocument[] = useFileTreeData().docs const { refreshResolvedCommentsDropdown } = useReviewPanelUpdaterFnsContext() diff --git a/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts b/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts index bdcba2f30d..0c940eb6f8 100644 --- a/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts +++ b/services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts @@ -26,7 +26,6 @@ function useAngularReviewPanelState(): ReviewPanelState { 'reviewPanel.commentThreads', true ) - const [docs] = useScopeValue>('docs') const [entries] = useScopeValue>( 'reviewPanel.entries', true @@ -143,7 +142,6 @@ function useAngularReviewPanelState(): ReviewPanelState { () => ({ collapsed, commentThreads, - docs, entries, entryHover, isAddingComment, @@ -170,7 +168,6 @@ function useAngularReviewPanelState(): ReviewPanelState { [ collapsed, commentThreads, - docs, entries, entryHover, isAddingComment, diff --git a/services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts b/services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts index a5c0ce598e..2c2b9d05dc 100644 --- a/services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts +++ b/services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts @@ -7,10 +7,7 @@ import { SubView, ThreadId, } from '../../../../../../../types/review-panel/review-panel' -import { - DocId, - MainDocument, -} from '../../../../../../../types/project-settings' +import { DocId } from '../../../../../../../types/project-settings' import { dispatchReviewPanelLayout } from '../../../extensions/changes/change-manager' /* eslint-disable no-use-before-define */ @@ -18,7 +15,6 @@ export interface ReviewPanelState { values: { collapsed: Record commentThreads: ReviewPanelCommentThreads - docs: MainDocument[] | undefined entries: ReviewPanelEntries entryHover: boolean isAddingComment: boolean diff --git a/services/web/frontend/js/shared/context/file-tree-data-context.jsx b/services/web/frontend/js/shared/context/file-tree-data-context.jsx index aced211eb2..b0dac3dae5 100644 --- a/services/web/frontend/js/shared/context/file-tree-data-context.jsx +++ b/services/web/frontend/js/shared/context/file-tree-data-context.jsx @@ -16,6 +16,7 @@ import { } from '../../features/file-tree/util/mutate-in-tree' import { countFiles } from '../../features/file-tree/util/count-in-tree' import useDeepCompareEffect from '../../shared/hooks/use-deep-compare-effect' +import { docsInFolder } from '@/features/file-tree/util/docs-in-folder' const FileTreeDataContext = createContext() @@ -147,6 +148,11 @@ export function FileTreeDataProvider({ children }) { const [selectedEntities, setSelectedEntities] = useState([]) + const docs = useMemo( + () => (fileTreeData ? docsInFolder(fileTreeData) : undefined), + [fileTreeData] + ) + useDeepCompareEffect(() => { dispatch({ type: ACTION_TYPES.RESET, @@ -210,6 +216,7 @@ export function FileTreeDataProvider({ children }) { hasFolders: fileTreeData?.folders.length > 0, selectedEntities, setSelectedEntities, + docs, } }, [ dispatchCreateDoc, @@ -222,6 +229,7 @@ export function FileTreeDataProvider({ children }) { fileTreeData, selectedEntities, setSelectedEntities, + docs, ]) return ( diff --git a/services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx b/services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx index 4c4e52c375..daac892279 100644 --- a/services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx +++ b/services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx @@ -1,12 +1,13 @@ import EditorLeftMenu from '../../../../frontend/js/features/editor-left-menu/components/editor-left-menu' import { AllowedImageName, - MainDocument, OverallThemeMeta, SpellCheckLanguage, } from '../../../../types/project-settings' import { EditorProviders } from '../../helpers/editor-providers' import { mockScope } from './scope' +import { Folder } from '../../../../types/folder' +import { docsInFolder } from '@/features/file-tree/util/docs-in-folder' describe('', function () { beforeEach(function () { @@ -390,40 +391,37 @@ describe('', function () { }) it('shows document menu correctly', function () { - const docs: MainDocument[] = [ - { - path: 'main.tex', - doc: { + const rootFolder: Folder = { + _id: 'root-folder-id', + name: 'rootFolder', + docs: [ + { + _id: 'id1', name: 'main.tex', - id: 'id1', - type: 'doc', - selected: false, - } as MainDocument['doc'], - }, - { - path: 'folder/main2.tex', - doc: { + }, + { + _id: 'id2', name: 'main2.tex', - id: 'id2', - type: 'doc', - selected: false, - } as MainDocument['doc'], - }, - ] + }, + ], + fileRefs: [], + folders: [], + } const scope = mockScope({ ui: { leftMenuShown: true, }, - docs, }) cy.mount( - + ) + const docs = docsInFolder(rootFolder) + cy.get('#settings-menu-rootDocId option').then( options => { const values = [...options].map(o => o.value) diff --git a/services/web/test/frontend/features/editor-left-menu/components/settings/settings-document.test.tsx b/services/web/test/frontend/features/editor-left-menu/components/settings/settings-document.test.tsx index 1694e2fc91..0638546600 100644 --- a/services/web/test/frontend/features/editor-left-menu/components/settings/settings-document.test.tsx +++ b/services/web/test/frontend/features/editor-left-menu/components/settings/settings-document.test.tsx @@ -5,21 +5,23 @@ import fetchMock from 'fetch-mock' import SettingsDocument from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-document' import * as isValidTeXFileModule from '../../../../../../frontend/js/main/is-valid-tex-file' import { renderWithEditorContext } from '../../../../helpers/render-with-context' -import type { MainDocument } from '../../../../../../types/project-settings' +import { Folder } from '../../../../../../types/folder' describe('', function () { let isValidTeXFileStub: sinon.SinonStub - const docs: MainDocument[] = [ - { - path: 'main.tex', - doc: { + + const rootFolder: Folder = { + _id: 'root-folder-id', + name: 'rootFolder', + docs: [ + { + _id: '123abc', name: 'main.tex', - id: '123abc', - type: 'doc', - selected: false, - } as MainDocument['doc'], - }, - ] + }, + ], + fileRefs: [], + folders: [], + } beforeEach(function () { isValidTeXFileStub = sinon @@ -34,9 +36,7 @@ describe('', function () { it('shows correct menu', async function () { renderWithEditorContext(, { - scope: { - docs, - }, + rootFolder: [rootFolder], }) const select = screen.getByLabelText('Main document') diff --git a/services/web/test/frontend/features/review-panel/review-panel.spec.tsx b/services/web/test/frontend/features/review-panel/review-panel.spec.tsx index 2caa0aa765..d8d58d18bf 100644 --- a/services/web/test/frontend/features/review-panel/review-panel.spec.tsx +++ b/services/web/test/frontend/features/review-panel/review-panel.spec.tsx @@ -22,6 +22,9 @@ describe('', function () { const scope = mockScope('') scope.editor.showVisual = true + // The tests expect no documents, so remove them from the scope + scope.project.rootFolder = [] + cy.wrap(scope).as('scope') cy.mount( diff --git a/services/web/types/project-settings.ts b/services/web/types/project-settings.ts index 2595367150..13e49d7757 100644 --- a/services/web/types/project-settings.ts +++ b/services/web/types/project-settings.ts @@ -11,8 +11,6 @@ export type MainDocument = { doc: { name: string id: DocId - type: string - selected: boolean } path: string }