mirror of
https://github.com/overleaf/overleaf.git
synced 2024-10-24 21:12:38 -04:00
a323f3af75
* Implement floating Add comment button * Fix comment typo * Remove unused imports * Make tooltip always appear above cursor Co-authored-by: Domagoj Kriskovic <dom.kriskovic@overleaf.com> * Refactor how new comment form is positioned * Add missing file * Create new map when rendering positions * Use codemirror state to manage ranges and allow for mutliple in-progress comments * Memoise sorting * Create new ranges map each time it is changed * Add back mutation observer * Only allow single tooltip * Fix typo * Convert state field to store a single tooltip * Make add comment tooltip content a react component * Refactor to remove usages of !important * Use RangeSet to keep track of new comment ranges * Fix logic broken in rebase * Map ranges through document changes * Add decorations for in-progress comments * Use set-review-panel-open rather than an editor event to open review panel * Implement new designs for add comment form * Add padding to textarea * Fix bug where comment was being submitted for incorrect range * Add missing key to ReviewPanelAddComment * Store new comment ranges as a DecorationSet * Small refactor to how ReviewPanelAddCommens are rendered * Make op prop to ReviewPanelEntry required * Add handling for disabling of add comemnt form buttons * Move viewer check inside AddCommentTooltip * Ensure that add comment button doesn't reshow when collaborators edit the document * Remove unneeded op check in ReviewPanelEntry * Update services/web/frontend/js/features/review-panel-new/components/review-panel-add-comment.tsx Co-authored-by: Domagoj Kriskovic <dom.kriskovic@overleaf.com> --------- Co-authored-by: Domagoj Kriskovic <dom.kriskovic@overleaf.com> GitOrigin-RevId: 3110845f6a557310f3bf72014689e2f2ab53e966
238 lines
6.5 KiB
TypeScript
238 lines
6.5 KiB
TypeScript
import {
|
|
createContext,
|
|
useContext,
|
|
useCallback,
|
|
useMemo,
|
|
useEffect,
|
|
Dispatch,
|
|
SetStateAction,
|
|
FC,
|
|
} from 'react'
|
|
import useScopeValue from '../hooks/use-scope-value'
|
|
import useDetachLayout from '../hooks/use-detach-layout'
|
|
import localStorage from '../../infrastructure/local-storage'
|
|
import getMeta from '../../utils/meta'
|
|
import { DetachRole } from './detach-context'
|
|
import { debugConsole } from '@/utils/debugging'
|
|
import { BinaryFile } from '@/features/file-view/types/binary-file'
|
|
import useScopeEventEmitter from '@/shared/hooks/use-scope-event-emitter'
|
|
import useEventListener from '../hooks/use-event-listener'
|
|
|
|
export type IdeLayout = 'sideBySide' | 'flat'
|
|
export type IdeView = 'editor' | 'file' | 'pdf' | 'history'
|
|
|
|
type LayoutContextValue = {
|
|
reattach: () => void
|
|
detach: () => void
|
|
detachIsLinked: boolean
|
|
detachRole: DetachRole
|
|
changeLayout: (newLayout: IdeLayout, newView?: IdeView) => void
|
|
view: IdeView | null
|
|
setView: (view: IdeView | null) => void
|
|
chatIsOpen: boolean
|
|
setChatIsOpen: Dispatch<SetStateAction<LayoutContextValue['chatIsOpen']>>
|
|
reviewPanelOpen: boolean
|
|
setReviewPanelOpen: Dispatch<
|
|
SetStateAction<LayoutContextValue['reviewPanelOpen']>
|
|
>
|
|
miniReviewPanelVisible: boolean
|
|
setMiniReviewPanelVisible: Dispatch<
|
|
SetStateAction<LayoutContextValue['miniReviewPanelVisible']>
|
|
>
|
|
leftMenuShown: boolean
|
|
setLeftMenuShown: Dispatch<
|
|
SetStateAction<LayoutContextValue['leftMenuShown']>
|
|
>
|
|
loadingStyleSheet: boolean
|
|
setLoadingStyleSheet: Dispatch<
|
|
SetStateAction<LayoutContextValue['loadingStyleSheet']>
|
|
>
|
|
pdfLayout: IdeLayout
|
|
pdfPreviewOpen: boolean
|
|
}
|
|
|
|
const debugPdfDetach = getMeta('ol-debugPdfDetach')
|
|
|
|
export const LayoutContext = createContext<LayoutContextValue | undefined>(
|
|
undefined
|
|
)
|
|
|
|
function setLayoutInLocalStorage(pdfLayout: IdeLayout) {
|
|
localStorage.setItem(
|
|
'pdf.layout',
|
|
pdfLayout === 'sideBySide' ? 'split' : 'flat'
|
|
)
|
|
}
|
|
|
|
export const LayoutProvider: FC = ({ children }) => {
|
|
// what to show in the "flat" view (editor or pdf)
|
|
const [view, _setView] = useScopeValue<IdeView | null>('ui.view')
|
|
const [openFile] = useScopeValue<BinaryFile | null>('openFile')
|
|
const historyToggleEmitter = useScopeEventEmitter('history:toggle', true)
|
|
|
|
const setView = useCallback(
|
|
(value: IdeView | null) => {
|
|
_setView(oldValue => {
|
|
// ensure that the "history:toggle" event is broadcast when switching in or out of history view
|
|
if (value === 'history' || oldValue === 'history') {
|
|
historyToggleEmitter()
|
|
}
|
|
|
|
if (value === 'editor' && openFile) {
|
|
// if a file is currently opened, ensure the view is 'file' instead of
|
|
// 'editor' when the 'editor' view is requested. This is to ensure
|
|
// that the entity selected in the file tree is the one visible and
|
|
// that docs don't take precedence over files.
|
|
return 'file'
|
|
}
|
|
|
|
return value
|
|
})
|
|
},
|
|
[_setView, openFile, historyToggleEmitter]
|
|
)
|
|
|
|
// whether the chat pane is open
|
|
const [chatIsOpen, setChatIsOpen] = useScopeValue<boolean>('ui.chatOpen')
|
|
|
|
// whether the review pane is open
|
|
const [reviewPanelOpen, setReviewPanelOpen] =
|
|
useScopeValue('ui.reviewPanelOpen')
|
|
|
|
// whether the review pane is collapsed
|
|
const [miniReviewPanelVisible, setMiniReviewPanelVisible] =
|
|
useScopeValue<boolean>('ui.miniReviewPanelVisible')
|
|
|
|
// whether the menu pane is open
|
|
const [leftMenuShown, setLeftMenuShown] =
|
|
useScopeValue<boolean>('ui.leftMenuShown')
|
|
|
|
// whether to display the editor and preview side-by-side or full-width ("flat")
|
|
const [pdfLayout, setPdfLayout] = useScopeValue<IdeLayout>('ui.pdfLayout')
|
|
|
|
// whether stylesheet on theme is loading
|
|
const [loadingStyleSheet, setLoadingStyleSheet] = useScopeValue<boolean>(
|
|
'ui.loadingStyleSheet'
|
|
)
|
|
|
|
const changeLayout = useCallback(
|
|
(newLayout: IdeLayout, newView: IdeView = 'editor') => {
|
|
setPdfLayout(newLayout)
|
|
setView(newLayout === 'sideBySide' ? 'editor' : newView)
|
|
setLayoutInLocalStorage(newLayout)
|
|
},
|
|
[setPdfLayout, setView]
|
|
)
|
|
|
|
const {
|
|
reattach,
|
|
detach,
|
|
isLinking: detachIsLinking,
|
|
isLinked: detachIsLinked,
|
|
role: detachRole,
|
|
isRedundant: detachIsRedundant,
|
|
} = useDetachLayout()
|
|
|
|
const pdfPreviewOpen =
|
|
pdfLayout === 'sideBySide' || view === 'pdf' || detachRole === 'detacher'
|
|
|
|
useEffect(() => {
|
|
if (debugPdfDetach) {
|
|
debugConsole.warn('Layout Effect', {
|
|
detachIsRedundant,
|
|
detachRole,
|
|
detachIsLinking,
|
|
detachIsLinked,
|
|
})
|
|
}
|
|
|
|
if (detachRole !== 'detacher') return // not in a PDF detacher layout
|
|
|
|
if (detachIsRedundant) {
|
|
changeLayout('sideBySide')
|
|
return
|
|
}
|
|
|
|
if (detachIsLinking || detachIsLinked) {
|
|
// the tab is linked to a detached tab (or about to be linked); show
|
|
// editor only
|
|
changeLayout('flat', 'editor')
|
|
}
|
|
}, [
|
|
detachIsRedundant,
|
|
detachRole,
|
|
detachIsLinking,
|
|
detachIsLinked,
|
|
changeLayout,
|
|
])
|
|
|
|
const handleSetReviewPanelOpenEvent = useCallback(
|
|
(e: Event) => {
|
|
const event = e as CustomEvent<{ isOpen: boolean }>
|
|
const { isOpen } = event.detail
|
|
setReviewPanelOpen(isOpen)
|
|
},
|
|
[setReviewPanelOpen]
|
|
)
|
|
|
|
useEventListener('set-review-panel-open', handleSetReviewPanelOpenEvent)
|
|
|
|
const value = useMemo<LayoutContextValue>(
|
|
() => ({
|
|
reattach,
|
|
detach,
|
|
detachIsLinked,
|
|
detachRole,
|
|
changeLayout,
|
|
chatIsOpen,
|
|
leftMenuShown,
|
|
pdfLayout,
|
|
pdfPreviewOpen,
|
|
reviewPanelOpen,
|
|
miniReviewPanelVisible,
|
|
loadingStyleSheet,
|
|
setChatIsOpen,
|
|
setLeftMenuShown,
|
|
setPdfLayout,
|
|
setReviewPanelOpen,
|
|
setMiniReviewPanelVisible,
|
|
setLoadingStyleSheet,
|
|
setView,
|
|
view,
|
|
}),
|
|
[
|
|
reattach,
|
|
detach,
|
|
detachIsLinked,
|
|
detachRole,
|
|
changeLayout,
|
|
chatIsOpen,
|
|
leftMenuShown,
|
|
pdfLayout,
|
|
pdfPreviewOpen,
|
|
reviewPanelOpen,
|
|
miniReviewPanelVisible,
|
|
loadingStyleSheet,
|
|
setChatIsOpen,
|
|
setLeftMenuShown,
|
|
setPdfLayout,
|
|
setReviewPanelOpen,
|
|
setMiniReviewPanelVisible,
|
|
setLoadingStyleSheet,
|
|
setView,
|
|
view,
|
|
]
|
|
)
|
|
|
|
return (
|
|
<LayoutContext.Provider value={value}>{children}</LayoutContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useLayoutContext() {
|
|
const context = useContext(LayoutContext)
|
|
if (!context) {
|
|
throw new Error('useLayoutContext is only available inside LayoutProvider')
|
|
}
|
|
return context
|
|
}
|