From 677ec173eddf7db9331fad14d11469a048b32466 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Wed, 28 Dec 2022 13:58:14 -0700 Subject: [PATCH] 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 --- .../context/project-settings-context.tsx | 10 ++-- ...-project-wide-settings-socket-listener.tsx | 51 +++++++++++++++---- .../hooks/use-set-project-wide-settings.tsx | 25 ++++++--- .../hooks/use-set-root-doc-id.tsx | 25 ++++++--- .../hooks/use-set-spell-check-language.tsx | 23 +++++++-- 5 files changed, 101 insertions(+), 33 deletions(-) diff --git a/services/web/frontend/js/features/editor-left-menu/context/project-settings-context.tsx b/services/web/frontend/js/features/editor-left-menu/context/project-settings-context.tsx index 5a2a5ce7ac..2562ecb48d 100644 --- a/services/web/frontend/js/features/editor-left-menu/context/project-settings-context.tsx +++ b/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 { FontFamily, @@ -52,6 +52,8 @@ export const ProjectSettingsContext = createContext< export function ProjectSettingsProvider({ children, }: PropsWithChildren>) { + const [ignoreUpdates, setIgnoreUpdates] = useState(false) + const { compiler, setCompiler, @@ -61,7 +63,7 @@ export function ProjectSettingsProvider({ setRootDocId, spellCheckLanguage, setSpellCheckLanguage, - } = useSetProjectWideSettings() + } = useSetProjectWideSettings({ ignoreUpdates }) const { autoComplete, @@ -86,7 +88,9 @@ export function ProjectSettingsProvider({ setPdfViewer, } = useUserWideSettings() - useProjectWideSettingsSocketListener() + useProjectWideSettingsSocketListener({ + onListen: () => setIgnoreUpdates(true), + }) const value: ProjectSettingsContextValue = useMemo( () => ({ diff --git a/services/web/frontend/js/features/editor-left-menu/hooks/use-project-wide-settings-socket-listener.tsx b/services/web/frontend/js/features/editor-left-menu/hooks/use-project-wide-settings-socket-listener.tsx index 8a2eb0bc0c..746a6fa6dc 100644 --- a/services/web/frontend/js/features/editor-left-menu/hooks/use-project-wide-settings-socket-listener.tsx +++ b/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 { useIdeContext } from '../../../shared/context/ide-context' 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 [compiler, setCompiler] = + const [compilerScope, setCompilerScope] = useScopeValue('project.compiler') - const [imageName, setImageName] = useScopeValue('project.imageName') - const [spellCheckLanguage, setSpellCheckLanguage] = useScopeValue( - 'project.spellCheckLanguage' + const [imageNameScope, setImageNameScope] = + useScopeValue('project.imageName') + const [spellCheckLanguageScope, setSpellCheckLanguageScope] = + useScopeValue('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(() => { // data is not available on initial mounting - const dataAvailable = compiler && imageName && spellCheckLanguage + const dataAvailable = + compilerScope && imageNameScope && spellCheckLanguageScope if (dataAvailable && ide?.socket) { ide.socket.on('compilerUpdated', setCompiler) @@ -32,11 +63,11 @@ export default function useProjectWideSettingsSocketListener() { } }, [ ide?.socket, - compiler, + compilerScope, setCompiler, - imageName, + imageNameScope, setImageName, - spellCheckLanguage, + spellCheckLanguageScope, setSpellCheckLanguage, ]) } diff --git a/services/web/frontend/js/features/editor-left-menu/hooks/use-set-project-wide-settings.tsx b/services/web/frontend/js/features/editor-left-menu/hooks/use-set-project-wide-settings.tsx index cd4800f5c2..21ded370a3 100644 --- a/services/web/frontend/js/features/editor-left-menu/hooks/use-set-project-wide-settings.tsx +++ b/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 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 const [project, setProject] = useScopeValue( 'project', @@ -17,26 +22,30 @@ export default function useSetProjectWideSettings() { const setCompiler = useCallback( (compiler: ProjectCompiler) => { - if (project?.compiler) { + const allowUpdate = !ignoreUpdates && project?.compiler + + if (allowUpdate) { setProject({ ...project, compiler }) saveProjectSettings({ projectId, compiler }) } }, - [projectId, project, setProject] + [projectId, project, setProject, ignoreUpdates] ) const setImageName = useCallback( (imageName: string) => { - if (project?.imageName) { + const allowUpdate = !ignoreUpdates && project?.imageName + + if (allowUpdate) { setProject({ ...project, 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 { compiler: project?.compiler, diff --git a/services/web/frontend/js/features/editor-left-menu/hooks/use-set-root-doc-id.tsx b/services/web/frontend/js/features/editor-left-menu/hooks/use-set-root-doc-id.tsx index 535ea77e41..7268d2fb5b 100644 --- a/services/web/frontend/js/features/editor-left-menu/hooks/use-set-root-doc-id.tsx +++ b/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 { saveProjectSettings } from '../utils/api' -export default function useSetRootDocId() { +type UseSetRootDocId = { + ignoreUpdates: boolean +} + +export default function useSetRootDocId({ ignoreUpdates }: UseSetRootDocId) { const [rootDocIdScope, setRootDocIdScope] = useScopeValue('project.rootDoc_id') const { permissionsLevel } = useEditorContext() @@ -12,12 +16,13 @@ export default function useSetRootDocId() { const setRootDocId = useCallback( 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 { await saveProjectSettings({ projectId, rootDoc_id: rootDocId }) setRootDocIdScope(rootDocId) @@ -26,7 +31,13 @@ export default function useSetRootDocId() { } } }, - [permissionsLevel, projectId, rootDocIdScope, setRootDocIdScope] + [ + permissionsLevel, + projectId, + rootDocIdScope, + setRootDocIdScope, + ignoreUpdates, + ] ) return setRootDocId } diff --git a/services/web/frontend/js/features/editor-left-menu/hooks/use-set-spell-check-language.tsx b/services/web/frontend/js/features/editor-left-menu/hooks/use-set-spell-check-language.tsx index 413c2ead6f..61fa12820f 100644 --- a/services/web/frontend/js/features/editor-left-menu/hooks/use-set-spell-check-language.tsx +++ b/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 { saveProjectSettings, saveUserSettings } from '../utils/api' -export default function useSetSpellCheckLanguage() { +type UseSetSpellCheckLanguage = { + ignoreUpdates: boolean +} + +export default function useSetSpellCheckLanguage({ + ignoreUpdates, +}: UseSetSpellCheckLanguage) { const [spellCheckLanguageScope, setSpellCheckLanguageScope] = useScopeValue('project.spellCheckLanguage') const { _id: projectId } = useProjectContext() const setSpellCheckLanguage = useCallback( (spellCheckLanguage: string) => { - if ( - spellCheckLanguageScope && + const allowUpdate = + !ignoreUpdates && + spellCheckLanguage && spellCheckLanguage !== spellCheckLanguageScope - ) { + + if (allowUpdate) { sendMB('setting-changed', { changedSetting: 'spellCheckLanguage', changedSettingVal: spellCheckLanguage, @@ -27,7 +35,12 @@ export default function useSetSpellCheckLanguage() { saveUserSettings({ spellCheckLanguage }) } }, - [projectId, setSpellCheckLanguageScope, spellCheckLanguageScope] + [ + projectId, + setSpellCheckLanguageScope, + spellCheckLanguageScope, + ignoreUpdates, + ] ) return setSpellCheckLanguage