mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-02 13:00:18 -05:00
2304536844
* Tidy up review panel components * Add ReviewPanel providers * [web] new design for review panel track change (#19544) * [web] new design for review panel track change * fixed mini view * mini icon style change * fix icon size * format date * useRangesUserContext hook * remove useRangesUserContext hook * using full class names * fix action icons hover * change wording for tooltips * added ReviewPanelChangeUser component * Update header in new review panel * Extract ReviewPanelTrackChangesMenuButton as a separate component * Remove wrapper div * Replace h2 with div for review panel label * Rename ReviewPanelTools to ReviewPanelHeader * Rename trackChangesExpanded -> trackChangesMenuExpanded * Dont break memoisation of ReviewPanelTrackChangesMenuButton * Fix the width of the track changes arrow icon * Update how prop types are declared * Remove new empty state from old review panel * Add empty state to new review panel * Add project members and owner to ChangesUsers context (#19624) --------- Co-authored-by: Alf Eaton <alf.eaton@overleaf.com> * Redesign comment entry in review panel (#19678) * Redesign comment entry in review panel * ReviewPanelCommentOptions component * remove unused prop * Tidying * Add conditional import * Optional changeManager * Add more split test compatibility * More split test compatibility * Fixes * Improve overview scrolling * Fix overview scrolling * Fix & simplify track changes toggle * Fix overview scrolling * Fix current file container * ExpandableContent component for messages in review panel (#19738) * ExpandableContent component for messages in review panel * remove isExpanded dependancy * Delete comment option for new review panel (#19772) * Delete comment option for new review panel * dont show thread warning if there are no replies * fix hasReplies issue * Implement initial collapsing overview files * Fix positioning of overview panel * Small styling changes * Add count of unresolved comments and tracked chanegs * More style adjustments * Move review-panel-overview styles into css file * Remove unused var --------- Co-authored-by: Domagoj Kriskovic <dom.kriskovic@overleaf.com> Co-authored-by: David Powell <david.powell@overleaf.com> Co-authored-by: David <33458145+davidmcpowell@users.noreply.github.com> GitOrigin-RevId: e67463443d541f88445a86eed5e2b6ec6040f9c7
291 lines
7.7 KiB
TypeScript
291 lines
7.7 KiB
TypeScript
import {
|
|
createContext,
|
|
FC,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
} from 'react'
|
|
import { useProjectContext } from '@/shared/context/project-context'
|
|
import {
|
|
CommentId,
|
|
ThreadId,
|
|
} from '../../../../../types/review-panel/review-panel'
|
|
import { ReviewPanelCommentThread } from '../../../../../types/review-panel/comment-thread'
|
|
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
|
|
import useSocketListener from '@/features/ide-react/hooks/use-socket-listener'
|
|
import { ReviewPanelCommentThreadMessageApi } from '../../../../../types/review-panel/api'
|
|
import { UserId } from '../../../../../types/user'
|
|
import { deleteJSON, getJSON, postJSON } from '@/infrastructure/fetch-json'
|
|
import RangesTracker from '@overleaf/ranges-tracker'
|
|
import { CommentOperation } from '../../../../../types/change'
|
|
import useScopeValue from '@/shared/hooks/use-scope-value'
|
|
import { DocumentContainer } from '@/features/ide-react/editor/document-container'
|
|
|
|
export type Threads = Record<ThreadId, ReviewPanelCommentThread>
|
|
|
|
export const ThreadsContext = createContext<Threads | undefined>(undefined)
|
|
|
|
type ThreadsActions = {
|
|
addComment: (pos: number, text: string, content: string) => Promise<void>
|
|
resolveThread: (threadId: ThreadId) => Promise<void>
|
|
reopenThread: (threadId: ThreadId) => Promise<void>
|
|
deleteThread: (threadId: ThreadId) => Promise<void>
|
|
addMessage: (threadId: ThreadId, content: string) => Promise<void>
|
|
editMessage: (
|
|
threadId: ThreadId,
|
|
commentId: CommentId,
|
|
content: string
|
|
) => Promise<void>
|
|
deleteMessage: (threadId: ThreadId, commentId: CommentId) => Promise<void>
|
|
}
|
|
|
|
const ThreadsActionsContext = createContext<ThreadsActions | undefined>(
|
|
undefined
|
|
)
|
|
|
|
export const ThreadsProvider: FC = ({ children }) => {
|
|
const { _id: projectId } = useProjectContext()
|
|
|
|
const [currentDoc] = useScopeValue<DocumentContainer | null>(
|
|
'editor.sharejs_doc'
|
|
)
|
|
|
|
// const [error, setError] = useState<Error>()
|
|
const [data, setData] = useState<Threads>()
|
|
|
|
// load the initial threads data
|
|
useEffect(() => {
|
|
const abortController = new AbortController()
|
|
|
|
getJSON(`/project/${projectId}/threads`, {
|
|
signal: abortController.signal,
|
|
}).then(data => {
|
|
setData(data)
|
|
})
|
|
// .catch(error => {
|
|
// setError(error)
|
|
// })
|
|
}, [projectId])
|
|
|
|
const { socket } = useConnectionContext()
|
|
|
|
useSocketListener(
|
|
socket,
|
|
'new-comment',
|
|
useCallback(
|
|
(threadId: ThreadId, comment: ReviewPanelCommentThreadMessageApi) => {
|
|
setData(value => {
|
|
if (value) {
|
|
const { submitting, ...thread } = value[threadId] ?? {
|
|
messages: [],
|
|
}
|
|
|
|
return {
|
|
...value,
|
|
[threadId]: {
|
|
...thread,
|
|
messages: [
|
|
...thread.messages,
|
|
{
|
|
...comment,
|
|
user: comment.user, // TODO
|
|
timestamp: new Date(comment.timestamp),
|
|
},
|
|
],
|
|
},
|
|
}
|
|
}
|
|
})
|
|
},
|
|
[]
|
|
)
|
|
)
|
|
|
|
useSocketListener(
|
|
socket,
|
|
'edit-message',
|
|
useCallback((threadId: ThreadId, commentId: CommentId, content: string) => {
|
|
setData(value => {
|
|
if (value) {
|
|
const thread = value[threadId] ?? { messages: [] }
|
|
|
|
return {
|
|
...value,
|
|
[threadId]: {
|
|
...thread,
|
|
messages: thread.messages.map(message =>
|
|
message.id === commentId ? { ...message, content } : message
|
|
),
|
|
},
|
|
}
|
|
}
|
|
})
|
|
}, [])
|
|
)
|
|
|
|
useSocketListener(
|
|
socket,
|
|
'delete-message',
|
|
useCallback((threadId: ThreadId, commentId: CommentId) => {
|
|
setData(value => {
|
|
if (value) {
|
|
const thread = value[threadId] ?? { messages: [] }
|
|
|
|
return {
|
|
...value,
|
|
[threadId]: {
|
|
...thread,
|
|
messages: thread.messages.filter(
|
|
message => message.id !== commentId
|
|
),
|
|
},
|
|
}
|
|
}
|
|
})
|
|
}, [])
|
|
)
|
|
|
|
useSocketListener(
|
|
socket,
|
|
'resolve-thread',
|
|
useCallback(
|
|
(
|
|
threadId: ThreadId,
|
|
user: { email: string; first_name: string; id: UserId }
|
|
) => {
|
|
setData(value => {
|
|
if (value) {
|
|
const thread = value[threadId] ?? { messages: [] }
|
|
|
|
return {
|
|
...value,
|
|
[threadId]: {
|
|
...thread,
|
|
resolved: true,
|
|
resolved_by_user: user, // TODO
|
|
resolved_at: new Date().toISOString(),
|
|
},
|
|
}
|
|
}
|
|
})
|
|
},
|
|
[]
|
|
)
|
|
)
|
|
|
|
useSocketListener(
|
|
socket,
|
|
'reopen-thread',
|
|
useCallback((threadId: ThreadId) => {
|
|
setData(value => {
|
|
if (value) {
|
|
const thread = value[threadId] ?? { messages: [] }
|
|
|
|
return {
|
|
...value,
|
|
[threadId]: {
|
|
...thread,
|
|
resolved: undefined,
|
|
resolved_by_user: undefined,
|
|
resolved_at: undefined,
|
|
},
|
|
}
|
|
}
|
|
})
|
|
}, [])
|
|
)
|
|
|
|
useSocketListener(
|
|
socket,
|
|
'delete-thread',
|
|
useCallback((threadId: ThreadId) => {
|
|
setData(value => {
|
|
if (value) {
|
|
const _value = { ...value }
|
|
delete _value[threadId]
|
|
return _value
|
|
}
|
|
})
|
|
}, [])
|
|
)
|
|
|
|
const actions = useMemo(
|
|
() => ({
|
|
async addComment(pos: number, text: string, content: string) {
|
|
const threadId = RangesTracker.generateId() as ThreadId
|
|
|
|
await postJSON(`/project/${projectId}/thread/${threadId}/messages`, {
|
|
body: { content },
|
|
})
|
|
|
|
const op: CommentOperation = {
|
|
c: text,
|
|
p: pos,
|
|
t: threadId,
|
|
}
|
|
|
|
currentDoc?.submitOp(op)
|
|
},
|
|
async resolveThread(threadId: string) {
|
|
await postJSON(
|
|
`/project/${projectId}/doc/${currentDoc?.doc_id}/thread/${threadId}/resolve`
|
|
)
|
|
},
|
|
async reopenThread(threadId: string) {
|
|
await postJSON(
|
|
`/project/${projectId}/doc/${currentDoc?.doc_id}/thread/${threadId}/reopen`
|
|
)
|
|
},
|
|
async deleteThread(threadId: string) {
|
|
await deleteJSON(
|
|
`/project/${projectId}/doc/${currentDoc?.doc_id}/thread/${threadId}`
|
|
)
|
|
currentDoc?.ranges?.removeCommentId(threadId)
|
|
},
|
|
async addMessage(threadId: ThreadId, content: string) {
|
|
await postJSON(`/project/${projectId}/thread/${threadId}/messages`, {
|
|
body: { content },
|
|
})
|
|
// TODO: error_submitting_comment
|
|
},
|
|
async editMessage(
|
|
threadId: ThreadId,
|
|
commentId: CommentId,
|
|
content: string
|
|
) {
|
|
await postJSON(
|
|
`/project/${projectId}/thread/${threadId}/messages/${commentId}/edit`,
|
|
{ body: { content } }
|
|
)
|
|
},
|
|
async deleteMessage(threadId: ThreadId, commentId: CommentId) {
|
|
await deleteJSON(
|
|
`/project/${projectId}/thread/${threadId}/messages/${commentId}`
|
|
)
|
|
},
|
|
}),
|
|
[currentDoc, projectId]
|
|
)
|
|
|
|
return (
|
|
<ThreadsActionsContext.Provider value={actions}>
|
|
<ThreadsContext.Provider value={data}>{children}</ThreadsContext.Provider>
|
|
</ThreadsActionsContext.Provider>
|
|
)
|
|
}
|
|
|
|
export const useThreadsContext = () => {
|
|
return useContext(ThreadsContext)
|
|
}
|
|
|
|
export const useThreadsActionsContext = () => {
|
|
const context = useContext(ThreadsActionsContext)
|
|
if (!context) {
|
|
throw new Error(
|
|
'useThreadsActionsContext is only available inside ThreadsProvider'
|
|
)
|
|
}
|
|
return context
|
|
}
|