Merge pull request #15397 from overleaf/ii-ide-page-prototype-review-panel

Init review panel for React IDE page

GitOrigin-RevId: fc23201055ae892c5c1d5cb88e472a0bb0cd6c25
This commit is contained in:
ilkin-overleaf 2023-11-02 13:18:12 +02:00 committed by Copybot
parent 9eb8743014
commit 0c403bf8e3
9 changed files with 440 additions and 8 deletions

View file

@ -9,6 +9,7 @@ import React, {
} from 'react'
import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store'
import populateLayoutScope from '@/features/ide-react/scope-adapters/layout-context-adapter'
import populateReviewPanelScope from '@/features/ide-react/scope-adapters/review-panel-context-adapter'
import { IdeProvider } from '@/shared/context/ide-context'
import {
createIdeEventEmitter,
@ -22,8 +23,8 @@ 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'
import { populateOnlineUsersScope } from '@/features/ide-react/context/online-users-context'
import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter'
import { populateReferenceScope } from '@/features/ide-react/context/references-context'
import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter'
type IdeReactContextValue = {
projectId: string
@ -75,6 +76,7 @@ function createReactScopeValueStore() {
populateOnlineUsersScope(scopeStore)
populateReferenceScope(scopeStore)
populateFileTreeScope(scopeStore)
populateReviewPanelScope(scopeStore)
scopeStore.allowNonExistentPath('hasLintingError')
scopeStore.allowNonExistentPath('loadingThreads')

View file

@ -0,0 +1,273 @@
import { useState, useMemo, useCallback } from 'react'
import useScopeValue from '../../../../../shared/hooks/use-scope-value'
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 { ReviewPanelStateReactIde } from '../types/review-panel-state'
import * as ReviewPanel from '../types/review-panel-state'
import {
SubView,
ThreadId,
} from '../../../../../../../types/review-panel/review-panel'
function useReviewPanelState(): ReviewPanelStateReactIde {
const { reviewPanelOpen, setReviewPanelOpen } = useLayoutContext()
const {
features: { trackChangesVisible },
} = useProjectContext()
const [subView, setSubView] = useScopeValue<ReviewPanel.Value<'subView'>>(
'reviewPanel.subView'
)
const [loading] = useScopeValue<ReviewPanel.Value<'loading'>>(
'reviewPanel.overview.loading'
)
const [nVisibleSelectedChanges] = useScopeValue<
ReviewPanel.Value<'nVisibleSelectedChanges'>
>('reviewPanel.nVisibleSelectedChanges')
const [collapsed, setCollapsed] = useScopeValue<
ReviewPanel.Value<'collapsed'>
>('reviewPanel.overview.docsCollapsedState')
const [commentThreads] = useScopeValue<ReviewPanel.Value<'commentThreads'>>(
'reviewPanel.commentThreads',
true
)
const [docs] = useScopeValue<ReviewPanel.Value<'docs'>>('docs')
const [entries] = useScopeValue<ReviewPanel.Value<'entries'>>(
'reviewPanel.entries',
true
)
const [loadingThreads] =
useScopeValue<ReviewPanel.Value<'loadingThreads'>>('loadingThreads')
const [permissions] =
useScopeValue<ReviewPanel.Value<'permissions'>>('permissions')
const [users] = useScopeValue<ReviewPanel.Value<'users'>>('users', true)
const [resolvedComments] = useScopeValue<
ReviewPanel.Value<'resolvedComments'>
>('reviewPanel.resolvedComments', true)
const [wantTrackChanges] = useScopeValue<
ReviewPanel.Value<'wantTrackChanges'>
>('editor.wantTrackChanges')
const [openDocId] =
useScopeValue<ReviewPanel.Value<'openDocId'>>('editor.open_doc_id')
const [shouldCollapse, setShouldCollapse] = useScopeValue<
ReviewPanel.Value<'shouldCollapse'>
>('reviewPanel.fullTCStateCollapsed')
const [lineHeight] = useScopeValue<number>(
'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<
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 [resolveComment] =
useScopeValue<ReviewPanel.UpdaterFn<'resolveComment'>>('resolveComment')
const [submitNewComment] =
useScopeValue<ReviewPanel.UpdaterFn<'submitNewComment'>>('submitNewComment')
const [deleteComment] =
useScopeValue<ReviewPanel.UpdaterFn<'deleteComment'>>('deleteComment')
const [gotoEntry] =
useScopeValue<ReviewPanel.UpdaterFn<'gotoEntry'>>('gotoEntry')
const [saveEdit] =
useScopeValue<ReviewPanel.UpdaterFn<'saveEdit'>>('saveEdit')
const [submitReplyAngular] =
useScopeValue<
(entry: { thread_id: ThreadId; replyContent: string }) => void
>('submitReply')
const [formattedProjectMembers] = useScopeValue<
ReviewPanel.Value<'formattedProjectMembers'>
>('reviewPanel.formattedProjectMembers')
const toggleReviewPanel = useCallback(() => {
if (!trackChangesVisible) {
return
}
setReviewPanelOpen(value => !value)
sendMB('rp-toggle-panel', {
value: reviewPanelOpen,
})
}, [reviewPanelOpen, setReviewPanelOpen, trackChangesVisible])
const [unresolveComment] =
useScopeValue<ReviewPanel.UpdaterFn<'unresolveComment'>>('unresolveComment')
const [deleteThread] =
useScopeValue<ReviewPanel.UpdaterFn<'deleteThread'>>('deleteThread')
const [refreshResolvedCommentsDropdown] = useScopeValue<
ReviewPanel.UpdaterFn<'refreshResolvedCommentsDropdown'>
>('refreshResolvedCommentsDropdown')
const [acceptChanges] =
useScopeValue<ReviewPanel.UpdaterFn<'acceptChanges'>>('acceptChanges')
const [rejectChanges] =
useScopeValue<ReviewPanel.UpdaterFn<'rejectChanges'>>('rejectChanges')
const [bulkAcceptActions] =
useScopeValue<ReviewPanel.UpdaterFn<'bulkAcceptActions'>>(
'bulkAcceptActions'
)
const [bulkRejectActions] =
useScopeValue<ReviewPanel.UpdaterFn<'bulkRejectActions'>>(
'bulkRejectActions'
)
const handleSetSubview = useCallback(
(subView: SubView) => {
setSubView(subView)
sendMB('rp-subview-change', { subView })
},
[setSubView]
)
const submitReply = useCallback(
(threadId: ThreadId, replyContent: string) => {
submitReplyAngular({ thread_id: threadId, replyContent })
},
[submitReplyAngular]
)
const [entryHover, setEntryHover] = useState(false)
const [isAddingComment, setIsAddingComment] = useState(false)
const [navHeight, setNavHeight] = useState(0)
const [toolbarHeight, setToolbarHeight] = useState(0)
const [layoutSuspended, setLayoutSuspended] = useState(false)
const values = useMemo<ReviewPanelStateReactIde['values']>(
() => ({
collapsed,
commentThreads,
docs,
entries,
entryHover,
isAddingComment,
loadingThreads,
nVisibleSelectedChanges,
permissions,
users,
resolvedComments,
shouldCollapse,
navHeight,
toolbarHeight,
subView,
wantTrackChanges,
loading,
openDocId,
lineHeight,
trackChangesState,
trackChangesOnForEveryone,
trackChangesOnForGuests,
trackChangesForGuestsAvailable,
formattedProjectMembers,
layoutSuspended,
}),
[
collapsed,
commentThreads,
docs,
entries,
entryHover,
isAddingComment,
loadingThreads,
nVisibleSelectedChanges,
permissions,
users,
resolvedComments,
shouldCollapse,
navHeight,
toolbarHeight,
subView,
wantTrackChanges,
loading,
openDocId,
lineHeight,
trackChangesState,
trackChangesOnForEveryone,
trackChangesOnForGuests,
trackChangesForGuestsAvailable,
formattedProjectMembers,
layoutSuspended,
]
)
const updaterFns = useMemo<ReviewPanelStateReactIde['updaterFns']>(
() => ({
handleSetSubview,
handleLayoutChange,
gotoEntry,
resolveComment,
submitReply,
acceptChanges,
rejectChanges,
toggleReviewPanel,
bulkAcceptActions,
bulkRejectActions,
saveEdit,
submitNewComment,
deleteComment,
unresolveComment,
refreshResolvedCommentsDropdown,
deleteThread,
toggleTrackChangesForEveryone,
toggleTrackChangesForUser,
toggleTrackChangesForGuests,
setEntryHover,
setCollapsed,
setShouldCollapse,
setIsAddingComment,
setNavHeight,
setToolbarHeight,
setLayoutSuspended,
}),
[
handleSetSubview,
gotoEntry,
resolveComment,
submitReply,
acceptChanges,
rejectChanges,
toggleReviewPanel,
bulkAcceptActions,
bulkRejectActions,
saveEdit,
submitNewComment,
deleteComment,
unresolveComment,
refreshResolvedCommentsDropdown,
deleteThread,
toggleTrackChangesForEveryone,
toggleTrackChangesForUser,
toggleTrackChangesForGuests,
setCollapsed,
setEntryHover,
setShouldCollapse,
setIsAddingComment,
setNavHeight,
setToolbarHeight,
setLayoutSuspended,
]
)
return { values, updaterFns }
}
export default useReviewPanelState

View file

@ -0,0 +1,43 @@
import { useContext, 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'
export const ReviewPanelReactIdeValueContext = createContext<
ReviewPanelStateReactIde['values'] | undefined
>(undefined)
export const ReviewPanelReactIdeUpdaterFnsContext = createContext<
ReviewPanelStateReactIde['updaterFns'] | undefined
>(undefined)
export const ReviewPanelReactIdeProvider: React.FC = ({ children }) => {
const { values, updaterFns } = useReviewPanelState()
return (
<ReviewPanelReactIdeValueContext.Provider value={values}>
<ReviewPanelReactIdeUpdaterFnsContext.Provider value={updaterFns}>
{children}
</ReviewPanelReactIdeUpdaterFnsContext.Provider>
</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
}

View file

@ -0,0 +1,11 @@
import { ReviewPanelState } from '@/features/source-editor/context/review-panel/types/review-panel-state'
export interface ReviewPanelStateReactIde extends ReviewPanelState {}
// Getter for values
export type Value<T extends keyof ReviewPanelStateReactIde['values']> =
ReviewPanelStateReactIde['values'][T]
// Getter for stable functions
export type UpdaterFn<T extends keyof ReviewPanelStateReactIde['updaterFns']> =
ReviewPanelStateReactIde['updaterFns'][T]

View file

@ -0,0 +1,47 @@
import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store'
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('docs', undefined)
store.set('reviewPanel.entries', {})
store.set('loadingThreads', true)
store.set('permissions', {
read: false,
write: false,
admin: false,
comment: false,
})
store.set('users', {})
store.set('reviewPanel.resolvedComments', {})
store.set('editor.wantTrackChanges', false)
store.set('editor.open_doc_id', null)
store.set('reviewPanel.fullTCStateCollapsed', true)
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 () => {})
store.set('acceptChanges', () => {})
store.set('rejectChanges', () => {})
store.set('bulkAcceptActions', () => {})
store.set('bulkRejectActions', () => {})
store.set('submitReply', () => {})
store.set('addNewComment', () => {})
}

View file

@ -64,7 +64,7 @@ function CodeMirrorEditor() {
<CodeMirrorSearch />
<CodeMirrorToolbar />
<CodeMirrorCommandTooltip />
{isReviewPanelReact && !isReactIde && <ReviewPanel />}
{(isReviewPanelReact || isReactIde) && <ReviewPanel />}
{sourceEditorComponents.map(
({ import: { default: Component }, path }) => (
<Component key={path} />

View file

@ -7,16 +7,22 @@ import {
ReviewPanelProvider,
useReviewPanelValueContext,
} from '../../context/review-panel/review-panel-context'
import { ReviewPanelReactIdeProvider } from '@/features/ide-react/context/review-panel/review-panel-context'
import { isCurrentFileView } from '../../utils/sub-view'
import { useLayoutContext } from '@/shared/context/layout-context'
import { useIdeContext } from '@/shared/context/ide-context'
import classnames from 'classnames'
type ReviewPanelViewProps = {
parentDomNode: Element
}
function ReviewPanelView({ parentDomNode }: ReviewPanelViewProps) {
const { subView } = useReviewPanelValueContext()
const { subView, loadingThreads } = useReviewPanelValueContext()
const { reviewPanelOpen } = useLayoutContext()
const { isReactIde } = useIdeContext()
return ReactDOM.createPortal(
const content = (
<>
<EditorWidgets />
{isCurrentFileView(subView) ? (
@ -24,15 +30,43 @@ function ReviewPanelView({ parentDomNode }: ReviewPanelViewProps) {
) : (
<OverviewContainer />
)}
</>,
</>
)
return ReactDOM.createPortal(
isReactIde ? (
<div
className={classnames('review-panel', {
'rp-state-current-file': subView === 'cur_file',
'rp-state-current-file-expanded':
subView === 'cur_file' && reviewPanelOpen,
'rp-state-current-file-mini':
subView === 'cur_file' && !reviewPanelOpen,
'rp-state-overview': subView === 'overview',
// 'rp-size-mini': ui.miniReviewPanelVisible,
'rp-size-expanded': reviewPanelOpen,
// 'rp-layout-left': reviewPanel.layoutToLeft,
'rp-loading-threads': loadingThreads,
})}
>
{content}
</div>
) : (
content
),
parentDomNode
)
}
function ReviewPanel() {
const view = useCodeMirrorViewContext()
const { isReactIde } = useIdeContext()
return (
return isReactIde ? (
<ReviewPanelReactIdeProvider>
<ReviewPanelView parentDomNode={view.scrollDOM} />
</ReviewPanelReactIdeProvider>
) : (
<ReviewPanelProvider>
<ReviewPanelView parentDomNode={view.scrollDOM} />
</ReviewPanelProvider>

View file

@ -1,5 +1,10 @@
import { createContext, useContext } from 'react'
import useAngularReviewPanelState from './hooks/use-angular-review-panel-state'
import {
ReviewPanelReactIdeUpdaterFnsContext,
ReviewPanelReactIdeValueContext,
} from '@/features/ide-react/context/review-panel/review-panel-context'
import { useIdeContext } from '@/shared/context/ide-context'
import { ReviewPanelState } from './types/review-panel-state'
const ReviewPanelValueContext = createContext<
@ -27,7 +32,11 @@ export function ReviewPanelProvider({ children }: ReviewPanelProviderProps) {
}
export function useReviewPanelValueContext() {
const context = useContext(ReviewPanelValueContext)
const contextAngularIde = useContext(ReviewPanelValueContext)
const contextReactIde = useContext(ReviewPanelReactIdeValueContext)
const { isReactIde } = useIdeContext()
const context = isReactIde ? contextReactIde : contextAngularIde
if (!context) {
throw new Error(
'ReviewPanelValueContext is only available inside ReviewPanelProvider'
@ -37,7 +46,11 @@ export function useReviewPanelValueContext() {
}
export function useReviewPanelUpdaterFnsContext() {
const context = useContext(ReviewPanelUpdaterFnsContext)
const contextAngularIde = useContext(ReviewPanelUpdaterFnsContext)
const contextReactIde = useContext(ReviewPanelReactIdeUpdaterFnsContext)
const { isReactIde } = useIdeContext()
const context = isReactIde ? contextReactIde : contextAngularIde
if (!context) {
throw new Error(
'ReviewPanelUpdaterFnsContext is only available inside ReviewPanelProvider'

View file

@ -12,6 +12,15 @@
position: relative;
height: 100%;
}
.review-panel {
height: 100%;
}
.rp-state-overview {
position: sticky;
top: 0;
}
}
.ide-react-main {