mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-10 14:40:47 +02:00
Merge pull request #15756 from overleaf/ii-ide-page-prototype-review-panel-entries
[web] init review panel entries for React IDE page GitOrigin-RevId: f6e6311e20f1673b1d97a3f5dfcab54e16da42e1
This commit is contained in:
@@ -29,6 +29,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import customLocalStorage from '@/infrastructure/local-storage'
|
||||
import useEventListener from '@/shared/hooks/use-event-listener'
|
||||
import { EditorType } from '@/features/ide-react/editor/types/editor-type'
|
||||
import { DocId } from '../../../../../types/project-settings'
|
||||
|
||||
interface GotoOffsetOptions {
|
||||
gotoOffset: number
|
||||
@@ -45,9 +46,9 @@ type EditorManager = {
|
||||
getEditorType: () => EditorType | null
|
||||
showSymbolPalette: boolean
|
||||
currentDocument: Document
|
||||
currentDocumentId: string | null
|
||||
currentDocumentId: DocId | null
|
||||
getCurrentDocValue: () => string | null
|
||||
getCurrentDocId: () => string | null
|
||||
getCurrentDocId: () => DocId | null
|
||||
startIgnoringExternalUpdates: () => void
|
||||
stopIgnoringExternalUpdates: () => void
|
||||
openDocId: (docId: string, options?: OpenDocOptions) => void
|
||||
@@ -96,7 +97,7 @@ export const EditorManagerProvider: FC = ({ children }) => {
|
||||
const [showVisual] = useScopeValue<boolean>('editor.showVisual')
|
||||
const [currentDocument, setCurrentDocument] =
|
||||
useScopeValue<Document>('editor.sharejs_doc')
|
||||
const [openDocId, setOpenDocId] = useScopeValue<string | null>(
|
||||
const [openDocId, setOpenDocId] = useScopeValue<DocId | null>(
|
||||
'editor.open_doc_id'
|
||||
)
|
||||
const [, setOpenDocName] = useScopeValue<string | null>(
|
||||
@@ -418,7 +419,7 @@ export const EditorManagerProvider: FC = ({ children }) => {
|
||||
}
|
||||
|
||||
// We're now either opening a new document or reloading a broken one.
|
||||
setOpenDocId(doc._id)
|
||||
setOpenDocId(doc._id as DocId)
|
||||
setOpenDocName(doc.name)
|
||||
setOpening(true)
|
||||
|
||||
|
||||
+406
-30
@@ -1,6 +1,9 @@
|
||||
import { useState, useEffect, useMemo, useCallback, useRef } from 'react'
|
||||
import { isEqual, cloneDeep } from 'lodash'
|
||||
import useScopeValue from '../../../../../shared/hooks/use-scope-value'
|
||||
import useSocketListener from '@/features/ide-react/hooks/use-socket-listener'
|
||||
import useAsync from '@/shared/hooks/use-async'
|
||||
import useAbortController from '@/shared/hooks/use-abort-controller'
|
||||
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'
|
||||
@@ -9,19 +12,49 @@ 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 { 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,
|
||||
ReviewPanelCommentThreads,
|
||||
ReviewPanelDocEntries,
|
||||
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'
|
||||
import {
|
||||
DeepReadonly,
|
||||
MergeAndOverride,
|
||||
} from '../../../../../../../types/utils'
|
||||
import { ReviewPanelCommentThread } from '../../../../../../../types/review-panel/comment-thread'
|
||||
import { DocId } from '../../../../../../../types/project-settings'
|
||||
import {
|
||||
ReviewPanelAggregateChangeEntry,
|
||||
ReviewPanelChangeEntry,
|
||||
ReviewPanelCommentEntry,
|
||||
ReviewPanelEntry,
|
||||
} from '../../../../../../../types/review-panel/entry'
|
||||
import {
|
||||
ReviewPanelCommentThreadMessageApi,
|
||||
ReviewPanelCommentThreadsApi,
|
||||
} from '../../../../../../../types/review-panel/api'
|
||||
import { Document } from '@/features/ide-react/editor/document'
|
||||
|
||||
function formatUser(user: any): any {
|
||||
const dispatchReviewPanelEvent = (type: string, payload?: any) => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('review-panel:event', {
|
||||
detail: { type, payload },
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const formatUser = (user: any): any => {
|
||||
let isSelf, name
|
||||
const id =
|
||||
(user != null ? user._id : undefined) ||
|
||||
@@ -62,6 +95,15 @@ function formatUser(user: any): any {
|
||||
}
|
||||
}
|
||||
|
||||
const formatComment = (
|
||||
comment: ReviewPanelCommentThreadMessageApi
|
||||
): ReviewPanelCommentThreadMessage => {
|
||||
const commentTyped = comment as unknown as ReviewPanelCommentThreadMessage
|
||||
commentTyped.user = formatUser(comment.user)
|
||||
commentTyped.timestamp = new Date(comment.timestamp)
|
||||
return commentTyped
|
||||
}
|
||||
|
||||
function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||
const { reviewPanelOpen, setReviewPanelOpen } = useLayoutContext()
|
||||
const { projectId } = useIdeReactContext()
|
||||
@@ -71,10 +113,14 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||
const {
|
||||
features: { trackChangesVisible, trackChanges },
|
||||
} = project
|
||||
const { isRestrictedTokenMember } = useEditorContext()
|
||||
|
||||
const [subView, setSubView] = useScopeValue<ReviewPanel.Value<'subView'>>(
|
||||
'reviewPanel.subView'
|
||||
)
|
||||
// TODO `currentDocument` and `currentDocumentId` should be get from `useEditorManagerContext()` but that makes tests fail
|
||||
const [currentDocument] = useScopeValue<Document>('editor.sharejs_doc')
|
||||
const [currentDocumentId] = useScopeValue<DocId>('editor.open_doc_id')
|
||||
|
||||
const [subView, setSubView] =
|
||||
useState<ReviewPanel.Value<'subView'>>('cur_file')
|
||||
const [loading] = useScopeValue<ReviewPanel.Value<'loading'>>(
|
||||
'reviewPanel.overview.loading'
|
||||
)
|
||||
@@ -84,29 +130,25 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||
const [collapsed, setCollapsed] = useScopeValue<
|
||||
ReviewPanel.Value<'collapsed'>
|
||||
>('reviewPanel.overview.docsCollapsedState')
|
||||
const [commentThreads] = useScopeValue<ReviewPanel.Value<'commentThreads'>>(
|
||||
'reviewPanel.commentThreads',
|
||||
true
|
||||
)
|
||||
const [entries] = useScopeValue<ReviewPanel.Value<'entries'>>(
|
||||
'reviewPanel.entries',
|
||||
true
|
||||
)
|
||||
const [loadingThreads] =
|
||||
useScopeValue<ReviewPanel.Value<'loadingThreads'>>('loadingThreads')
|
||||
const [commentThreads, setCommentThreads] = useState<
|
||||
ReviewPanel.Value<'commentThreads'>
|
||||
>({})
|
||||
const [entries, setEntries] = useState<ReviewPanel.Value<'entries'>>({})
|
||||
|
||||
const [permissions] =
|
||||
useScopeValue<ReviewPanel.Value<'permissions'>>('permissions')
|
||||
const [users] = useScopeValue<ReviewPanel.Value<'users'>>('users', true)
|
||||
const [resolvedComments] = useScopeValue<
|
||||
const [users, setUsers] = useScopeValue<ReviewPanel.Value<'users'>>(
|
||||
'users',
|
||||
true
|
||||
)
|
||||
const [resolvedComments, setResolvedComments] = useState<
|
||||
ReviewPanel.Value<'resolvedComments'>
|
||||
>('reviewPanel.resolvedComments', true)
|
||||
>({})
|
||||
|
||||
const [wantTrackChanges, setWantTrackChanges] = useScopeValue<
|
||||
ReviewPanel.Value<'wantTrackChanges'>
|
||||
>('editor.wantTrackChanges')
|
||||
const [openDocId] =
|
||||
useScopeValue<ReviewPanel.Value<'openDocId'>>('editor.open_doc_id')
|
||||
const openDocId = currentDocumentId
|
||||
const [shouldCollapse, setShouldCollapse] =
|
||||
useState<ReviewPanel.Value<'shouldCollapse'>>(true)
|
||||
const [lineHeight] = useScopeValue<number>(
|
||||
@@ -126,6 +168,325 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||
const [trackChangesForGuestsAvailable, setTrackChangesForGuestsAvailable] =
|
||||
useState<ReviewPanel.Value<'trackChangesForGuestsAvailable'>>(false)
|
||||
|
||||
const [resolvedThreadIds, setResolvedThreadIds] = useState<
|
||||
Record<ThreadId, boolean>
|
||||
>({})
|
||||
|
||||
const {
|
||||
isLoading: loadingThreads,
|
||||
reset,
|
||||
runAsync: runAsyncThreads,
|
||||
} = useAsync<ReviewPanelCommentThreadsApi>()
|
||||
const loadThreadsController = useAbortController()
|
||||
const loadThreadsExecuted = useRef(false)
|
||||
const ensureThreadsAreLoaded = useCallback(() => {
|
||||
if (loadThreadsExecuted.current) {
|
||||
// We get any updates in real time so only need to load them once.
|
||||
return
|
||||
}
|
||||
loadThreadsExecuted.current = true
|
||||
|
||||
return runAsyncThreads(
|
||||
getJSON(`/project/${projectId}/threads`, {
|
||||
signal: loadThreadsController.signal,
|
||||
})
|
||||
)
|
||||
.then(threads => {
|
||||
const tempResolvedThreadIds: typeof resolvedThreadIds = {}
|
||||
const threadsEntries = Object.entries(threads) as [
|
||||
[
|
||||
ThreadId,
|
||||
MergeAndOverride<
|
||||
ReviewPanelCommentThread,
|
||||
ReviewPanelCommentThreadsApi[ThreadId]
|
||||
>
|
||||
]
|
||||
]
|
||||
for (const [threadId, thread] of threadsEntries) {
|
||||
for (const comment of thread.messages) {
|
||||
formatComment(comment)
|
||||
}
|
||||
if (thread.resolved_by_user) {
|
||||
thread.resolved_by_user = formatUser(thread.resolved_by_user)
|
||||
tempResolvedThreadIds[threadId] = true
|
||||
}
|
||||
}
|
||||
setResolvedThreadIds(tempResolvedThreadIds)
|
||||
setCommentThreads(threads as unknown as ReviewPanelCommentThreads)
|
||||
|
||||
dispatchReviewPanelEvent('loaded_threads')
|
||||
handleLayoutChange({ async: true })
|
||||
|
||||
return {
|
||||
resolvedThreadIds: tempResolvedThreadIds,
|
||||
commentThreads: threads,
|
||||
}
|
||||
})
|
||||
.catch(debugConsole.error)
|
||||
}, [loadThreadsController.signal, projectId, runAsyncThreads])
|
||||
|
||||
const rangesTrackers = useRef<Record<DocId, RangesTracker>>({})
|
||||
const refreshingRangeUsers = useRef(false)
|
||||
const refreshedForUserIds = useRef(new Set<UserId>())
|
||||
const refreshChangeUsers = useCallback(
|
||||
(userId: UserId | null) => {
|
||||
if (userId != null) {
|
||||
if (refreshedForUserIds.current.has(userId)) {
|
||||
// We've already tried to refresh to get this user id, so stop it looping
|
||||
return
|
||||
}
|
||||
refreshedForUserIds.current.add(userId)
|
||||
}
|
||||
|
||||
// Only do one refresh at once
|
||||
if (refreshingRangeUsers.current) {
|
||||
return
|
||||
}
|
||||
refreshingRangeUsers.current = true
|
||||
|
||||
getJSON(`/project/${projectId}/changes/users`)
|
||||
.then(usersResponse => {
|
||||
refreshingRangeUsers.current = false
|
||||
const tempUsers = {} as ReviewPanel.Value<'users'>
|
||||
// Always include ourself, since if we submit an op, we might need to display info
|
||||
// about it locally before it has been flushed through the server
|
||||
if (user) {
|
||||
tempUsers[user.id] = formatUser(user)
|
||||
}
|
||||
|
||||
for (const user of usersResponse) {
|
||||
if (user.id) {
|
||||
tempUsers[user.id] = formatUser(user)
|
||||
}
|
||||
}
|
||||
|
||||
setUsers(tempUsers)
|
||||
})
|
||||
.catch(error => {
|
||||
refreshingRangeUsers.current = false
|
||||
debugConsole.error(error)
|
||||
})
|
||||
},
|
||||
[projectId, setUsers, user]
|
||||
)
|
||||
|
||||
const getChangeTracker = useCallback(
|
||||
(docId: DocId) => {
|
||||
if (!rangesTrackers.current[docId]) {
|
||||
rangesTrackers.current[docId] = new RangesTracker() as RangesTracker
|
||||
rangesTrackers.current[docId].resolvedThreadIds = {
|
||||
...resolvedThreadIds,
|
||||
}
|
||||
}
|
||||
return rangesTrackers.current[docId]
|
||||
},
|
||||
[resolvedThreadIds]
|
||||
)
|
||||
|
||||
const getDocEntries = useCallback(
|
||||
(docId: DocId) => {
|
||||
return entries[docId] ?? ({} as ReviewPanelDocEntries)
|
||||
},
|
||||
[entries]
|
||||
)
|
||||
|
||||
const getDocResolvedComments = useCallback(
|
||||
(docId: DocId) => {
|
||||
return resolvedComments[docId] ?? ({} as ReviewPanelDocEntries)
|
||||
},
|
||||
[resolvedComments]
|
||||
)
|
||||
|
||||
const updateEntries = useCallback(
|
||||
async (docId: DocId) => {
|
||||
const rangesTracker = getChangeTracker(docId)
|
||||
let localResolvedThreadIds = resolvedThreadIds
|
||||
|
||||
if (!isRestrictedTokenMember) {
|
||||
if (rangesTracker.comments.length > 0) {
|
||||
const threadsLoadResult = await ensureThreadsAreLoaded()
|
||||
if (typeof threadsLoadResult === 'object') {
|
||||
localResolvedThreadIds = threadsLoadResult.resolvedThreadIds
|
||||
}
|
||||
} else if (loadingThreads) {
|
||||
// ensure that tracked changes are highlighted even if no comments are loaded
|
||||
reset()
|
||||
dispatchReviewPanelEvent('loaded_threads')
|
||||
}
|
||||
}
|
||||
|
||||
const docEntries = cloneDeep(getDocEntries(docId))
|
||||
const docResolvedComments = cloneDeep(getDocResolvedComments(docId))
|
||||
// Assume we'll delete everything until we see it, then we'll remove it from this object
|
||||
const deleteChanges = new Set<keyof ReviewPanelDocEntries>()
|
||||
|
||||
for (const [id, change] of Object.entries(docEntries)) {
|
||||
if (
|
||||
'entry_ids' in change &&
|
||||
id !== 'add-comment' &&
|
||||
id !== 'bulk-actions'
|
||||
) {
|
||||
for (const entryId of change.entry_ids) {
|
||||
deleteChanges.add(entryId)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [, change] of Object.entries(docResolvedComments)) {
|
||||
if ('entry_ids' in change) {
|
||||
for (const entryId of change.entry_ids) {
|
||||
deleteChanges.add(entryId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let potentialAggregate = false
|
||||
let prevInsertion = null
|
||||
|
||||
for (const change of rangesTracker.changes as any[]) {
|
||||
if (
|
||||
potentialAggregate &&
|
||||
change.op.d &&
|
||||
change.op.p === prevInsertion.op.p + prevInsertion.op.i.length &&
|
||||
change.metadata.user_id === prevInsertion.metadata.user_id
|
||||
) {
|
||||
// An actual aggregate op.
|
||||
const aggregateChangeEntries = docEntries as Record<
|
||||
string,
|
||||
ReviewPanelAggregateChangeEntry
|
||||
>
|
||||
aggregateChangeEntries[prevInsertion.id].type = 'aggregate-change'
|
||||
aggregateChangeEntries[prevInsertion.id].metadata.replaced_content =
|
||||
change.op.d
|
||||
aggregateChangeEntries[prevInsertion.id].entry_ids.push(change.id)
|
||||
} else {
|
||||
if (docEntries[change.id] == null) {
|
||||
docEntries[change.id] = {} as ReviewPanelEntry
|
||||
}
|
||||
deleteChanges.delete(change.id)
|
||||
const newEntry: Partial<ReviewPanelChangeEntry> = {
|
||||
type: change.op.i ? 'insert' : 'delete',
|
||||
entry_ids: [change.id],
|
||||
content: change.op.i || change.op.d,
|
||||
offset: change.op.p,
|
||||
metadata: change.metadata,
|
||||
}
|
||||
const newEntryEntries = Object.entries(newEntry) as [
|
||||
[keyof typeof newEntry, typeof newEntry[keyof typeof newEntry]]
|
||||
]
|
||||
for (const [key, value] of newEntryEntries) {
|
||||
const entriesTyped = docEntries[change.id] as Record<any, any>
|
||||
entriesTyped[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (change.op.i) {
|
||||
potentialAggregate = true
|
||||
prevInsertion = change
|
||||
} else {
|
||||
potentialAggregate = false
|
||||
prevInsertion = null
|
||||
}
|
||||
|
||||
if (!users[change.metadata.user_id]) {
|
||||
if (!isRestrictedTokenMember) {
|
||||
refreshChangeUsers(change.metadata.user_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const comment of rangesTracker.comments) {
|
||||
deleteChanges.delete(comment.id)
|
||||
|
||||
const newEntry: Partial<ReviewPanelCommentEntry> = {
|
||||
type: 'comment',
|
||||
thread_id: comment.op.t,
|
||||
entry_ids: [comment.id],
|
||||
content: comment.op.c,
|
||||
offset: comment.op.p,
|
||||
}
|
||||
const newEntryEntries = Object.entries(newEntry) as [
|
||||
[keyof typeof newEntry, typeof newEntry[keyof typeof newEntry]]
|
||||
]
|
||||
|
||||
let newComment: any
|
||||
if (localResolvedThreadIds[comment.op.t]) {
|
||||
docResolvedComments[comment.id] ??= {} as ReviewPanelCommentEntry
|
||||
newComment = docResolvedComments[comment.id]
|
||||
delete docEntries[comment.id]
|
||||
} else {
|
||||
docEntries[comment.id] ??= {} as ReviewPanelEntry
|
||||
newComment = docEntries[comment.id]
|
||||
delete docResolvedComments[comment.id]
|
||||
}
|
||||
|
||||
for (const [key, value] of newEntryEntries) {
|
||||
newComment[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
deleteChanges.forEach(changeId => {
|
||||
delete docEntries[changeId]
|
||||
delete docResolvedComments[changeId]
|
||||
})
|
||||
|
||||
setEntries(prev => {
|
||||
return isEqual(prev[docId], docEntries)
|
||||
? prev
|
||||
: { ...prev, [docId]: docEntries }
|
||||
})
|
||||
setResolvedComments(prev => {
|
||||
return isEqual(prev[docId], docResolvedComments)
|
||||
? prev
|
||||
: { ...prev, [docId]: docResolvedComments }
|
||||
})
|
||||
|
||||
return docEntries
|
||||
},
|
||||
[
|
||||
getChangeTracker,
|
||||
getDocEntries,
|
||||
getDocResolvedComments,
|
||||
isRestrictedTokenMember,
|
||||
refreshChangeUsers,
|
||||
resolvedThreadIds,
|
||||
users,
|
||||
ensureThreadsAreLoaded,
|
||||
loadingThreads,
|
||||
reset,
|
||||
]
|
||||
)
|
||||
|
||||
const regenerateTrackChangesId = useCallback(
|
||||
(doc: typeof currentDocument) => {
|
||||
const currentChangeTracker = getChangeTracker(doc.doc_id as DocId)
|
||||
const oldId = currentChangeTracker.getIdSeed()
|
||||
const newId = RangesTracker.generateIdSeed()
|
||||
currentChangeTracker.setIdSeed(newId)
|
||||
doc.setTrackChangesIdSeeds({ pending: newId, inflight: oldId })
|
||||
},
|
||||
[getChangeTracker]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentDocument) {
|
||||
return
|
||||
}
|
||||
// The open doc range tracker is kept up to date in real-time so
|
||||
// replace any outdated info with this
|
||||
rangesTrackers.current[currentDocument.doc_id as DocId] =
|
||||
currentDocument.ranges
|
||||
rangesTrackers.current[currentDocument.doc_id as DocId].resolvedThreadIds =
|
||||
{ ...resolvedThreadIds }
|
||||
currentDocument.on('flipped_pending_to_inflight', () =>
|
||||
regenerateTrackChangesId(currentDocument)
|
||||
)
|
||||
regenerateTrackChangesId(currentDocument)
|
||||
|
||||
return () => {
|
||||
currentDocument.off('flipped_pending_to_inflight')
|
||||
}
|
||||
}, [currentDocument, regenerateTrackChangesId, resolvedThreadIds])
|
||||
|
||||
const currentUserType = useCallback((): 'member' | 'guest' | 'anonymous' => {
|
||||
if (!user) {
|
||||
return 'anonymous'
|
||||
@@ -387,6 +748,7 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||
const projectJoinedEffectExecuted = useRef(false)
|
||||
useEffect(() => {
|
||||
if (!projectJoinedEffectExecuted.current) {
|
||||
projectJoinedEffectExecuted.current = true
|
||||
requestAnimationFrame(() => {
|
||||
if (trackChanges) {
|
||||
applyTrackChangesStateToClient(project.trackChangesState)
|
||||
@@ -395,7 +757,6 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||
}
|
||||
setGuestFeatureBasedOnProjectAccessLevel(project.publicAccessLevel)
|
||||
})
|
||||
projectJoinedEffectExecuted.current = true
|
||||
}
|
||||
}, [
|
||||
applyTrackChangesStateToClient,
|
||||
@@ -489,13 +850,10 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||
'bulkRejectActions'
|
||||
)
|
||||
|
||||
const handleSetSubview = useCallback(
|
||||
(subView: SubView) => {
|
||||
setSubView(subView)
|
||||
sendMB('rp-subview-change', { subView })
|
||||
},
|
||||
[setSubView]
|
||||
)
|
||||
const handleSetSubview = useCallback((subView: SubView) => {
|
||||
setSubView(subView)
|
||||
sendMB('rp-subview-change', { subView })
|
||||
}, [])
|
||||
|
||||
const submitReply = useCallback(
|
||||
(threadId: ThreadId, replyContent: string) => {
|
||||
@@ -523,11 +881,27 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||
}
|
||||
}
|
||||
|
||||
const editorTrackChangesChanged = async () => {
|
||||
const entries = await updateEntries(currentDocumentId)
|
||||
dispatchReviewPanelEvent('recalculate-screen-positions', {
|
||||
entries,
|
||||
updateType: 'trackedChangesChange',
|
||||
})
|
||||
// Ensure that watchers, such as the React-based review panel component,
|
||||
// are informed of the changes to entries
|
||||
handleLayoutChange()
|
||||
}
|
||||
|
||||
const handleEditorEvents = (e: Event) => {
|
||||
const event = e as CustomEvent
|
||||
const { type } = event.detail
|
||||
|
||||
switch (type) {
|
||||
case 'track-changes:changed': {
|
||||
editorTrackChangesChanged()
|
||||
break
|
||||
}
|
||||
|
||||
case 'toggle-track-changes': {
|
||||
toggleTrackChangesFromKbdShortcut()
|
||||
break
|
||||
@@ -541,10 +915,12 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||
window.removeEventListener('editor:event', handleEditorEvents)
|
||||
}
|
||||
}, [
|
||||
currentDocumentId,
|
||||
toggleTrackChangesForUser,
|
||||
trackChanges,
|
||||
trackChangesState,
|
||||
trackChangesVisible,
|
||||
updateEntries,
|
||||
user.id,
|
||||
])
|
||||
|
||||
|
||||
+1
-5
@@ -2,12 +2,8 @@ import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/rea
|
||||
|
||||
export default function populateReviewPanelScope(store: ReactScopeValueStore) {
|
||||
store.set('reviewPanel.overview.docsCollapsedState', {})
|
||||
store.set('reviewPanel.subView', 'cur_file')
|
||||
store.set('reviewPanel.overview.loading', false)
|
||||
store.set('reviewPanel.nVisibleSelectedChanges', 0)
|
||||
store.set('reviewPanel.commentThreads', {})
|
||||
store.set('reviewPanel.entries', {})
|
||||
store.set('loadingThreads', true)
|
||||
store.set('permissions', {
|
||||
read: false,
|
||||
write: false,
|
||||
@@ -15,7 +11,7 @@ export default function populateReviewPanelScope(store: ReactScopeValueStore) {
|
||||
comment: false,
|
||||
})
|
||||
store.set('users', {})
|
||||
store.set('reviewPanel.resolvedComments', {})
|
||||
store.set('reviewPanel.layoutToLeft', false)
|
||||
store.set('reviewPanel.rendererData.lineHeight', 0)
|
||||
store.set('resolveComment', () => {})
|
||||
store.set('submitNewComment', async () => {})
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ReviewPanelCommentThreadMessage, ThreadId } from './review-panel'
|
||||
import { MergeAndOverride } from '../utils'
|
||||
|
||||
export type ReviewPanelCommentThreadMessageApi = MergeAndOverride<
|
||||
ReviewPanelCommentThreadMessage,
|
||||
{ timestamp: number }
|
||||
>
|
||||
|
||||
export type ReviewPanelCommentThreadsApi = Record<
|
||||
ThreadId,
|
||||
{
|
||||
messages: ReviewPanelCommentThreadMessageApi[]
|
||||
}
|
||||
>
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
import { UserId } from '../user'
|
||||
import { DateString } from '../helpers/date'
|
||||
|
||||
interface ReviewPanelCommentThreadBase {
|
||||
export interface ReviewPanelCommentThreadBase {
|
||||
messages: Array<ReviewPanelCommentThreadMessage>
|
||||
submitting?: boolean // angular specific (to be made into a local state)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export type CommentId = Brand<string, 'CommentId'>
|
||||
export interface ReviewPanelCommentThreadMessage {
|
||||
content: string
|
||||
id: CommentId
|
||||
timestamp: number
|
||||
timestamp: Date
|
||||
user: ReviewPanelUser
|
||||
user_id: UserId
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user