Move project context out of scope value store (#26615)
* Refactor project context to not use scope store * Fix Cypress tests for project context changes * Fix frontend React Testing Library tests for project context changes * Remove redundant code * Fix some project types in tests * Remove unused import and fix a type * Throw an error if updating the project in the project context before joining the project * Fix some review panel tests * Remove unused imports GitOrigin-RevId: 2f0c928b651f387aa980c29aef7d1ba0649790a7
This commit is contained in:
@@ -9,9 +9,7 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import useScopeValue from '../hooks/use-scope-value'
|
||||
import useBrowserWindow from '../hooks/use-browser-window'
|
||||
import { useIdeContext } from './ide-context'
|
||||
import { useProjectContext } from './project-context'
|
||||
import { useDetachContext } from './detach-context'
|
||||
import getMeta from '../../utils/meta'
|
||||
@@ -46,12 +44,18 @@ export const EditorContext = createContext<
|
||||
>(undefined)
|
||||
|
||||
export const EditorProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
||||
const { socket } = useIdeContext()
|
||||
const { id: userId, featureUsage } = useUserContext()
|
||||
const { role } = useDetachContext()
|
||||
const { showGenericMessageModal } = useModalsContext()
|
||||
|
||||
const { owner, features, _id: projectId, members } = useProjectContext()
|
||||
const {
|
||||
features,
|
||||
projectId,
|
||||
project,
|
||||
name: projectName,
|
||||
updateProject,
|
||||
} = useProjectContext()
|
||||
const { owner, members } = project || {}
|
||||
|
||||
const cobranding = useMemo(() => {
|
||||
const brandVariation = getMeta('ol-brandVariation')
|
||||
@@ -71,8 +75,6 @@ export const EditorProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
||||
)
|
||||
}, [])
|
||||
|
||||
const [projectName, setProjectName] = useScopeValue('project.name')
|
||||
|
||||
const [inactiveTutorials, setInactiveTutorials] = useState(
|
||||
() => getMeta('ol-inactiveTutorials') || []
|
||||
)
|
||||
@@ -95,10 +97,12 @@ export const EditorProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
||||
|
||||
const isPendingEditor = useMemo(
|
||||
() =>
|
||||
members?.some(
|
||||
member =>
|
||||
member._id === userId &&
|
||||
(member.pendingEditor || member.pendingReviewer)
|
||||
Boolean(
|
||||
members?.some(
|
||||
member =>
|
||||
member._id === userId &&
|
||||
(member.pendingEditor || member.pendingReviewer)
|
||||
)
|
||||
),
|
||||
[members, userId]
|
||||
)
|
||||
@@ -110,33 +114,25 @@ export const EditorProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
||||
[inactiveTutorials]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (socket) {
|
||||
socket.on('projectNameUpdated', setProjectName)
|
||||
return () => socket.removeListener('projectNameUpdated', setProjectName)
|
||||
}
|
||||
}, [socket, setProjectName])
|
||||
|
||||
const renameProject = useCallback(
|
||||
(newName: string) => {
|
||||
setProjectName((oldName: string) => {
|
||||
if (oldName !== newName) {
|
||||
saveProjectSettings(projectId, { name: newName }).catch(
|
||||
(response: any) => {
|
||||
setProjectName(oldName)
|
||||
const { data, status } = response
|
||||
const oldName = projectName
|
||||
if (newName !== oldName) {
|
||||
updateProject({ name: newName })
|
||||
saveProjectSettings(projectId, { name: newName }).catch(
|
||||
(response: any) => {
|
||||
updateProject({ name: oldName })
|
||||
const { data, status } = response
|
||||
|
||||
showGenericMessageModal(
|
||||
'Error renaming project',
|
||||
status === 400 ? data : 'Please try again in a moment'
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
return newName
|
||||
})
|
||||
showGenericMessageModal(
|
||||
'Error renaming project',
|
||||
status === 400 ? data : 'Please try again in a moment'
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
[setProjectName, projectId, showGenericMessageModal]
|
||||
[projectName, updateProject, projectId, showGenericMessageModal]
|
||||
)
|
||||
|
||||
const { setTitle } = useBrowserWindow()
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
FC,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import useScopeValue from '../hooks/use-scope-value'
|
||||
import {
|
||||
renameInTree,
|
||||
deleteInTree,
|
||||
@@ -20,7 +19,6 @@ import useDeepCompareEffect from '../../shared/hooks/use-deep-compare-effect'
|
||||
import { docsInFolder } from '@/features/file-tree/util/docs-in-folder'
|
||||
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
|
||||
import { Folder } from '../../../../types/folder'
|
||||
import { Project } from '../../../../types/project'
|
||||
import { MainDocument } from '../../../../types/project-settings'
|
||||
import { FindResult } from '@/features/file-tree/util/path'
|
||||
import {
|
||||
@@ -28,6 +26,7 @@ import {
|
||||
useSnapshotContext,
|
||||
} from '@/features/ide-react/context/snapshot-context'
|
||||
import importOverleafModules from '../../../macros/import-overleaf-module.macro'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
|
||||
|
||||
const { buildFileTree, createFolder } =
|
||||
@@ -182,7 +181,7 @@ export function useFileTreeData() {
|
||||
export const FileTreeDataProvider: FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [project] = useScopeValue<Project>('project')
|
||||
const { project } = useProjectContext()
|
||||
const { currentDocumentId, setOpenDocName } = useEditorOpenDocContext()
|
||||
const { permissionsLevel } = useIdeReactContext()
|
||||
const { fileTreeFromHistory, snapshot, snapshotVersion } =
|
||||
@@ -195,7 +194,7 @@ export const FileTreeDataProvider: FC<React.PropsWithChildren> = ({
|
||||
useEffect(() => {
|
||||
if (fileTreeFromHistory) return
|
||||
setRootFolder(project?.rootFolder)
|
||||
}, [project, fileTreeFromHistory])
|
||||
}, [project?.rootFolder, fileTreeFromHistory])
|
||||
|
||||
useEffect(() => {
|
||||
if (!fileTreeFromHistory) return
|
||||
|
||||
@@ -131,13 +131,8 @@ export const LocalCompileProvider: FC<React.PropsWithChildren> = ({
|
||||
const { currentDocument } = useEditorOpenDocContext()
|
||||
const { role } = useDetachContext()
|
||||
|
||||
const {
|
||||
_id: projectId,
|
||||
rootDocId,
|
||||
joinedOnce,
|
||||
imageName,
|
||||
compiler: compilerName,
|
||||
} = useProjectContext()
|
||||
const { projectId, joinedOnce, project } = useProjectContext()
|
||||
const { rootDocId, imageName, compiler: compilerName } = project || {}
|
||||
|
||||
const { pdfPreviewOpen } = useLayoutContext()
|
||||
|
||||
|
||||
@@ -1,10 +1,31 @@
|
||||
import { FC, createContext, useContext, useMemo, useState } from 'react'
|
||||
import useScopeValue from '../hooks/use-scope-value'
|
||||
import {
|
||||
FC,
|
||||
createContext,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { ProjectContextValue } from './types/project-context'
|
||||
import { ProjectUpdate, ProjectMetadata } from './types/project-metadata'
|
||||
import { ProjectSnapshot } from '@/infrastructure/project-snapshot'
|
||||
import { Tag } from '../../../../app/src/Features/Tags/types'
|
||||
|
||||
const ProjectContext = createContext<ProjectContextValue | undefined>(undefined)
|
||||
type ProjectContextValue = {
|
||||
projectId: ProjectMetadata['_id']
|
||||
project: ProjectMetadata | null
|
||||
joinProject: (project: ProjectMetadata) => void
|
||||
updateProject: (projectUpdate: ProjectUpdate) => void
|
||||
joinedOnce: boolean
|
||||
projectSnapshot: ProjectSnapshot
|
||||
tags: Tag[]
|
||||
features: ProjectMetadata['features']
|
||||
name: ProjectMetadata['name']
|
||||
}
|
||||
|
||||
export const ProjectContext = createContext<ProjectContextValue | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
export function useProjectContext() {
|
||||
const context = useContext(ProjectContext)
|
||||
@@ -18,35 +39,33 @@ export function useProjectContext() {
|
||||
return context
|
||||
}
|
||||
|
||||
// when the provider is created the project is still not added to the Angular
|
||||
// scope. A few props are populated to prevent errors in existing React
|
||||
// components
|
||||
const projectFallback = {
|
||||
_id: getMeta('ol-project_id'),
|
||||
name: '',
|
||||
features: {},
|
||||
}
|
||||
|
||||
export const ProjectProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
||||
const [project] = useScopeValue('project')
|
||||
const joinedOnce = !!project
|
||||
const [joinedOnce, setJoinedOnce] = useState(false)
|
||||
const [project, setProject] = useState<ProjectMetadata | null>(null)
|
||||
|
||||
const {
|
||||
_id,
|
||||
compiler,
|
||||
imageName,
|
||||
name,
|
||||
rootDocId,
|
||||
members,
|
||||
invites,
|
||||
features,
|
||||
publicAccesLevel: publicAccessLevel,
|
||||
owner,
|
||||
trackChangesState,
|
||||
mainBibliographyDoc_id: mainBibliographyDocId,
|
||||
} = project || projectFallback
|
||||
// Expose some project properties with fallbacks for convenience
|
||||
const projectId = project ? project._id : getMeta('ol-project_id')
|
||||
const name = project ? project.name : ''
|
||||
const features = project ? project.features : {}
|
||||
|
||||
const [projectSnapshot] = useState(() => new ProjectSnapshot(_id))
|
||||
const joinProject = useCallback((projectData: ProjectMetadata) => {
|
||||
setProject(projectData)
|
||||
setJoinedOnce(true)
|
||||
}, [])
|
||||
|
||||
const updateProject = useCallback((projectUpdateData: ProjectUpdate) => {
|
||||
setProject(projectData => {
|
||||
// Only perform the update if `project` is already set, otherwise we could
|
||||
// end up with an incomplete project object
|
||||
if (!projectData) {
|
||||
throw new Error('Project not initialized. Use joinProject first.')
|
||||
}
|
||||
|
||||
return Object.assign({}, projectData, projectUpdateData)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const [projectSnapshot] = useState(() => new ProjectSnapshot(projectId))
|
||||
|
||||
const tags = useMemo(
|
||||
() =>
|
||||
@@ -56,41 +75,17 @@ export const ProjectProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
||||
[]
|
||||
)
|
||||
|
||||
const value = useMemo(() => {
|
||||
return {
|
||||
_id,
|
||||
compiler,
|
||||
imageName,
|
||||
name,
|
||||
rootDocId,
|
||||
members,
|
||||
invites,
|
||||
features,
|
||||
publicAccessLevel,
|
||||
owner,
|
||||
tags,
|
||||
trackChangesState,
|
||||
mainBibliographyDocId,
|
||||
projectSnapshot,
|
||||
joinedOnce,
|
||||
}
|
||||
}, [
|
||||
_id,
|
||||
compiler,
|
||||
imageName,
|
||||
name,
|
||||
rootDocId,
|
||||
members,
|
||||
invites,
|
||||
features,
|
||||
publicAccessLevel,
|
||||
owner,
|
||||
tags,
|
||||
trackChangesState,
|
||||
mainBibliographyDocId,
|
||||
projectSnapshot,
|
||||
const value = {
|
||||
projectId,
|
||||
project,
|
||||
joinProject,
|
||||
updateProject,
|
||||
joinedOnce,
|
||||
])
|
||||
projectSnapshot,
|
||||
tags,
|
||||
features,
|
||||
name,
|
||||
}
|
||||
|
||||
return (
|
||||
<ProjectContext.Provider value={value}>{children}</ProjectContext.Provider>
|
||||
|
||||
+8
-14
@@ -1,9 +1,9 @@
|
||||
import { UserId } from '../../../../../types/user'
|
||||
import { PublicAccessLevel } from '../../../../../types/public-access-level'
|
||||
import { ProjectSnapshot } from '@/infrastructure/project-snapshot'
|
||||
import { Tag } from '../../../../../app/src/Features/Tags/types'
|
||||
import { ProjectSettings } from '@/features/editor-left-menu/utils/api'
|
||||
import { Folder } from '../../../../../types/folder'
|
||||
|
||||
export type ProjectContextMember = {
|
||||
export type ProjectMember = {
|
||||
_id: UserId
|
||||
privileges: 'readOnly' | 'readAndWrite' | 'review'
|
||||
email: string
|
||||
@@ -13,15 +13,11 @@ export type ProjectContextMember = {
|
||||
pendingReviewer?: boolean
|
||||
}
|
||||
|
||||
export type ProjectContextValue = {
|
||||
export interface ProjectMetadata extends ProjectSettings {
|
||||
_id: string
|
||||
name: string
|
||||
rootDocId?: string
|
||||
mainBibliographyDocId?: string
|
||||
compiler: string
|
||||
imageName: string
|
||||
members: ProjectContextMember[]
|
||||
invites: ProjectContextMember[]
|
||||
members: ProjectMember[]
|
||||
invites: ProjectMember[]
|
||||
features: {
|
||||
collaborators?: number
|
||||
compileGroup?: 'alpha' | 'standard' | 'priority'
|
||||
@@ -44,10 +40,8 @@ export type ProjectContextValue = {
|
||||
privileges: string
|
||||
signUpDate: string
|
||||
}
|
||||
tags: Tag[]
|
||||
rootFolder?: Folder[]
|
||||
trackChangesState: boolean | Record<UserId | '__guests__', boolean>
|
||||
projectSnapshot: ProjectSnapshot
|
||||
joinedOnce: boolean
|
||||
}
|
||||
|
||||
export type ProjectContextUpdateValue = Partial<ProjectContextValue>
|
||||
export type ProjectUpdate = Partial<ProjectMetadata>
|
||||
@@ -10,7 +10,7 @@ type UseStopOnFirstErrorProps = {
|
||||
export function useStopOnFirstError(opts: UseStopOnFirstErrorProps = {}) {
|
||||
const { eventSource } = opts
|
||||
const { stopOnFirstError, setStopOnFirstError } = useCompileContext()
|
||||
const { _id: projectId } = useProjectContext()
|
||||
const { projectId } = useProjectContext()
|
||||
|
||||
type Opts = {
|
||||
projectId: string
|
||||
|
||||
Reference in New Issue
Block a user