mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #15557 from overleaf/ii-ide-page-prototype-review-panel-track-changes
Review panel track changes for React IDE page GitOrigin-RevId: d061596581ff10bd897b286dcd5c280ce79a6384
This commit is contained in:
parent
71a78c8edd
commit
7db5d761ea
18 changed files with 495 additions and 109 deletions
|
@ -1,4 +1,3 @@
|
|||
import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store'
|
||||
import {
|
||||
createContext,
|
||||
FC,
|
||||
|
@ -79,35 +78,6 @@ export type EditorScopeValue = {
|
|||
error_state: boolean
|
||||
}
|
||||
|
||||
export function populateEditorScope(
|
||||
store: ReactScopeValueStore,
|
||||
projectId: string
|
||||
) {
|
||||
// This value is not used in the React code. It's just here to prevent errors
|
||||
// from EditorProvider
|
||||
store.set('state.loading', false)
|
||||
|
||||
store.set('project.name', null)
|
||||
|
||||
store.set('editor', {
|
||||
showSymbolPalette: false,
|
||||
toggleSymbolPalette: () => {},
|
||||
sharejs_doc: null,
|
||||
open_doc_id: null,
|
||||
open_doc_name: null,
|
||||
opening: true,
|
||||
trackChanges: false,
|
||||
wantTrackChanges: false,
|
||||
// No Ace here
|
||||
newSourceEditor: true,
|
||||
error_state: false,
|
||||
})
|
||||
store.persisted('editor.showVisual', false, `editor.mode.${projectId}`, {
|
||||
toPersisted: showVisual => (showVisual ? 'rich-text' : 'source'),
|
||||
fromPersisted: mode => mode === 'rich-text',
|
||||
})
|
||||
}
|
||||
|
||||
const EditorManagerContext = createContext<EditorManager | undefined>(undefined)
|
||||
|
||||
export const EditorManagerProvider: FC = ({ children }) => {
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
import { JoinProjectPayload } from '@/features/ide-react/connection/join-project-payload'
|
||||
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
|
||||
import { getMockIde } from '@/shared/context/mock/mock-ide'
|
||||
import { populateEditorScope } from '@/features/ide-react/context/editor-manager-context'
|
||||
import { populateEditorScope } from '@/features/ide-react/scope-adapters/editor-manager-context-adapter'
|
||||
import { postJSON } from '@/infrastructure/fetch-json'
|
||||
import { EventLog } from '@/features/ide-react/editor/event-log'
|
||||
import { populateSettingsScope } from '@/features/ide-react/scope-adapters/settings-adapter'
|
||||
|
|
|
@ -1,21 +1,76 @@
|
|||
import { useState, useMemo, useCallback } from 'react'
|
||||
import { useState, useEffect, useMemo, useCallback, useRef } from 'react'
|
||||
import useScopeValue from '../../../../../shared/hooks/use-scope-value'
|
||||
import useSocketListener from '@/features/ide-react/hooks/use-socket-listener'
|
||||
import { sendMB } from '../../../../../infrastructure/event-tracking'
|
||||
import { dispatchReviewPanelLayout as handleLayoutChange } from '@/features/source-editor/extensions/changes/change-manager'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
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 { debugConsole } from '@/utils/debugging'
|
||||
import { postJSON } from '@/infrastructure/fetch-json'
|
||||
import { ReviewPanelStateReactIde } from '../types/review-panel-state'
|
||||
import ColorManager from '@/ide/colors/ColorManager'
|
||||
import * as ReviewPanel from '../types/review-panel-state'
|
||||
import {
|
||||
SubView,
|
||||
ThreadId,
|
||||
} from '../../../../../../../types/review-panel/review-panel'
|
||||
import { UserId } from '../../../../../../../types/user'
|
||||
import { PublicAccessLevel } from '../../../../../../../types/public-access-level'
|
||||
import { DeepReadonly } from '../../../../../../../types/utils'
|
||||
|
||||
function formatUser(user: any): any {
|
||||
let isSelf, name
|
||||
const id =
|
||||
(user != null ? user._id : undefined) ||
|
||||
(user != null ? user.id : undefined)
|
||||
|
||||
if (id == null) {
|
||||
return {
|
||||
email: null,
|
||||
name: 'Anonymous',
|
||||
isSelf: false,
|
||||
hue: ColorManager.ANONYMOUS_HUE,
|
||||
avatar_text: 'A',
|
||||
}
|
||||
}
|
||||
if (id === window.user_id) {
|
||||
name = 'You'
|
||||
isSelf = true
|
||||
} else {
|
||||
name = [user.first_name, user.last_name]
|
||||
.filter(n => n != null && n !== '')
|
||||
.join(' ')
|
||||
if (name === '') {
|
||||
name =
|
||||
(user.email != null ? user.email.split('@')[0] : undefined) || 'Unknown'
|
||||
}
|
||||
isSelf = false
|
||||
}
|
||||
return {
|
||||
id,
|
||||
email: user.email,
|
||||
name,
|
||||
isSelf,
|
||||
hue: ColorManager.getHueForUserId(id),
|
||||
avatar_text: [user.first_name, user.last_name]
|
||||
.filter(n => n != null)
|
||||
.map(n => n[0])
|
||||
.join(''),
|
||||
}
|
||||
}
|
||||
|
||||
function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||
const { reviewPanelOpen, setReviewPanelOpen } = useLayoutContext()
|
||||
const { projectId } = useIdeReactContext()
|
||||
const project = useProjectContext()
|
||||
const user = useUserContext()
|
||||
const { socket } = useConnectionContext()
|
||||
const {
|
||||
features: { trackChangesVisible },
|
||||
} = useProjectContext()
|
||||
features: { trackChangesVisible, trackChanges },
|
||||
} = project
|
||||
|
||||
const [subView, setSubView] = useScopeValue<ReviewPanel.Value<'subView'>>(
|
||||
'reviewPanel.subView'
|
||||
|
@ -47,7 +102,7 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
|||
ReviewPanel.Value<'resolvedComments'>
|
||||
>('reviewPanel.resolvedComments', true)
|
||||
|
||||
const [wantTrackChanges] = useScopeValue<
|
||||
const [wantTrackChanges, setWantTrackChanges] = useScopeValue<
|
||||
ReviewPanel.Value<'wantTrackChanges'>
|
||||
>('editor.wantTrackChanges')
|
||||
const [openDocId] =
|
||||
|
@ -58,28 +113,337 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
|||
'reviewPanel.rendererData.lineHeight'
|
||||
)
|
||||
|
||||
const [toggleTrackChangesForEveryone] = useScopeValue<
|
||||
ReviewPanel.UpdaterFn<'toggleTrackChangesForEveryone'>
|
||||
>('toggleTrackChangesForEveryone')
|
||||
const [toggleTrackChangesForUser] = useScopeValue<
|
||||
ReviewPanel.UpdaterFn<'toggleTrackChangesForUser'>
|
||||
>('toggleTrackChangesForUser')
|
||||
const [toggleTrackChangesForGuests] = useScopeValue<
|
||||
ReviewPanel.UpdaterFn<'toggleTrackChangesForGuests'>
|
||||
>('toggleTrackChangesForGuests')
|
||||
|
||||
const [trackChangesState] = useScopeValue<
|
||||
const [formattedProjectMembers, setFormattedProjectMembers] = useState<
|
||||
ReviewPanel.Value<'formattedProjectMembers'>
|
||||
>({})
|
||||
const [trackChangesState, setTrackChangesState] = useState<
|
||||
ReviewPanel.Value<'trackChangesState'>
|
||||
>('reviewPanel.trackChangesState')
|
||||
const [trackChangesOnForEveryone] = useScopeValue<
|
||||
ReviewPanel.Value<'trackChangesOnForEveryone'>
|
||||
>('reviewPanel.trackChangesOnForEveryone')
|
||||
const [trackChangesOnForGuests] = useScopeValue<
|
||||
ReviewPanel.Value<'trackChangesOnForGuests'>
|
||||
>('reviewPanel.trackChangesOnForGuests')
|
||||
const [trackChangesForGuestsAvailable] = useScopeValue<
|
||||
ReviewPanel.Value<'trackChangesForGuestsAvailable'>
|
||||
>('reviewPanel.trackChangesForGuestsAvailable')
|
||||
>({})
|
||||
const [trackChangesOnForEveryone, setTrackChangesOnForEveryone] =
|
||||
useState<ReviewPanel.Value<'trackChangesOnForEveryone'>>(false)
|
||||
const [trackChangesOnForGuests, setTrackChangesOnForGuests] =
|
||||
useState<ReviewPanel.Value<'trackChangesOnForGuests'>>(false)
|
||||
const [trackChangesForGuestsAvailable, setTrackChangesForGuestsAvailable] =
|
||||
useState<ReviewPanel.Value<'trackChangesForGuestsAvailable'>>(false)
|
||||
|
||||
const currentUserType = useCallback((): 'member' | 'guest' | 'anonymous' => {
|
||||
if (!user) {
|
||||
return 'anonymous'
|
||||
}
|
||||
if (project.owner === user.id) {
|
||||
return 'member'
|
||||
}
|
||||
for (const member of project.members as any[]) {
|
||||
if (member._id === user.id) {
|
||||
return 'member'
|
||||
}
|
||||
}
|
||||
return 'guest'
|
||||
}, [project.members, project.owner, user])
|
||||
|
||||
const applyClientTrackChangesStateToServer = useCallback(
|
||||
(
|
||||
trackChangesOnForEveryone: boolean,
|
||||
trackChangesOnForGuests: boolean,
|
||||
trackChangesState: ReviewPanel.Value<'trackChangesState'>
|
||||
) => {
|
||||
const data: {
|
||||
on?: boolean
|
||||
on_for?: Record<UserId, boolean>
|
||||
on_for_guests?: boolean
|
||||
} = {}
|
||||
if (trackChangesOnForEveryone) {
|
||||
data.on = true
|
||||
} else {
|
||||
data.on_for = {}
|
||||
const entries = Object.entries(trackChangesState) as Array<
|
||||
[
|
||||
UserId,
|
||||
NonNullable<
|
||||
typeof trackChangesState[keyof typeof trackChangesState]
|
||||
>
|
||||
]
|
||||
>
|
||||
for (const [userId, { value }] of entries) {
|
||||
data.on_for[userId] = value
|
||||
}
|
||||
if (trackChangesOnForGuests) {
|
||||
data.on_for_guests = true
|
||||
}
|
||||
}
|
||||
postJSON(`/project/${projectId}/track_changes`, {
|
||||
body: data,
|
||||
}).catch(debugConsole.error)
|
||||
},
|
||||
[projectId]
|
||||
)
|
||||
|
||||
const setGuestsTCState = useCallback(
|
||||
(newValue: boolean) => {
|
||||
setTrackChangesOnForGuests(newValue)
|
||||
if (currentUserType() === 'guest' || currentUserType() === 'anonymous') {
|
||||
setWantTrackChanges(newValue)
|
||||
}
|
||||
},
|
||||
[currentUserType, setWantTrackChanges]
|
||||
)
|
||||
|
||||
const setUserTCState = useCallback(
|
||||
(
|
||||
trackChangesState: DeepReadonly<ReviewPanel.Value<'trackChangesState'>>,
|
||||
userId: UserId,
|
||||
newValue: boolean,
|
||||
isLocal = false
|
||||
) => {
|
||||
const newTrackChangesState: ReviewPanel.Value<'trackChangesState'> = {
|
||||
...trackChangesState,
|
||||
}
|
||||
const state =
|
||||
newTrackChangesState[userId] ??
|
||||
({} as NonNullable<typeof newTrackChangesState[UserId]>)
|
||||
newTrackChangesState[userId] = state
|
||||
|
||||
if (state.syncState == null || state.syncState === 'synced') {
|
||||
state.value = newValue
|
||||
state.syncState = 'synced'
|
||||
} else if (state.syncState === 'pending' && state.value === newValue) {
|
||||
state.syncState = 'synced'
|
||||
} else if (isLocal) {
|
||||
state.value = newValue
|
||||
state.syncState = 'pending'
|
||||
}
|
||||
|
||||
setTrackChangesState(newTrackChangesState)
|
||||
|
||||
if (userId === user.id) {
|
||||
setWantTrackChanges(newValue)
|
||||
}
|
||||
|
||||
return newTrackChangesState
|
||||
},
|
||||
[setWantTrackChanges, user.id]
|
||||
)
|
||||
|
||||
const setEveryoneTCState = useCallback(
|
||||
(newValue: boolean, isLocal = false) => {
|
||||
setTrackChangesOnForEveryone(newValue)
|
||||
let newTrackChangesState: ReviewPanel.Value<'trackChangesState'> = {
|
||||
...trackChangesState,
|
||||
}
|
||||
for (const member of project.members as any[]) {
|
||||
newTrackChangesState = setUserTCState(
|
||||
newTrackChangesState,
|
||||
member._id,
|
||||
newValue,
|
||||
isLocal
|
||||
)
|
||||
}
|
||||
setGuestsTCState(newValue)
|
||||
|
||||
newTrackChangesState = setUserTCState(
|
||||
newTrackChangesState,
|
||||
project.owner._id,
|
||||
newValue,
|
||||
isLocal
|
||||
)
|
||||
|
||||
return { trackChangesState: newTrackChangesState }
|
||||
},
|
||||
[
|
||||
project.members,
|
||||
project.owner._id,
|
||||
setGuestsTCState,
|
||||
setUserTCState,
|
||||
trackChangesState,
|
||||
]
|
||||
)
|
||||
|
||||
const toggleTrackChangesForEveryone = useCallback<
|
||||
ReviewPanel.UpdaterFn<'toggleTrackChangesForEveryone'>
|
||||
>(
|
||||
(onForEveryone: boolean) => {
|
||||
const { trackChangesState } = setEveryoneTCState(onForEveryone, true)
|
||||
setGuestsTCState(onForEveryone)
|
||||
applyClientTrackChangesStateToServer(
|
||||
onForEveryone,
|
||||
onForEveryone,
|
||||
trackChangesState
|
||||
)
|
||||
},
|
||||
[applyClientTrackChangesStateToServer, setEveryoneTCState, setGuestsTCState]
|
||||
)
|
||||
|
||||
const toggleTrackChangesForGuests = useCallback<
|
||||
ReviewPanel.UpdaterFn<'toggleTrackChangesForGuests'>
|
||||
>(
|
||||
(onForGuests: boolean) => {
|
||||
setGuestsTCState(onForGuests)
|
||||
applyClientTrackChangesStateToServer(
|
||||
trackChangesOnForEveryone,
|
||||
onForGuests,
|
||||
trackChangesState
|
||||
)
|
||||
},
|
||||
[
|
||||
applyClientTrackChangesStateToServer,
|
||||
setGuestsTCState,
|
||||
trackChangesOnForEveryone,
|
||||
trackChangesState,
|
||||
]
|
||||
)
|
||||
|
||||
const toggleTrackChangesForUser = useCallback<
|
||||
ReviewPanel.UpdaterFn<'toggleTrackChangesForUser'>
|
||||
>(
|
||||
(onForUser: boolean, userId: UserId) => {
|
||||
const newTrackChangesState = setUserTCState(
|
||||
trackChangesState,
|
||||
userId,
|
||||
onForUser,
|
||||
true
|
||||
)
|
||||
applyClientTrackChangesStateToServer(
|
||||
trackChangesOnForEveryone,
|
||||
trackChangesOnForGuests,
|
||||
newTrackChangesState
|
||||
)
|
||||
},
|
||||
[
|
||||
applyClientTrackChangesStateToServer,
|
||||
setUserTCState,
|
||||
trackChangesOnForEveryone,
|
||||
trackChangesOnForGuests,
|
||||
trackChangesState,
|
||||
]
|
||||
)
|
||||
|
||||
const applyTrackChangesStateToClient = useCallback(
|
||||
(state: boolean | Record<UserId, boolean>) => {
|
||||
if (typeof state === 'boolean') {
|
||||
setEveryoneTCState(state)
|
||||
setGuestsTCState(state)
|
||||
} else {
|
||||
setTrackChangesOnForEveryone(false)
|
||||
// TODO
|
||||
// @ts-ignore
|
||||
setGuestsTCState(state.__guests__ === true)
|
||||
|
||||
let newTrackChangesState: ReviewPanel.Value<'trackChangesState'> = {
|
||||
...trackChangesState,
|
||||
}
|
||||
for (const member of project.members as any[]) {
|
||||
newTrackChangesState = setUserTCState(
|
||||
newTrackChangesState,
|
||||
member._id,
|
||||
state[member._id] ?? false
|
||||
)
|
||||
}
|
||||
newTrackChangesState = setUserTCState(
|
||||
newTrackChangesState,
|
||||
project.owner._id,
|
||||
state[project.owner._id] ?? false
|
||||
)
|
||||
return newTrackChangesState
|
||||
}
|
||||
},
|
||||
[
|
||||
project.members,
|
||||
project.owner._id,
|
||||
setEveryoneTCState,
|
||||
setGuestsTCState,
|
||||
setUserTCState,
|
||||
trackChangesState,
|
||||
]
|
||||
)
|
||||
|
||||
const setGuestFeatureBasedOnProjectAccessLevel = (
|
||||
projectPublicAccessLevel: PublicAccessLevel
|
||||
) => {
|
||||
setTrackChangesForGuestsAvailable(projectPublicAccessLevel === 'tokenBased')
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setGuestFeatureBasedOnProjectAccessLevel(project.publicAccessLevel)
|
||||
}, [project.publicAccessLevel])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
trackChangesForGuestsAvailable ||
|
||||
!trackChangesOnForGuests ||
|
||||
trackChangesOnForEveryone
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Overrides guest setting
|
||||
toggleTrackChangesForGuests(false)
|
||||
}, [
|
||||
toggleTrackChangesForGuests,
|
||||
trackChangesForGuestsAvailable,
|
||||
trackChangesOnForEveryone,
|
||||
trackChangesOnForGuests,
|
||||
])
|
||||
|
||||
const projectJoinedEffectExecuted = useRef(false)
|
||||
useEffect(() => {
|
||||
if (!projectJoinedEffectExecuted.current) {
|
||||
requestAnimationFrame(() => {
|
||||
if (trackChanges) {
|
||||
applyTrackChangesStateToClient(project.trackChangesState)
|
||||
} else {
|
||||
applyTrackChangesStateToClient(false)
|
||||
}
|
||||
setGuestFeatureBasedOnProjectAccessLevel(project.publicAccessLevel)
|
||||
})
|
||||
projectJoinedEffectExecuted.current = true
|
||||
}
|
||||
}, [
|
||||
applyTrackChangesStateToClient,
|
||||
trackChanges,
|
||||
project.publicAccessLevel,
|
||||
project.trackChangesState,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
setFormattedProjectMembers(prevState => {
|
||||
const tempFormattedProjectMembers: typeof prevState = {}
|
||||
if (project.owner) {
|
||||
tempFormattedProjectMembers[project.owner._id] = formatUser(
|
||||
project.owner
|
||||
)
|
||||
}
|
||||
if (project.members) {
|
||||
for (const member of project.members) {
|
||||
if (member.privileges === 'readAndWrite') {
|
||||
if (!trackChangesState[member._id]) {
|
||||
// An added member will have track changes enabled if track changes is on for everyone
|
||||
setUserTCState(
|
||||
trackChangesState,
|
||||
member._id,
|
||||
trackChangesOnForEveryone,
|
||||
true
|
||||
)
|
||||
}
|
||||
tempFormattedProjectMembers[member._id] = formatUser(member)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tempFormattedProjectMembers
|
||||
})
|
||||
}, [
|
||||
project.members,
|
||||
project.owner,
|
||||
setUserTCState,
|
||||
trackChangesOnForEveryone,
|
||||
trackChangesState,
|
||||
])
|
||||
|
||||
useSocketListener(
|
||||
socket,
|
||||
'toggle-track-changes',
|
||||
applyTrackChangesStateToClient
|
||||
)
|
||||
|
||||
const [resolveComment] =
|
||||
useScopeValue<ReviewPanel.UpdaterFn<'resolveComment'>>('resolveComment')
|
||||
const [submitNewComment] =
|
||||
|
@ -95,10 +459,6 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
|||
(entry: { thread_id: ThreadId; replyContent: string }) => void
|
||||
>('submitReply')
|
||||
|
||||
const [formattedProjectMembers] = useScopeValue<
|
||||
ReviewPanel.Value<'formattedProjectMembers'>
|
||||
>('reviewPanel.formattedProjectMembers')
|
||||
|
||||
const toggleReviewPanel = useCallback(() => {
|
||||
if (!trackChangesVisible) {
|
||||
return
|
||||
|
@ -150,6 +510,43 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
|||
const [toolbarHeight, setToolbarHeight] = useState(0)
|
||||
const [layoutSuspended, setLayoutSuspended] = useState(false)
|
||||
|
||||
// listen for events from the CodeMirror 6 track changes extension
|
||||
useEffect(() => {
|
||||
const toggleTrackChangesFromKbdShortcut = () => {
|
||||
if (trackChangesVisible && trackChanges) {
|
||||
const userId: UserId = user.id
|
||||
const state = trackChangesState[userId]
|
||||
if (state) {
|
||||
toggleTrackChangesForUser(!state.value, userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleEditorEvents = (e: Event) => {
|
||||
const event = e as CustomEvent
|
||||
const { type } = event.detail
|
||||
|
||||
switch (type) {
|
||||
case 'toggle-track-changes': {
|
||||
toggleTrackChangesFromKbdShortcut()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('editor:event', handleEditorEvents)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('editor:event', handleEditorEvents)
|
||||
}
|
||||
}, [
|
||||
toggleTrackChangesForUser,
|
||||
trackChanges,
|
||||
trackChangesState,
|
||||
trackChangesVisible,
|
||||
user.id,
|
||||
])
|
||||
|
||||
const values = useMemo<ReviewPanelStateReactIde['values']>(
|
||||
() => ({
|
||||
collapsed,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useContext, createContext } from 'react'
|
||||
import { createContext } from 'react'
|
||||
import useReviewPanelState from '@/features/ide-react/context/review-panel/hooks/use-review-panel-state'
|
||||
import { ReviewPanelStateReactIde } from '@/features/ide-react/context/review-panel/types/review-panel-state'
|
||||
|
||||
|
@ -21,23 +21,3 @@ export const ReviewPanelReactIdeProvider: React.FC = ({ children }) => {
|
|||
</ReviewPanelReactIdeValueContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useReviewPanelReactIdeValueContext() {
|
||||
const context = useContext(ReviewPanelReactIdeValueContext)
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'ReviewPanelReactIdeValueContext is only available inside ReviewPanelReactIdeProvider'
|
||||
)
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
export function useReviewPanelReactIdeUpdaterFnsContext() {
|
||||
const context = useContext(ReviewPanelReactIdeUpdaterFnsContext)
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'ReviewPanelReactIdeUpdaterFnsContext is only available inside ReviewPanelReactIdeProvider'
|
||||
)
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store'
|
||||
|
||||
export function populateEditorScope(
|
||||
store: ReactScopeValueStore,
|
||||
projectId: string
|
||||
) {
|
||||
// This value is not used in the React code. It's just here to prevent errors
|
||||
// from EditorProvider
|
||||
store.set('state.loading', false)
|
||||
|
||||
store.set('project.name', null)
|
||||
|
||||
store.set('editor', {
|
||||
showSymbolPalette: false,
|
||||
toggleSymbolPalette: () => {},
|
||||
sharejs_doc: null,
|
||||
open_doc_id: null,
|
||||
open_doc_name: null,
|
||||
opening: true,
|
||||
trackChanges: false,
|
||||
wantTrackChanges: false,
|
||||
// No Ace here
|
||||
newSourceEditor: true,
|
||||
error_state: false,
|
||||
})
|
||||
store.persisted('editor.showVisual', false, `editor.mode.${projectId}`, {
|
||||
toPersisted: showVisual => (showVisual ? 'rich-text' : 'source'),
|
||||
fromPersisted: mode => mode === 'rich-text',
|
||||
})
|
||||
}
|
|
@ -17,20 +17,11 @@ export default function populateReviewPanelScope(store: ReactScopeValueStore) {
|
|||
store.set('users', {})
|
||||
store.set('reviewPanel.resolvedComments', {})
|
||||
store.set('reviewPanel.rendererData.lineHeight', 0)
|
||||
store.set('reviewPanel.trackChangesState', {})
|
||||
store.set('reviewPanel.trackChangesOnForEveryone', false)
|
||||
store.set('reviewPanel.trackChangesOnForGuests', false)
|
||||
store.set('reviewPanel.trackChangesForGuestsAvailable', false)
|
||||
store.set('reviewPanel.formattedProjectMembers', {})
|
||||
store.set('toggleTrackChangesForEveryone', () => {})
|
||||
store.set('toggleTrackChangesForUser', () => {})
|
||||
store.set('toggleTrackChangesForGuests', () => {})
|
||||
store.set('resolveComment', () => {})
|
||||
store.set('submitNewComment', async () => {})
|
||||
store.set('deleteComment', () => {})
|
||||
store.set('gotoEntry', () => {})
|
||||
store.set('saveEdit', () => {})
|
||||
store.set('toggleReviewPanel', () => {})
|
||||
store.set('unresolveComment', () => {})
|
||||
store.set('deleteThread', () => {})
|
||||
store.set('refreshResolvedCommentsDropdown', async () => {})
|
||||
|
|
|
@ -141,11 +141,11 @@ function ToggleMenu() {
|
|||
description={t('track_changes_for_x', { name: member.name })}
|
||||
handleToggle={() =>
|
||||
toggleTrackChangesForUser(
|
||||
!trackChangesState[member.id].value,
|
||||
!trackChangesState[member.id]?.value,
|
||||
member.id
|
||||
)
|
||||
}
|
||||
value={trackChangesState[member.id].value}
|
||||
value={Boolean(trackChangesState[member.id]?.value)}
|
||||
disabled={
|
||||
trackChangesOnForEveryone ||
|
||||
!project.features.trackChanges ||
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
} from '../../../../../../../types/review-panel/review-panel'
|
||||
import { DocId } from '../../../../../../../types/project-settings'
|
||||
import { dispatchReviewPanelLayout } from '../../../extensions/changes/change-manager'
|
||||
import { UserId } from '../../../../../../../types/user'
|
||||
|
||||
/* eslint-disable no-use-before-define */
|
||||
export interface ReviewPanelState {
|
||||
|
@ -31,14 +32,16 @@ export interface ReviewPanelState {
|
|||
loading: boolean
|
||||
openDocId: DocId | null
|
||||
lineHeight: number
|
||||
trackChangesState: Record<string, { value: boolean; syncState: string }>
|
||||
trackChangesState:
|
||||
| Record<UserId, { value: boolean; syncState: 'synced' | 'pending' }>
|
||||
| Record<UserId, undefined>
|
||||
trackChangesOnForEveryone: boolean
|
||||
trackChangesOnForGuests: boolean
|
||||
trackChangesForGuestsAvailable: boolean
|
||||
formattedProjectMembers: Record<
|
||||
string,
|
||||
{
|
||||
id: string
|
||||
id: UserId
|
||||
name: string
|
||||
}
|
||||
>
|
||||
|
@ -55,9 +58,9 @@ export interface ReviewPanelState {
|
|||
submitReply: (threadId: ThreadId, replyContent: string) => void
|
||||
acceptChanges: (entryIds: unknown) => void
|
||||
rejectChanges: (entryIds: unknown) => void
|
||||
toggleTrackChangesForEveryone: (isOn: boolean) => unknown
|
||||
toggleTrackChangesForUser: (isOn: boolean, memberId: string) => unknown
|
||||
toggleTrackChangesForGuests: (isOn: boolean) => unknown
|
||||
toggleTrackChangesForEveryone: (onForEveryone: boolean) => void
|
||||
toggleTrackChangesForUser: (onForUser: boolean, userId: UserId) => void
|
||||
toggleTrackChangesForGuests: (onForGuests: boolean) => void
|
||||
toggleReviewPanel: () => void
|
||||
bulkAcceptActions: () => void
|
||||
bulkRejectActions: () => void
|
||||
|
|
|
@ -89,6 +89,7 @@ export function ProjectProvider({ children }) {
|
|||
publicAccesLevel: publicAccessLevel,
|
||||
owner,
|
||||
showNewCompileTimeoutUI,
|
||||
trackChangesState,
|
||||
} = project || projectFallback
|
||||
|
||||
const tags = useMemo(
|
||||
|
@ -123,6 +124,7 @@ export function ProjectProvider({ children }) {
|
|||
showNewCompileTimeoutUI:
|
||||
newCompileTimeoutOverride || showNewCompileTimeoutUI,
|
||||
tags,
|
||||
trackChangesState,
|
||||
}
|
||||
}, [
|
||||
_id,
|
||||
|
@ -136,6 +138,7 @@ export function ProjectProvider({ children }) {
|
|||
showNewCompileTimeoutUI,
|
||||
newCompileTimeoutOverride,
|
||||
tags,
|
||||
trackChangesState,
|
||||
])
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useMemo } from 'react'
|
||||
import { get } from 'lodash'
|
||||
import { User } from '../../../types/user'
|
||||
import { User, UserId } from '../../../types/user'
|
||||
import { Project } from '../../../types/project'
|
||||
import {
|
||||
mockBuildFile,
|
||||
|
@ -26,7 +26,7 @@ const scopeWatchers: [string, (value: any) => void][] = []
|
|||
|
||||
const initialize = () => {
|
||||
const user: User = {
|
||||
id: 'story-user',
|
||||
id: 'story-user' as UserId,
|
||||
email: 'story-user@example.com',
|
||||
allowedFreeTrial: true,
|
||||
features: { dropbox: true, symbolPalette: true },
|
||||
|
|
|
@ -6,6 +6,7 @@ import { mockScope } from '../helpers/mock-scope'
|
|||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import CodeMirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
|
||||
import { activeEditorLine } from '../helpers/active-editor-line'
|
||||
import { UserId } from '../../../../../types/user'
|
||||
|
||||
const Container: FC = ({ children }) => (
|
||||
<div style={{ width: 785, height: 785 }}>{children}</div>
|
||||
|
@ -860,7 +861,7 @@ describe('autocomplete', { scrollBehavior: false }, function () {
|
|||
|
||||
window.metaAttributesCache.set('ol-showSymbolPalette', true)
|
||||
const user = {
|
||||
id: '123abd',
|
||||
id: '123abd' as UserId,
|
||||
email: 'testuser@example.com',
|
||||
}
|
||||
cy.mount(
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
groupActiveSubscriptionWithPendingLicenseChange,
|
||||
} from '../../fixtures/subscriptions'
|
||||
import * as useLocationModule from '../../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { UserId } from '../../../../../../types/user'
|
||||
|
||||
const userId = 'fff999fff999'
|
||||
const memberGroupSubscriptions: MemberGroupSubscription[] = [
|
||||
|
@ -24,7 +25,7 @@ const memberGroupSubscriptions: MemberGroupSubscription[] = [
|
|||
userIsGroupManager: false,
|
||||
planLevelName: 'Professional',
|
||||
admin_id: {
|
||||
id: 'abc123abc123',
|
||||
id: 'abc123abc123' as UserId,
|
||||
email: 'you@example.com',
|
||||
},
|
||||
},
|
||||
|
@ -33,7 +34,7 @@ const memberGroupSubscriptions: MemberGroupSubscription[] = [
|
|||
userIsGroupManager: true,
|
||||
planLevelName: 'Collaborator',
|
||||
admin_id: {
|
||||
id: 'bcd456bcd456',
|
||||
id: 'bcd456bcd456' as UserId,
|
||||
email: 'someone@example.com',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
cleanUpContext,
|
||||
renderWithSubscriptionDashContext,
|
||||
} from '../../helpers/render-with-subscription-dash-context'
|
||||
import { UserId } from '../../../../../../types/user'
|
||||
|
||||
function getManagedGroupSubscription(groupSSO: boolean, managedUsers: boolean) {
|
||||
const subscriptionOne = {
|
||||
|
@ -17,7 +18,7 @@ function getManagedGroupSubscription(groupSSO: boolean, managedUsers: boolean) {
|
|||
userIsGroupMember: true,
|
||||
planLevelName: 'Professional',
|
||||
admin_id: {
|
||||
id: 'abc123abc123',
|
||||
id: 'abc123abc123' as UserId,
|
||||
email: 'you@example.com',
|
||||
},
|
||||
features: {
|
||||
|
@ -31,7 +32,7 @@ function getManagedGroupSubscription(groupSSO: boolean, managedUsers: boolean) {
|
|||
userIsGroupMember: false,
|
||||
planLevelName: 'Collaborator',
|
||||
admin_id: {
|
||||
id: 'bcd456bcd456',
|
||||
id: 'bcd456bcd456' as UserId,
|
||||
email: 'someone@example.com',
|
||||
},
|
||||
features: {
|
||||
|
|
5
services/web/types/public-access-level.ts
Normal file
5
services/web/types/public-access-level.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export type PublicAccessLevel =
|
||||
| 'readOnly'
|
||||
| 'readAndWrite'
|
||||
| 'private'
|
||||
| 'tokenBased'
|
|
@ -1,8 +1,8 @@
|
|||
import {
|
||||
ReviewPanelCommentThreadMessage,
|
||||
ReviewPanelUser,
|
||||
UserId,
|
||||
} from './review-panel'
|
||||
import { UserId } from '../user'
|
||||
import { DateString } from '../helpers/date'
|
||||
|
||||
interface ReviewPanelCommentThreadBase {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { ThreadId, UserId } from './review-panel'
|
||||
import { ThreadId } from './review-panel'
|
||||
import { UserId } from '../user'
|
||||
|
||||
export interface ReviewPanelEntryScreenPos {
|
||||
y: number
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Brand } from '../helpers/brand'
|
||||
import { DocId } from '../project-settings'
|
||||
import { UserId } from '../user'
|
||||
import {
|
||||
ReviewPanelAddCommentEntry,
|
||||
ReviewPanelBulkActionsEntry,
|
||||
|
@ -29,8 +30,6 @@ export type ReviewPanelDocEntries = Record<
|
|||
|
||||
export type ReviewPanelEntries = Record<DocId, ReviewPanelDocEntries>
|
||||
|
||||
export type UserId = Brand<string, 'UserId'>
|
||||
|
||||
export interface ReviewPanelUser {
|
||||
avatar_text: string
|
||||
email: string
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import { Brand } from './helpers/brand'
|
||||
|
||||
export type RefProviders = {
|
||||
mendeley?: boolean
|
||||
zotero?: boolean
|
||||
}
|
||||
|
||||
export type UserId = Brand<string, 'UserId'>
|
||||
|
||||
export type User = {
|
||||
id: string
|
||||
id: UserId
|
||||
email: string
|
||||
allowedFreeTrial?: boolean
|
||||
signUpDate?: string // date string
|
||||
|
|
Loading…
Reference in a new issue