Merge pull request #15829 from overleaf/ii-ide-page-prototype-permissions

[web] React ide page permissions

GitOrigin-RevId: 727d33e0654d4bfefe5e710e553895f793cacb82
This commit is contained in:
ilkin-overleaf 2023-11-23 14:03:22 +02:00 committed by Copybot
parent 1f39b6d72a
commit e4f5afc0c0
14 changed files with 168 additions and 48 deletions

View file

@ -1,5 +1,5 @@
import { Project } from '../../../../../types/project'
import { PermissionsLevel } from '../types/permissions-level'
import { PermissionsLevel } from '@/features/ide-react/types/permissions'
export type JoinProjectPayloadProject = Pick<
Project,

View file

@ -21,6 +21,7 @@ import useEventListener from '@/shared/hooks/use-event-listener'
import { FileTreeFindResult } from '@/features/ide-react/types/file-tree'
import { Project } from '../../../../../types/project'
import { useModalsContext } from '@/features/ide-react/context/modals-context'
import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
import { useTranslation } from 'react-i18next'
type DocumentMetadata = {
@ -53,6 +54,7 @@ export const MetadataProvider: FC = ({ children }) => {
const { socket } = useConnectionContext()
const { onlineUsersCount } = useOnlineUsersContext()
const { permissionsLevel } = useEditorContext()
const { permissions } = usePermissionsContext()
const { currentDocument } = useEditorManagerContext()
const { showGenericMessageModal } = useModalsContext()
@ -190,7 +192,7 @@ export const MetadataProvider: FC = ({ children }) => {
)
}
window.setTimeout(() => {
if (permissionsLevel !== 'readOnly') {
if (permissions.write) {
loadProjectMetaFromServer()
}
}, 200)
@ -204,7 +206,7 @@ export const MetadataProvider: FC = ({ children }) => {
}, [
eventEmitter,
loadProjectMetaFromServer,
permissionsLevel,
permissions,
showGenericMessageModal,
t,
])

View file

@ -0,0 +1,117 @@
import { createContext, useContext, useState, useEffect, useMemo } 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'
type PermissionsContextValue = {
permissions: Permissions
}
const PermissionsContext = createContext<PermissionsContextValue | undefined>(
undefined
)
const readOnlyPermissions: Readonly<Permissions> = {
read: true,
write: false,
admin: false,
comment: true,
}
const readAndWritePermissions: Readonly<Permissions> = {
read: true,
write: true,
admin: false,
comment: true,
}
const ownerPermissions: Readonly<Permissions> = {
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<Permissions>({
read: false,
write: false,
admin: false,
comment: false,
})
const { connectionState } = useConnectionContext()
const { permissionsLevel } = useEditorContext()
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])
useEffect(() => {
if (connectionState.forceDisconnected) {
setPermissions(prevState => ({ ...prevState, write: false }))
}
}, [connectionState.forceDisconnected])
const value = useMemo<PermissionsContextValue>(
() => ({
permissions,
}),
[permissions]
)
return (
<PermissionsContext.Provider value={value}>
{children}
</PermissionsContext.Provider>
)
}
export function usePermissionsContext(): PermissionsContextValue {
const context = useContext(PermissionsContext)
if (!context) {
throw new Error(
'usePermissionsContext is only available inside PermissionsProvider'
)
}
return context
}

View file

@ -19,6 +19,7 @@ import { SplitTestProvider } from '@/shared/context/split-test-context'
import { ModalsContextProvider } from '@/features/ide-react/context/modals-context'
import { FileTreePathProvider } from '@/features/file-tree/contexts/file-tree-path'
import { UserSettingsProvider } from '@/shared/context/user-settings-context'
import { PermissionsProvider } from '@/features/ide-react/context/permissions-context'
export const ReactContextRoot: FC = ({ children }) => {
return (
@ -33,25 +34,27 @@ export const ReactContextRoot: FC = ({ children }) => {
<ReferencesProvider>
<DetachProvider>
<EditorProvider>
<ProjectSettingsProvider>
<LayoutProvider>
<LocalCompileProvider>
<DetachCompileProvider>
<ChatProvider>
<ModalsContextProvider>
<EditorManagerProvider>
<OnlineUsersProvider>
<MetadataProvider>
{children}
</MetadataProvider>
</OnlineUsersProvider>
</EditorManagerProvider>
</ModalsContextProvider>
</ChatProvider>
</DetachCompileProvider>
</LocalCompileProvider>
</LayoutProvider>
</ProjectSettingsProvider>
<PermissionsProvider>
<ProjectSettingsProvider>
<LayoutProvider>
<LocalCompileProvider>
<DetachCompileProvider>
<ChatProvider>
<ModalsContextProvider>
<EditorManagerProvider>
<OnlineUsersProvider>
<MetadataProvider>
{children}
</MetadataProvider>
</OnlineUsersProvider>
</EditorManagerProvider>
</ModalsContextProvider>
</ChatProvider>
</DetachCompileProvider>
</LocalCompileProvider>
</LayoutProvider>
</ProjectSettingsProvider>
</PermissionsProvider>
</EditorProvider>
</DetachProvider>
</ReferencesProvider>

View file

@ -11,13 +11,13 @@ import { useLayoutContext } from '@/shared/context/layout-context'
import { useUserContext } from '@/shared/context/user-context'
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
import { debugConsole } from '@/utils/debugging'
import { useEditorContext } from '@/shared/context/editor-context'
import { getJSON, postJSON } from '@/infrastructure/fetch-json'
import ColorManager from '@/ide/colors/ColorManager'
// @ts-ignore
import RangesTracker from '@overleaf/ranges-tracker'
import { ReviewPanelStateReactIde } from '../types/review-panel-state'
import * as ReviewPanel from '../types/review-panel-state'
import {
ReviewPanelCommentThreadMessage,
@ -28,6 +28,7 @@ import {
} from '../../../../../../../types/review-panel/review-panel'
import { UserId } from '../../../../../../../types/user'
import { PublicAccessLevel } from '../../../../../../../types/public-access-level'
import { ReviewPanelStateReactIde } from '../types/review-panel-state'
import {
DeepReadonly,
MergeAndOverride,
@ -114,6 +115,8 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
features: { trackChangesVisible, trackChanges },
} = project
const { isRestrictedTokenMember } = useEditorContext()
// TODO permissions to be removed from the review panel context. It currently acts just as a proxy.
const { permissions } = usePermissionsContext()
// TODO `currentDocument` and `currentDocumentId` should be get from `useEditorManagerContext()` but that makes tests fail
const [currentDocument] = useScopeValue<Document>('editor.sharejs_doc')
@ -134,9 +137,6 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
ReviewPanel.Value<'commentThreads'>
>({})
const [entries, setEntries] = useState<ReviewPanel.Value<'entries'>>({})
const [permissions] =
useScopeValue<ReviewPanel.Value<'permissions'>>('permissions')
const [users, setUsers] = useScopeValue<ReviewPanel.Value<'users'>>(
'users',
true

View file

@ -1,6 +1,6 @@
import { Emitter } from 'strict-event-emitter'
import { Project } from '../../../../types/project'
import { PermissionsLevel } from '@/features/ide-react/types/permissions-level'
import { PermissionsLevel } from '@/features/ide-react/types/permissions'
import { ShareJsDoc } from '@/features/ide-react/editor/share-js-doc'
import { GotoLineOptions } from '@/features/ide-react/types/goto-line-options'
import { CursorPosition } from '@/features/ide-react/types/cursor-position'

View file

@ -1 +0,0 @@
export type PermissionsLevel = 'owner' | 'readAndWrite' | 'readOnly'

View file

@ -0,0 +1,8 @@
export type Permissions = {
read: boolean
write: boolean
admin: boolean
comment: boolean
}
export type PermissionsLevel = 'owner' | 'readAndWrite' | 'readOnly'

View file

@ -8,10 +8,8 @@ import AutoExpandingTextArea from '../../../../../shared/components/auto-expandi
import Icon from '../../../../../shared/components/icon'
import { useReviewPanelUpdaterFnsContext } from '../../../context/review-panel/review-panel-context'
import classnames from 'classnames'
import {
ReviewPanelPermissions,
ThreadId,
} from '../../../../../../../types/review-panel/review-panel'
import { ThreadId } from '../../../../../../../types/review-panel/review-panel'
import { Permissions } from '@/features/ide-react/types/permissions'
import { DocId } from '../../../../../../../types/project-settings'
import { ReviewPanelCommentThread } from '../../../../../../../types/review-panel/comment-thread'
import { ReviewPanelCommentEntry } from '../../../../../../../types/review-panel/entry'
@ -24,7 +22,7 @@ type CommentEntryProps = {
entryId: ThreadId
thread: ReviewPanelCommentThread | undefined
threadId: ReviewPanelCommentEntry['thread_id']
permissions: ReviewPanelPermissions
permissions: Permissions
} & Pick<ReviewPanelCommentEntry, 'offset' | 'focused'>
function CommentEntry({

View file

@ -4,7 +4,7 @@ import Linkify from 'react-linkify'
import { formatTime } from '../../../../utils/format-date'
import { useReviewPanelUpdaterFnsContext } from '../../../context/review-panel/review-panel-context'
import { FilteredResolvedComments } from '../toolbar/resolved-comments-dropdown'
import { ReviewPanelPermissions } from '../../../../../../../types/review-panel/review-panel'
import { Permissions } from '@/features/ide-react/types/permissions'
function LinkDecorator(
decoratedHref: string,
@ -20,7 +20,7 @@ function LinkDecorator(
type ResolvedCommentEntryProps = {
thread: FilteredResolvedComments
permissions: ReviewPanelPermissions
permissions: Permissions
contentLimit?: number
}

View file

@ -2,11 +2,11 @@ import { useTranslation } from 'react-i18next'
import { useMemo } from 'react'
import ResolvedCommentEntry from '../entries/resolved-comment-entry'
import { FilteredResolvedComments } from './resolved-comments-dropdown'
import { ReviewPanelPermissions } from '../../../../../../../types/review-panel/review-panel'
import { Permissions } from '@/features/ide-react/types/permissions'
type ResolvedCommentsScrollerProps = {
resolvedComments: FilteredResolvedComments[]
permissions: ReviewPanelPermissions
permissions: Permissions
}
function ResolvedCommentsScroller({

View file

@ -1,16 +1,16 @@
import { ReviewPanelChangeEntry } from '../../../../../../../types/review-panel/entry'
import { DocId } from '../../../../../../../types/project-settings'
import {
ReviewPanelPermissions,
ReviewPanelUser,
ThreadId,
} from '../../../../../../../types/review-panel/review-panel'
import { Permissions } from '@/features/ide-react/types/permissions'
export interface BaseChangeEntryProps
extends Pick<ReviewPanelChangeEntry, 'content' | 'offset' | 'focused'> {
docId: DocId
entryId: ThreadId
permissions: ReviewPanelPermissions
permissions: Permissions
user: ReviewPanelUser | undefined
timestamp: ReviewPanelChangeEntry['metadata']['ts']
contentLimit?: number

View file

@ -2,11 +2,11 @@ import {
CommentId,
ReviewPanelCommentThreads,
ReviewPanelEntries,
ReviewPanelPermissions,
ReviewPanelUsers,
SubView,
ThreadId,
} from '../../../../../../../types/review-panel/review-panel'
import { Permissions } from '@/features/ide-react/types/permissions'
import { DocId } from '../../../../../../../types/project-settings'
import { dispatchReviewPanelLayout } from '../../../extensions/changes/change-manager'
import { UserId } from '../../../../../../../types/user'
@ -21,7 +21,7 @@ export interface ReviewPanelState {
isAddingComment: boolean
loadingThreads: boolean
nVisibleSelectedChanges: number
permissions: ReviewPanelPermissions
permissions: Permissions
users: ReviewPanelUsers
resolvedComments: ReviewPanelEntries
shouldCollapse: boolean

View file

@ -10,13 +10,6 @@ import { ReviewPanelCommentThread } from './comment-thread'
export type SubView = 'cur_file' | 'overview'
export interface ReviewPanelPermissions {
read: boolean
write: boolean
admin: boolean
comment: boolean
}
export type ThreadId = Brand<string, 'ThreadId'>
// Entries may contain `add-comment` and `bulk-actions` props along with DocIds
// Ideally the `add-comment` and `bulk-actions` objects should not be within the entries object