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
This commit is contained in:
M Fahru 2022-12-28 13:58:14 -07:00 committed by Copybot
parent af01ace5a0
commit 677ec173ed
5 changed files with 101 additions and 33 deletions

View file

@ -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<Record<string, never>>) {
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(
() => ({

View file

@ -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<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(() => {
// 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,
])
}

View file

@ -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<ProjectSettingsScope | undefined>(
'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,

View file

@ -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<string>('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
}

View file

@ -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<string>('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