mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
19a17060ab
GitOrigin-RevId: e4fafddd67909a0e709439ddbe560c66e2629bdb
213 lines
5.6 KiB
TypeScript
213 lines
5.6 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 { debugConsole } from '@/utils/debugging'
|
|
import { BinaryFile } from '@/features/file-view/types/binary-file'
|
|
import useScopeEventEmitter from '@/shared/hooks/use-scope-event-emitter'
|
|
|
|
export type IdeLayout = 'sideBySide' | 'flat'
|
|
export type IdeView = 'editor' | 'file' | 'pdf' | 'history'
|
|
|
|
type LayoutContextValue = {
|
|
reattach: () => void
|
|
detach: () => void
|
|
detachIsLinked: boolean
|
|
detachRole: 'detacher' | 'detached' | null
|
|
changeLayout: (newLayout: IdeLayout, newView?: IdeView) => void
|
|
view: IdeView
|
|
setView: (view: IdeView) => void
|
|
chatIsOpen: boolean
|
|
setChatIsOpen: Dispatch<SetStateAction<LayoutContextValue['chatIsOpen']>>
|
|
reviewPanelOpen: boolean
|
|
setReviewPanelOpen: Dispatch<
|
|
SetStateAction<LayoutContextValue['reviewPanelOpen']>
|
|
>
|
|
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>('ui.view')
|
|
const [openFile] = useScopeValue<BinaryFile | null>('openFile')
|
|
const historyToggleEmitter = useScopeEventEmitter('history:toggle', true)
|
|
|
|
const setView = useCallback(
|
|
(value: IdeView) => {
|
|
_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 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 value = useMemo<LayoutContextValue>(
|
|
() => ({
|
|
reattach,
|
|
detach,
|
|
detachIsLinked,
|
|
detachRole,
|
|
changeLayout,
|
|
chatIsOpen,
|
|
leftMenuShown,
|
|
pdfLayout,
|
|
pdfPreviewOpen,
|
|
reviewPanelOpen,
|
|
loadingStyleSheet,
|
|
setChatIsOpen,
|
|
setLeftMenuShown,
|
|
setPdfLayout,
|
|
setReviewPanelOpen,
|
|
setLoadingStyleSheet,
|
|
setView,
|
|
view,
|
|
}),
|
|
[
|
|
reattach,
|
|
detach,
|
|
detachIsLinked,
|
|
detachRole,
|
|
changeLayout,
|
|
chatIsOpen,
|
|
leftMenuShown,
|
|
pdfLayout,
|
|
pdfPreviewOpen,
|
|
reviewPanelOpen,
|
|
loadingStyleSheet,
|
|
setChatIsOpen,
|
|
setLeftMenuShown,
|
|
setPdfLayout,
|
|
setReviewPanelOpen,
|
|
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
|
|
}
|