Răsfoiți Sursa

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
Tim Down 2 ani în urmă
părinte
comite
6f34a84ebd

+ 3 - 3
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<MainDocument[] | undefined>('docs')
+  const docs: MainDocument[] = useFileTreeData().docs
   const { rootDocId, setRootDocId } = useProjectSettingsContext()
 
   const validDocsOptions = useMemo(() => {

+ 31 - 0
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<MainDocument>(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
+}

+ 0 - 5
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')

+ 0 - 3
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<ReviewPanel.Value<'docs'>>('docs')
   const [entries] = useScopeValue<ReviewPanel.Value<'entries'>>(
     '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,

+ 0 - 1
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', {

+ 4 - 1
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 (
     <Container>

+ 7 - 2
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()
 

+ 0 - 3
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<ReviewPanel.Value<'docs'>>('docs')
   const [entries] = useScopeValue<ReviewPanel.Value<'entries'>>(
     '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,

+ 1 - 5
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<DocId, boolean>
     commentThreads: ReviewPanelCommentThreads
-    docs: MainDocument[] | undefined
     entries: ReviewPanelEntries
     entryHover: boolean
     isAddingComment: boolean

+ 8 - 0
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 (

+ 19 - 21
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('<EditorLeftMenu />', function () {
   beforeEach(function () {
@@ -390,40 +391,37 @@ describe('<EditorLeftMenu />', 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(
-          <EditorProviders scope={scope}>
+          <EditorProviders scope={scope} rootFolder={[rootFolder as any]}>
             <EditorLeftMenu />
           </EditorProviders>
         )
 
+        const docs = docsInFolder(rootFolder)
+
         cy.get<HTMLOptionElement>('#settings-menu-rootDocId option').then(
           options => {
             const values = [...options].map(o => o.value)

+ 14 - 14
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('<SettingsDocument />', 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('<SettingsDocument />', function () {
 
   it('shows correct menu', async function () {
     renderWithEditorContext(<SettingsDocument />, {
-      scope: {
-        docs,
-      },
+      rootFolder: [rootFolder],
     })
 
     const select = screen.getByLabelText('Main document')

+ 3 - 0
services/web/test/frontend/features/review-panel/review-panel.spec.tsx

@@ -22,6 +22,9 @@ describe('<ReviewPanel />', 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(

+ 0 - 2
services/web/types/project-settings.ts

@@ -11,8 +11,6 @@ export type MainDocument = {
   doc: {
     name: string
     id: DocId
-    type: string
-    selected: boolean
   }
   path: string
 }