mirror of
https://github.com/overleaf/overleaf.git
synced 2025-03-22 02:04:31 +00:00
Merge pull request #15829 from overleaf/ii-ide-page-prototype-permissions
[web] React ide page permissions GitOrigin-RevId: 727d33e0654d4bfefe5e710e553895f793cacb82
This commit is contained in:
parent
1f39b6d72a
commit
e4f5afc0c0
14 changed files with 168 additions and 48 deletions
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
])
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export type PermissionsLevel = 'owner' | 'readAndWrite' | 'readOnly'
|
|
@ -0,0 +1,8 @@
|
|||
export type Permissions = {
|
||||
read: boolean
|
||||
write: boolean
|
||||
admin: boolean
|
||||
comment: boolean
|
||||
}
|
||||
|
||||
export type PermissionsLevel = 'owner' | 'readAndWrite' | 'readOnly'
|
|
@ -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({
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue