diff --git a/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx b/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx index 74da8de40f..2ece493fe7 100644 --- a/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx @@ -49,6 +49,12 @@ function populateIdeReactScope(store: ReactScopeValueStore) { function populateProjectScope(store: ReactScopeValueStore) { store.allowNonExistentPath('project', true) store.set('permissionsLevel', 'readOnly') + store.set('permissions', { + read: true, + write: false, + admin: false, + comment: true, + }) } function populatePdfScope(store: ReactScopeValueStore) { diff --git a/services/web/frontend/js/features/ide-react/context/metadata-context.tsx b/services/web/frontend/js/features/ide-react/context/metadata-context.tsx index 66d07bcc5c..3ee21ffe59 100644 --- a/services/web/frontend/js/features/ide-react/context/metadata-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/metadata-context.tsx @@ -54,7 +54,7 @@ export const MetadataProvider: FC = ({ children }) => { const { socket } = useConnectionContext() const { onlineUsersCount } = useOnlineUsersContext() const { permissionsLevel } = useEditorContext() - const { permissions } = usePermissionsContext() + const permissions = usePermissionsContext() const { currentDocument } = useEditorManagerContext() const { showGenericMessageModal } = useModalsContext() diff --git a/services/web/frontend/js/features/ide-react/context/permissions-context.tsx b/services/web/frontend/js/features/ide-react/context/permissions-context.tsx index 5355cca7b7..65eaf65953 100644 --- a/services/web/frontend/js/features/ide-react/context/permissions-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/permissions-context.tsx @@ -1,110 +1,73 @@ -import { createContext, useContext, useState, useEffect, useMemo } from 'react' +import { createContext, useContext, useEffect } from 'react' import { useConnectionContext } from '@/features/ide-react/context/connection-context' import { useEditorContext } from '@/shared/context/editor-context' import getMeta from '@/utils/meta' -import { Permissions } from '@/features/ide-react/types/permissions' +import { + Permissions, + PermissionsLevel, +} from '@/features/ide-react/types/permissions' +import useScopeValue from '@/shared/hooks/use-scope-value' +import { DeepReadonly } from '../../../../../types/utils' -type PermissionsContextValue = { - permissions: Permissions -} +const PermissionsContext = createContext(undefined) -const PermissionsContext = createContext( - undefined -) - -const readOnlyPermissions: Readonly = { - read: true, - write: false, - admin: false, - comment: true, -} -const readAndWritePermissions: Readonly = { - read: true, - write: true, - admin: false, - comment: true, -} -const ownerPermissions: Readonly = { - read: true, - write: true, - admin: true, - comment: true, -} -const permissionsMap = { - readOnly: readOnlyPermissions, - readAndWrite: readAndWritePermissions, - owner: ownerPermissions, - anonymous: { - readOnly: { ...readOnlyPermissions, comment: false }, - readAndWrite: { ...readAndWritePermissions, comment: false }, - owner: { ...ownerPermissions, comment: false }, - }, -} as const - -export const PermissionsProvider: React.FC = ({ children }) => { - const [permissions, setPermissions] = useState({ - read: false, +const permissionsMap: DeepReadonly> = { + readOnly: { + read: true, write: false, admin: false, - comment: false, - }) + comment: true, + }, + readAndWrite: { + read: true, + write: true, + admin: false, + comment: true, + }, + owner: { + read: true, + write: true, + admin: true, + comment: true, + }, +} + +const anonymousPermissionsMap: typeof permissionsMap = { + readOnly: { ...permissionsMap.readOnly, comment: false }, + readAndWrite: { ...permissionsMap.readAndWrite, comment: false }, + owner: { ...permissionsMap.owner, comment: false }, +} + +export const PermissionsProvider: React.FC = ({ children }) => { + const [permissions, setPermissions] = + useScopeValue>('permissions') const { connectionState } = useConnectionContext() - const { permissionsLevel } = useEditorContext() + const { permissionsLevel } = useEditorContext() as { + permissionsLevel: PermissionsLevel + } const anonymous = getMeta('ol-anonymous') as boolean | undefined useEffect(() => { - if (permissionsLevel === 'readOnly') { - if (anonymous) { - setPermissions(permissionsMap.anonymous.readOnly) - } else { - setPermissions(permissionsMap.readOnly) - } - } - if (permissionsLevel === 'readAndWrite') { - if (permissions.admin) { - if (anonymous) { - setPermissions(permissionsMap.anonymous.owner) - } else { - setPermissions(permissionsMap.owner) - } - } else { - if (anonymous) { - setPermissions(permissionsMap.anonymous.readAndWrite) - } else { - setPermissions(permissionsMap.readAndWrite) - } - } - } - if (permissionsLevel === 'owner') { - if (anonymous) { - setPermissions(permissionsMap.anonymous.owner) - } else { - setPermissions(permissionsMap.owner) - } - } - }, [anonymous, permissions, permissionsLevel]) + const activePermissionsMap = anonymous + ? anonymousPermissionsMap + : permissionsMap + setPermissions(activePermissionsMap[permissionsLevel]) + }, [anonymous, permissionsLevel, setPermissions]) useEffect(() => { if (connectionState.forceDisconnected) { setPermissions(prevState => ({ ...prevState, write: false })) } - }, [connectionState.forceDisconnected]) - - const value = useMemo( - () => ({ - permissions, - }), - [permissions] - ) + }, [connectionState.forceDisconnected, setPermissions]) return ( - + {children} ) } -export function usePermissionsContext(): PermissionsContextValue { +export function usePermissionsContext() { const context = useContext(PermissionsContext) if (!context) { diff --git a/services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts b/services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts index a98a7f3a3b..5f65b1fcb9 100644 --- a/services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts +++ b/services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts @@ -129,7 +129,7 @@ function useReviewPanelState(): ReviewPanelStateReactIde { } = project const { isRestrictedTokenMember } = useEditorContext() // TODO permissions to be removed from the review panel context. It currently acts just as a proxy. - const { permissions } = usePermissionsContext() + const permissions = usePermissionsContext() const { showGenericMessageModal } = useModalsContext() const addCommentEmitter = useScopeEventEmitter('comment:start_adding') diff --git a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts index 629b28250e..ee191d80a1 100644 --- a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts +++ b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts @@ -26,7 +26,6 @@ import { useIdeContext } from '../../../shared/context/ide-context' import { restoreScrollPosition } from '../extensions/scroll-position' import { setEditable } from '../extensions/editable' import { useFileTreeData } from '../../../shared/context/file-tree-data-context' -import { useEditorContext } from '../../../shared/context/editor-context' import { setAutoPair } from '../extensions/auto-pair' import { setAutoComplete } from '../extensions/auto-complete' import { usePhrases } from './use-phrases' @@ -51,7 +50,8 @@ function useCodeMirrorScope(view: EditorView) { const ide = useIdeContext() const { fileTreeData } = useFileTreeData() - const { permissionsLevel } = useEditorContext() + + const [permissions] = useScopeValue<{ write: boolean }>('permissions') // set up scope listeners @@ -240,7 +240,7 @@ function useCodeMirrorScope(view: EditorView) { } }, [view, fileTreeData]) - const editableRef = useRef(permissionsLevel !== 'readOnly') + const editableRef = useRef(permissions.write) const { previewByPath } = useFileTreePathContext() @@ -317,9 +317,9 @@ function useCodeMirrorScope(view: EditorView) { }, [view, previewByPath]) useEffect(() => { - editableRef.current = permissionsLevel !== 'readOnly' + editableRef.current = permissions.write view.dispatch(setEditable(editableRef.current)) // the editor needs to be locked when there's a problem saving data - }, [view, permissionsLevel]) + }, [view, permissions.write]) useEffect(() => { phrasesRef.current = phrases diff --git a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-readonly.spec.tsx b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-readonly.spec.tsx index 8f26ab8f6f..92d061323b 100644 --- a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-readonly.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-readonly.spec.tsx @@ -13,7 +13,7 @@ const mountEditor = ( props?: Omit, 'children' | 'scope'> ) => { const scope = mockScope(content) - scope.permissionsLevel = 'readOnly' + scope.permissions.write = false scope.editor.showVisual = true cy.mount( diff --git a/services/web/test/frontend/features/source-editor/components/codemirror-editor.spec.tsx b/services/web/test/frontend/features/source-editor/components/codemirror-editor.spec.tsx index 7036cbecdf..c77165670d 100644 --- a/services/web/test/frontend/features/source-editor/components/codemirror-editor.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/codemirror-editor.spec.tsx @@ -227,6 +227,7 @@ describe('', { scrollBehavior: false }, function () { it('does not allow typing to the document in read-only mode', function () { const scope = mockScope() scope.permissionsLevel = 'readOnly' + scope.permissions.write = false cy.mount(