mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
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
This commit is contained in:
parent
1c820de200
commit
6f34a84ebd
14 changed files with 90 additions and 60 deletions
|
@ -2,16 +2,16 @@ import { useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import isValidTeXFile from '../../../../main/is-valid-tex-file'
|
import isValidTeXFile from '../../../../main/is-valid-tex-file'
|
||||||
import { useEditorContext } from '../../../../shared/context/editor-context'
|
import { useEditorContext } from '../../../../shared/context/editor-context'
|
||||||
import useScopeValue from '../../../../shared/hooks/use-scope-value'
|
|
||||||
import { useProjectSettingsContext } from '../../context/project-settings-context'
|
import { useProjectSettingsContext } from '../../context/project-settings-context'
|
||||||
import SettingsMenuSelect from './settings-menu-select'
|
import SettingsMenuSelect from './settings-menu-select'
|
||||||
import type { Option } 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() {
|
export default function SettingsDocument() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { permissionsLevel } = useEditorContext()
|
const { permissionsLevel } = useEditorContext()
|
||||||
const [docs] = useScopeValue<MainDocument[] | undefined>('docs')
|
const docs: MainDocument[] = useFileTreeData().docs
|
||||||
const { rootDocId, setRootDocId } = useProjectSettingsContext()
|
const { rootDocId, setRootDocId } = useProjectSettingsContext()
|
||||||
|
|
||||||
const validDocsOptions = useMemo(() => {
|
const validDocsOptions = useMemo(() => {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -56,10 +56,6 @@ function populatePdfScope(store: ReactScopeValueStore) {
|
||||||
store.allowNonExistentPath('pdf', true)
|
store.allowNonExistentPath('pdf', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateFileTreeScope(store: ReactScopeValueStore) {
|
|
||||||
store.set('docs', [])
|
|
||||||
}
|
|
||||||
|
|
||||||
function createReactScopeValueStore(projectId: string) {
|
function createReactScopeValueStore(projectId: string) {
|
||||||
const scopeStore = new ReactScopeValueStore()
|
const scopeStore = new ReactScopeValueStore()
|
||||||
|
|
||||||
|
@ -76,7 +72,6 @@ function createReactScopeValueStore(projectId: string) {
|
||||||
populateSettingsScope(scopeStore)
|
populateSettingsScope(scopeStore)
|
||||||
populateOnlineUsersScope(scopeStore)
|
populateOnlineUsersScope(scopeStore)
|
||||||
populateReferenceScope(scopeStore)
|
populateReferenceScope(scopeStore)
|
||||||
populateFileTreeScope(scopeStore)
|
|
||||||
populateReviewPanelScope(scopeStore)
|
populateReviewPanelScope(scopeStore)
|
||||||
|
|
||||||
scopeStore.allowNonExistentPath('hasLintingError')
|
scopeStore.allowNonExistentPath('hasLintingError')
|
||||||
|
|
|
@ -33,7 +33,6 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||||
'reviewPanel.commentThreads',
|
'reviewPanel.commentThreads',
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
const [docs] = useScopeValue<ReviewPanel.Value<'docs'>>('docs')
|
|
||||||
const [entries] = useScopeValue<ReviewPanel.Value<'entries'>>(
|
const [entries] = useScopeValue<ReviewPanel.Value<'entries'>>(
|
||||||
'reviewPanel.entries',
|
'reviewPanel.entries',
|
||||||
true
|
true
|
||||||
|
@ -156,7 +155,6 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||||
() => ({
|
() => ({
|
||||||
collapsed,
|
collapsed,
|
||||||
commentThreads,
|
commentThreads,
|
||||||
docs,
|
|
||||||
entries,
|
entries,
|
||||||
entryHover,
|
entryHover,
|
||||||
isAddingComment,
|
isAddingComment,
|
||||||
|
@ -183,7 +181,6 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||||
[
|
[
|
||||||
collapsed,
|
collapsed,
|
||||||
commentThreads,
|
commentThreads,
|
||||||
docs,
|
|
||||||
entries,
|
entries,
|
||||||
entryHover,
|
entryHover,
|
||||||
isAddingComment,
|
isAddingComment,
|
||||||
|
|
|
@ -6,7 +6,6 @@ export default function populateReviewPanelScope(store: ReactScopeValueStore) {
|
||||||
store.set('reviewPanel.overview.loading', false)
|
store.set('reviewPanel.overview.loading', false)
|
||||||
store.set('reviewPanel.nVisibleSelectedChanges', 0)
|
store.set('reviewPanel.nVisibleSelectedChanges', 0)
|
||||||
store.set('reviewPanel.commentThreads', {})
|
store.set('reviewPanel.commentThreads', {})
|
||||||
store.set('docs', undefined)
|
|
||||||
store.set('reviewPanel.entries', {})
|
store.set('reviewPanel.entries', {})
|
||||||
store.set('loadingThreads', true)
|
store.set('loadingThreads', true)
|
||||||
store.set('permissions', {
|
store.set('permissions', {
|
||||||
|
|
|
@ -5,9 +5,12 @@ import Nav from './nav'
|
||||||
import Icon from '../../../../shared/components/icon'
|
import Icon from '../../../../shared/components/icon'
|
||||||
import OverviewFile from './overview-file'
|
import OverviewFile from './overview-file'
|
||||||
import { useReviewPanelValueContext } from '../../context/review-panel/review-panel-context'
|
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() {
|
function OverviewContainer() {
|
||||||
const { loading, docs } = useReviewPanelValueContext()
|
const { loading } = useReviewPanelValueContext()
|
||||||
|
const docs: MainDocument[] = useFileTreeData().docs
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
|
|
@ -13,8 +13,12 @@ import {
|
||||||
ThreadId,
|
ThreadId,
|
||||||
} from '../../../../../../../types/review-panel/review-panel'
|
} from '../../../../../../../types/review-panel/review-panel'
|
||||||
import { ReviewPanelResolvedCommentThread } from '../../../../../../../types/review-panel/comment-thread'
|
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 { ReviewPanelEntry } from '../../../../../../../types/review-panel/entry'
|
||||||
|
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
|
||||||
|
|
||||||
export interface FilteredResolvedComments
|
export interface FilteredResolvedComments
|
||||||
extends ReviewPanelResolvedCommentThread {
|
extends ReviewPanelResolvedCommentThread {
|
||||||
|
@ -29,8 +33,9 @@ function ResolvedCommentsDropdown() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const { docs, commentThreads, resolvedComments, permissions } =
|
const { commentThreads, resolvedComments, permissions } =
|
||||||
useReviewPanelValueContext()
|
useReviewPanelValueContext()
|
||||||
|
const docs: MainDocument[] = useFileTreeData().docs
|
||||||
|
|
||||||
const { refreshResolvedCommentsDropdown } = useReviewPanelUpdaterFnsContext()
|
const { refreshResolvedCommentsDropdown } = useReviewPanelUpdaterFnsContext()
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ function useAngularReviewPanelState(): ReviewPanelState {
|
||||||
'reviewPanel.commentThreads',
|
'reviewPanel.commentThreads',
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
const [docs] = useScopeValue<ReviewPanel.Value<'docs'>>('docs')
|
|
||||||
const [entries] = useScopeValue<ReviewPanel.Value<'entries'>>(
|
const [entries] = useScopeValue<ReviewPanel.Value<'entries'>>(
|
||||||
'reviewPanel.entries',
|
'reviewPanel.entries',
|
||||||
true
|
true
|
||||||
|
@ -143,7 +142,6 @@ function useAngularReviewPanelState(): ReviewPanelState {
|
||||||
() => ({
|
() => ({
|
||||||
collapsed,
|
collapsed,
|
||||||
commentThreads,
|
commentThreads,
|
||||||
docs,
|
|
||||||
entries,
|
entries,
|
||||||
entryHover,
|
entryHover,
|
||||||
isAddingComment,
|
isAddingComment,
|
||||||
|
@ -170,7 +168,6 @@ function useAngularReviewPanelState(): ReviewPanelState {
|
||||||
[
|
[
|
||||||
collapsed,
|
collapsed,
|
||||||
commentThreads,
|
commentThreads,
|
||||||
docs,
|
|
||||||
entries,
|
entries,
|
||||||
entryHover,
|
entryHover,
|
||||||
isAddingComment,
|
isAddingComment,
|
||||||
|
|
|
@ -7,10 +7,7 @@ import {
|
||||||
SubView,
|
SubView,
|
||||||
ThreadId,
|
ThreadId,
|
||||||
} from '../../../../../../../types/review-panel/review-panel'
|
} from '../../../../../../../types/review-panel/review-panel'
|
||||||
import {
|
import { DocId } from '../../../../../../../types/project-settings'
|
||||||
DocId,
|
|
||||||
MainDocument,
|
|
||||||
} from '../../../../../../../types/project-settings'
|
|
||||||
import { dispatchReviewPanelLayout } from '../../../extensions/changes/change-manager'
|
import { dispatchReviewPanelLayout } from '../../../extensions/changes/change-manager'
|
||||||
|
|
||||||
/* eslint-disable no-use-before-define */
|
/* eslint-disable no-use-before-define */
|
||||||
|
@ -18,7 +15,6 @@ export interface ReviewPanelState {
|
||||||
values: {
|
values: {
|
||||||
collapsed: Record<DocId, boolean>
|
collapsed: Record<DocId, boolean>
|
||||||
commentThreads: ReviewPanelCommentThreads
|
commentThreads: ReviewPanelCommentThreads
|
||||||
docs: MainDocument[] | undefined
|
|
||||||
entries: ReviewPanelEntries
|
entries: ReviewPanelEntries
|
||||||
entryHover: boolean
|
entryHover: boolean
|
||||||
isAddingComment: boolean
|
isAddingComment: boolean
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
} from '../../features/file-tree/util/mutate-in-tree'
|
} from '../../features/file-tree/util/mutate-in-tree'
|
||||||
import { countFiles } from '../../features/file-tree/util/count-in-tree'
|
import { countFiles } from '../../features/file-tree/util/count-in-tree'
|
||||||
import useDeepCompareEffect from '../../shared/hooks/use-deep-compare-effect'
|
import useDeepCompareEffect from '../../shared/hooks/use-deep-compare-effect'
|
||||||
|
import { docsInFolder } from '@/features/file-tree/util/docs-in-folder'
|
||||||
|
|
||||||
const FileTreeDataContext = createContext()
|
const FileTreeDataContext = createContext()
|
||||||
|
|
||||||
|
@ -147,6 +148,11 @@ export function FileTreeDataProvider({ children }) {
|
||||||
|
|
||||||
const [selectedEntities, setSelectedEntities] = useState([])
|
const [selectedEntities, setSelectedEntities] = useState([])
|
||||||
|
|
||||||
|
const docs = useMemo(
|
||||||
|
() => (fileTreeData ? docsInFolder(fileTreeData) : undefined),
|
||||||
|
[fileTreeData]
|
||||||
|
)
|
||||||
|
|
||||||
useDeepCompareEffect(() => {
|
useDeepCompareEffect(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTION_TYPES.RESET,
|
type: ACTION_TYPES.RESET,
|
||||||
|
@ -210,6 +216,7 @@ export function FileTreeDataProvider({ children }) {
|
||||||
hasFolders: fileTreeData?.folders.length > 0,
|
hasFolders: fileTreeData?.folders.length > 0,
|
||||||
selectedEntities,
|
selectedEntities,
|
||||||
setSelectedEntities,
|
setSelectedEntities,
|
||||||
|
docs,
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
dispatchCreateDoc,
|
dispatchCreateDoc,
|
||||||
|
@ -222,6 +229,7 @@ export function FileTreeDataProvider({ children }) {
|
||||||
fileTreeData,
|
fileTreeData,
|
||||||
selectedEntities,
|
selectedEntities,
|
||||||
setSelectedEntities,
|
setSelectedEntities,
|
||||||
|
docs,
|
||||||
])
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import EditorLeftMenu from '../../../../frontend/js/features/editor-left-menu/components/editor-left-menu'
|
import EditorLeftMenu from '../../../../frontend/js/features/editor-left-menu/components/editor-left-menu'
|
||||||
import {
|
import {
|
||||||
AllowedImageName,
|
AllowedImageName,
|
||||||
MainDocument,
|
|
||||||
OverallThemeMeta,
|
OverallThemeMeta,
|
||||||
SpellCheckLanguage,
|
SpellCheckLanguage,
|
||||||
} from '../../../../types/project-settings'
|
} from '../../../../types/project-settings'
|
||||||
import { EditorProviders } from '../../helpers/editor-providers'
|
import { EditorProviders } from '../../helpers/editor-providers'
|
||||||
import { mockScope } from './scope'
|
import { mockScope } from './scope'
|
||||||
|
import { Folder } from '../../../../types/folder'
|
||||||
|
import { docsInFolder } from '@/features/file-tree/util/docs-in-folder'
|
||||||
|
|
||||||
describe('<EditorLeftMenu />', function () {
|
describe('<EditorLeftMenu />', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
@ -390,40 +391,37 @@ describe('<EditorLeftMenu />', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows document menu correctly', function () {
|
it('shows document menu correctly', function () {
|
||||||
const docs: MainDocument[] = [
|
const rootFolder: Folder = {
|
||||||
{
|
_id: 'root-folder-id',
|
||||||
path: 'main.tex',
|
name: 'rootFolder',
|
||||||
doc: {
|
docs: [
|
||||||
|
{
|
||||||
|
_id: 'id1',
|
||||||
name: 'main.tex',
|
name: 'main.tex',
|
||||||
id: 'id1',
|
},
|
||||||
type: 'doc',
|
{
|
||||||
selected: false,
|
_id: 'id2',
|
||||||
} as MainDocument['doc'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'folder/main2.tex',
|
|
||||||
doc: {
|
|
||||||
name: 'main2.tex',
|
name: 'main2.tex',
|
||||||
id: 'id2',
|
},
|
||||||
type: 'doc',
|
],
|
||||||
selected: false,
|
fileRefs: [],
|
||||||
} as MainDocument['doc'],
|
folders: [],
|
||||||
},
|
}
|
||||||
]
|
|
||||||
|
|
||||||
const scope = mockScope({
|
const scope = mockScope({
|
||||||
ui: {
|
ui: {
|
||||||
leftMenuShown: true,
|
leftMenuShown: true,
|
||||||
},
|
},
|
||||||
docs,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.mount(
|
cy.mount(
|
||||||
<EditorProviders scope={scope}>
|
<EditorProviders scope={scope} rootFolder={[rootFolder as any]}>
|
||||||
<EditorLeftMenu />
|
<EditorLeftMenu />
|
||||||
</EditorProviders>
|
</EditorProviders>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const docs = docsInFolder(rootFolder)
|
||||||
|
|
||||||
cy.get<HTMLOptionElement>('#settings-menu-rootDocId option').then(
|
cy.get<HTMLOptionElement>('#settings-menu-rootDocId option').then(
|
||||||
options => {
|
options => {
|
||||||
const values = [...options].map(o => o.value)
|
const values = [...options].map(o => o.value)
|
||||||
|
|
|
@ -5,21 +5,23 @@ import fetchMock from 'fetch-mock'
|
||||||
import SettingsDocument from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-document'
|
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 * as isValidTeXFileModule from '../../../../../../frontend/js/main/is-valid-tex-file'
|
||||||
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
|
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
|
||||||
import type { MainDocument } from '../../../../../../types/project-settings'
|
import { Folder } from '../../../../../../types/folder'
|
||||||
|
|
||||||
describe('<SettingsDocument />', function () {
|
describe('<SettingsDocument />', function () {
|
||||||
let isValidTeXFileStub: sinon.SinonStub
|
let isValidTeXFileStub: sinon.SinonStub
|
||||||
const docs: MainDocument[] = [
|
|
||||||
{
|
const rootFolder: Folder = {
|
||||||
path: 'main.tex',
|
_id: 'root-folder-id',
|
||||||
doc: {
|
name: 'rootFolder',
|
||||||
|
docs: [
|
||||||
|
{
|
||||||
|
_id: '123abc',
|
||||||
name: 'main.tex',
|
name: 'main.tex',
|
||||||
id: '123abc',
|
},
|
||||||
type: 'doc',
|
],
|
||||||
selected: false,
|
fileRefs: [],
|
||||||
} as MainDocument['doc'],
|
folders: [],
|
||||||
},
|
}
|
||||||
]
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
isValidTeXFileStub = sinon
|
isValidTeXFileStub = sinon
|
||||||
|
@ -34,9 +36,7 @@ describe('<SettingsDocument />', function () {
|
||||||
|
|
||||||
it('shows correct menu', async function () {
|
it('shows correct menu', async function () {
|
||||||
renderWithEditorContext(<SettingsDocument />, {
|
renderWithEditorContext(<SettingsDocument />, {
|
||||||
scope: {
|
rootFolder: [rootFolder],
|
||||||
docs,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const select = screen.getByLabelText('Main document')
|
const select = screen.getByLabelText('Main document')
|
||||||
|
|
|
@ -22,6 +22,9 @@ describe('<ReviewPanel />', function () {
|
||||||
const scope = mockScope('')
|
const scope = mockScope('')
|
||||||
scope.editor.showVisual = true
|
scope.editor.showVisual = true
|
||||||
|
|
||||||
|
// The tests expect no documents, so remove them from the scope
|
||||||
|
scope.project.rootFolder = []
|
||||||
|
|
||||||
cy.wrap(scope).as('scope')
|
cy.wrap(scope).as('scope')
|
||||||
|
|
||||||
cy.mount(
|
cy.mount(
|
||||||
|
|
|
@ -11,8 +11,6 @@ export type MainDocument = {
|
||||||
doc: {
|
doc: {
|
||||||
name: string
|
name: string
|
||||||
id: DocId
|
id: DocId
|
||||||
type: string
|
|
||||||
selected: boolean
|
|
||||||
}
|
}
|
||||||
path: string
|
path: string
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue