Просмотр исходного кода

Disable updating project-wide settings while socket is currently listening to update project-wide settings in a project.

This may happen if the project is being used by multiple people, and we want to avoid race condition on the update since it's possible for multiple people to update setting value at the same time.

GitOrigin-RevId: cdad6a6456e2d9e4ef1812ebfd6f6ef59f23747f
M Fahru 3 лет назад
Родитель
Сommit
677ec173ed

+ 7 - 3
services/web/frontend/js/features/editor-left-menu/context/project-settings-context.tsx

@@ -1,4 +1,4 @@
-import { createContext, useContext, useMemo } from 'react'
+import { createContext, useContext, useMemo, useState } from 'react'
 import type { PropsWithChildren } from 'react'
 import type { PropsWithChildren } from 'react'
 import type {
 import type {
   FontFamily,
   FontFamily,
@@ -52,6 +52,8 @@ export const ProjectSettingsContext = createContext<
 export function ProjectSettingsProvider({
 export function ProjectSettingsProvider({
   children,
   children,
 }: PropsWithChildren<Record<string, never>>) {
 }: PropsWithChildren<Record<string, never>>) {
+  const [ignoreUpdates, setIgnoreUpdates] = useState(false)
+
   const {
   const {
     compiler,
     compiler,
     setCompiler,
     setCompiler,
@@ -61,7 +63,7 @@ export function ProjectSettingsProvider({
     setRootDocId,
     setRootDocId,
     spellCheckLanguage,
     spellCheckLanguage,
     setSpellCheckLanguage,
     setSpellCheckLanguage,
-  } = useSetProjectWideSettings()
+  } = useSetProjectWideSettings({ ignoreUpdates })
 
 
   const {
   const {
     autoComplete,
     autoComplete,
@@ -86,7 +88,9 @@ export function ProjectSettingsProvider({
     setPdfViewer,
     setPdfViewer,
   } = useUserWideSettings()
   } = useUserWideSettings()
 
 
-  useProjectWideSettingsSocketListener()
+  useProjectWideSettingsSocketListener({
+    onListen: () => setIgnoreUpdates(true),
+  })
 
 
   const value: ProjectSettingsContextValue = useMemo(
   const value: ProjectSettingsContextValue = useMemo(
     () => ({
     () => ({

+ 41 - 10
services/web/frontend/js/features/editor-left-menu/hooks/use-project-wide-settings-socket-listener.tsx

@@ -1,21 +1,52 @@
-import { useEffect } from 'react'
+import { useCallback, useEffect } from 'react'
 import { ProjectCompiler } from '../../../../../types/project-settings'
 import { ProjectCompiler } from '../../../../../types/project-settings'
 import { useIdeContext } from '../../../shared/context/ide-context'
 import { useIdeContext } from '../../../shared/context/ide-context'
 import useScopeValue from '../../../shared/hooks/use-scope-value'
 import useScopeValue from '../../../shared/hooks/use-scope-value'
 
 
-export default function useProjectWideSettingsSocketListener() {
+type UseProjectWideSettingsSocketListener = {
+  onListen: () => void
+}
+
+export default function useProjectWideSettingsSocketListener({
+  onListen,
+}: UseProjectWideSettingsSocketListener) {
   const ide = useIdeContext()
   const ide = useIdeContext()
 
 
-  const [compiler, setCompiler] =
+  const [compilerScope, setCompilerScope] =
     useScopeValue<ProjectCompiler>('project.compiler')
     useScopeValue<ProjectCompiler>('project.compiler')
-  const [imageName, setImageName] = useScopeValue<string>('project.imageName')
-  const [spellCheckLanguage, setSpellCheckLanguage] = useScopeValue<string>(
-    'project.spellCheckLanguage'
+  const [imageNameScope, setImageNameScope] =
+    useScopeValue<string>('project.imageName')
+  const [spellCheckLanguageScope, setSpellCheckLanguageScope] =
+    useScopeValue<string>('project.spellCheckLanguage')
+
+  const setCompiler = useCallback(
+    (compiler: ProjectCompiler) => {
+      onListen()
+      setCompilerScope(compiler)
+    },
+    [setCompilerScope, onListen]
+  )
+
+  const setImageName = useCallback(
+    (imageName: string) => {
+      onListen()
+      setImageNameScope(imageName)
+    },
+    [setImageNameScope, onListen]
+  )
+
+  const setSpellCheckLanguage = useCallback(
+    (spellCheckLanguage: string) => {
+      onListen()
+      setSpellCheckLanguageScope(spellCheckLanguage)
+    },
+    [setSpellCheckLanguageScope, onListen]
   )
   )
 
 
   useEffect(() => {
   useEffect(() => {
     // data is not available on initial mounting
     // data is not available on initial mounting
-    const dataAvailable = compiler && imageName && spellCheckLanguage
+    const dataAvailable =
+      compilerScope && imageNameScope && spellCheckLanguageScope
 
 
     if (dataAvailable && ide?.socket) {
     if (dataAvailable && ide?.socket) {
       ide.socket.on('compilerUpdated', setCompiler)
       ide.socket.on('compilerUpdated', setCompiler)
@@ -32,11 +63,11 @@ export default function useProjectWideSettingsSocketListener() {
     }
     }
   }, [
   }, [
     ide?.socket,
     ide?.socket,
-    compiler,
+    compilerScope,
     setCompiler,
     setCompiler,
-    imageName,
+    imageNameScope,
     setImageName,
     setImageName,
-    spellCheckLanguage,
+    spellCheckLanguageScope,
     setSpellCheckLanguage,
     setSpellCheckLanguage,
   ])
   ])
 }
 }

+ 17 - 8
services/web/frontend/js/features/editor-left-menu/hooks/use-set-project-wide-settings.tsx

@@ -6,8 +6,13 @@ import { ProjectSettingsScope, saveProjectSettings } from '../utils/api'
 import useSetRootDocId from './use-set-root-doc-id'
 import useSetRootDocId from './use-set-root-doc-id'
 import useSetSpellCheckLanguage from './use-set-spell-check-language'
 import useSetSpellCheckLanguage from './use-set-spell-check-language'
 
 
-// TODO: handle ignoreUpdates
-export default function useSetProjectWideSettings() {
+type UseSetProjectWideSettings = {
+  ignoreUpdates: boolean
+}
+
+export default function useSetProjectWideSettings({
+  ignoreUpdates,
+}: UseSetProjectWideSettings) {
   // The value will be undefined on mount
   // The value will be undefined on mount
   const [project, setProject] = useScopeValue<ProjectSettingsScope | undefined>(
   const [project, setProject] = useScopeValue<ProjectSettingsScope | undefined>(
     'project',
     'project',
@@ -17,26 +22,30 @@ export default function useSetProjectWideSettings() {
 
 
   const setCompiler = useCallback(
   const setCompiler = useCallback(
     (compiler: ProjectCompiler) => {
     (compiler: ProjectCompiler) => {
-      if (project?.compiler) {
+      const allowUpdate = !ignoreUpdates && project?.compiler
+
+      if (allowUpdate) {
         setProject({ ...project, compiler })
         setProject({ ...project, compiler })
         saveProjectSettings({ projectId, compiler })
         saveProjectSettings({ projectId, compiler })
       }
       }
     },
     },
-    [projectId, project, setProject]
+    [projectId, project, setProject, ignoreUpdates]
   )
   )
 
 
   const setImageName = useCallback(
   const setImageName = useCallback(
     (imageName: string) => {
     (imageName: string) => {
-      if (project?.imageName) {
+      const allowUpdate = !ignoreUpdates && project?.imageName
+
+      if (allowUpdate) {
         setProject({ ...project, imageName })
         setProject({ ...project, imageName })
         saveProjectSettings({ projectId, imageName })
         saveProjectSettings({ projectId, imageName })
       }
       }
     },
     },
-    [projectId, project, setProject]
+    [projectId, project, setProject, ignoreUpdates]
   )
   )
 
 
-  const setRootDocId = useSetRootDocId()
-  const setSpellCheckLanguage = useSetSpellCheckLanguage()
+  const setRootDocId = useSetRootDocId({ ignoreUpdates })
+  const setSpellCheckLanguage = useSetSpellCheckLanguage({ ignoreUpdates })
 
 
   return {
   return {
     compiler: project?.compiler,
     compiler: project?.compiler,

+ 18 - 7
services/web/frontend/js/features/editor-left-menu/hooks/use-set-root-doc-id.tsx

@@ -4,7 +4,11 @@ import { useProjectContext } from '../../../shared/context/project-context'
 import useScopeValue from '../../../shared/hooks/use-scope-value'
 import useScopeValue from '../../../shared/hooks/use-scope-value'
 import { saveProjectSettings } from '../utils/api'
 import { saveProjectSettings } from '../utils/api'
 
 
-export default function useSetRootDocId() {
+type UseSetRootDocId = {
+  ignoreUpdates: boolean
+}
+
+export default function useSetRootDocId({ ignoreUpdates }: UseSetRootDocId) {
   const [rootDocIdScope, setRootDocIdScope] =
   const [rootDocIdScope, setRootDocIdScope] =
     useScopeValue<string>('project.rootDoc_id')
     useScopeValue<string>('project.rootDoc_id')
   const { permissionsLevel } = useEditorContext()
   const { permissionsLevel } = useEditorContext()
@@ -12,12 +16,13 @@ export default function useSetRootDocId() {
 
 
   const setRootDocId = useCallback(
   const setRootDocId = useCallback(
     async (rootDocId: string) => {
     async (rootDocId: string) => {
-      const disallowChange =
-        typeof rootDocIdScope === 'undefined' ||
-        permissionsLevel === 'readOnly' ||
-        rootDocIdScope === rootDocId
+      const allowUpdate =
+        !ignoreUpdates &&
+        typeof rootDocIdScope !== 'undefined' &&
+        permissionsLevel !== 'readOnly' &&
+        rootDocIdScope !== rootDocId
 
 
-      if (!disallowChange) {
+      if (allowUpdate) {
         try {
         try {
           await saveProjectSettings({ projectId, rootDoc_id: rootDocId })
           await saveProjectSettings({ projectId, rootDoc_id: rootDocId })
           setRootDocIdScope(rootDocId)
           setRootDocIdScope(rootDocId)
@@ -26,7 +31,13 @@ export default function useSetRootDocId() {
         }
         }
       }
       }
     },
     },
-    [permissionsLevel, projectId, rootDocIdScope, setRootDocIdScope]
+    [
+      permissionsLevel,
+      projectId,
+      rootDocIdScope,
+      setRootDocIdScope,
+      ignoreUpdates,
+    ]
   )
   )
   return setRootDocId
   return setRootDocId
 }
 }

+ 18 - 5
services/web/frontend/js/features/editor-left-menu/hooks/use-set-spell-check-language.tsx

@@ -4,17 +4,25 @@ import { useProjectContext } from '../../../shared/context/project-context'
 import useScopeValue from '../../../shared/hooks/use-scope-value'
 import useScopeValue from '../../../shared/hooks/use-scope-value'
 import { saveProjectSettings, saveUserSettings } from '../utils/api'
 import { saveProjectSettings, saveUserSettings } from '../utils/api'
 
 
-export default function useSetSpellCheckLanguage() {
+type UseSetSpellCheckLanguage = {
+  ignoreUpdates: boolean
+}
+
+export default function useSetSpellCheckLanguage({
+  ignoreUpdates,
+}: UseSetSpellCheckLanguage) {
   const [spellCheckLanguageScope, setSpellCheckLanguageScope] =
   const [spellCheckLanguageScope, setSpellCheckLanguageScope] =
     useScopeValue<string>('project.spellCheckLanguage')
     useScopeValue<string>('project.spellCheckLanguage')
   const { _id: projectId } = useProjectContext()
   const { _id: projectId } = useProjectContext()
 
 
   const setSpellCheckLanguage = useCallback(
   const setSpellCheckLanguage = useCallback(
     (spellCheckLanguage: string) => {
     (spellCheckLanguage: string) => {
-      if (
-        spellCheckLanguageScope &&
+      const allowUpdate =
+        !ignoreUpdates &&
+        spellCheckLanguage &&
         spellCheckLanguage !== spellCheckLanguageScope
         spellCheckLanguage !== spellCheckLanguageScope
-      ) {
+
+      if (allowUpdate) {
         sendMB('setting-changed', {
         sendMB('setting-changed', {
           changedSetting: 'spellCheckLanguage',
           changedSetting: 'spellCheckLanguage',
           changedSettingVal: spellCheckLanguage,
           changedSettingVal: spellCheckLanguage,
@@ -27,7 +35,12 @@ export default function useSetSpellCheckLanguage() {
         saveUserSettings({ spellCheckLanguage })
         saveUserSettings({ spellCheckLanguage })
       }
       }
     },
     },
-    [projectId, setSpellCheckLanguageScope, spellCheckLanguageScope]
+    [
+      projectId,
+      setSpellCheckLanguageScope,
+      spellCheckLanguageScope,
+      ignoreUpdates,
+    ]
   )
   )
 
 
   return setSpellCheckLanguage
   return setSpellCheckLanguage