mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Migrate layout context to TypeScript (#15275)
Migrate layout context to TypeScript GitOrigin-RevId: ce453bfb67f7c36176fa24144413b556cd3c117e
This commit is contained in:
parent
ec085a0807
commit
3be937c503
10 changed files with 80 additions and 109 deletions
|
@ -18,7 +18,7 @@ const MessageList = lazy(() => import('./message-list'))
|
||||||
const ChatPane = React.memo(function ChatPane() {
|
const ChatPane = React.memo(function ChatPane() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { chatIsOpen } = useLayoutContext({ chatIsOpen: PropTypes.bool })
|
const { chatIsOpen } = useLayoutContext()
|
||||||
const user = useUserContext({
|
const user = useUserContext({
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
})
|
})
|
||||||
|
|
|
@ -136,7 +136,7 @@ export function ChatProvider({ children }) {
|
||||||
_id: PropTypes.string.isRequired,
|
_id: PropTypes.string.isRequired,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { chatIsOpen } = useLayoutContext({ chatIsOpen: PropTypes.bool })
|
const { chatIsOpen } = useLayoutContext()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
hasFocus: windowHasFocus,
|
hasFocus: windowHasFocus,
|
||||||
|
|
|
@ -12,9 +12,7 @@ export default function SettingsOverallTheme() {
|
||||||
const overallThemes = getMeta('ol-overallThemes') as
|
const overallThemes = getMeta('ol-overallThemes') as
|
||||||
| OverallThemeMeta[]
|
| OverallThemeMeta[]
|
||||||
| undefined
|
| undefined
|
||||||
const { loadingStyleSheet } = useLayoutContext() as {
|
const { loadingStyleSheet } = useLayoutContext()
|
||||||
loadingStyleSheet: boolean
|
|
||||||
}
|
|
||||||
const { overallTheme, setOverallTheme } = useProjectSettingsContext()
|
const { overallTheme, setOverallTheme } = useProjectSettingsContext()
|
||||||
|
|
||||||
const options: Array<Option<OverallTheme>> = useMemo(
|
const options: Array<Option<OverallTheme>> = useMemo(
|
||||||
|
|
|
@ -23,17 +23,6 @@ const editorContextPropTypes = {
|
||||||
permissionsLevel: PropTypes.string,
|
permissionsLevel: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const layoutContextPropTypes = {
|
|
||||||
chatIsOpen: PropTypes.bool,
|
|
||||||
setChatIsOpen: PropTypes.func.isRequired,
|
|
||||||
reviewPanelOpen: PropTypes.bool,
|
|
||||||
setReviewPanelOpen: PropTypes.func.isRequired,
|
|
||||||
view: PropTypes.string,
|
|
||||||
setView: PropTypes.func.isRequired,
|
|
||||||
setLeftMenuShown: PropTypes.func.isRequired,
|
|
||||||
pdfLayout: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
const chatContextPropTypes = {
|
const chatContextPropTypes = {
|
||||||
markMessagesAsRead: PropTypes.func.isRequired,
|
markMessagesAsRead: PropTypes.func.isRequired,
|
||||||
unreadMessageCount: PropTypes.number.isRequired,
|
unreadMessageCount: PropTypes.number.isRequired,
|
||||||
|
@ -70,7 +59,7 @@ const EditorNavigationToolbarRoot = React.memo(
|
||||||
view,
|
view,
|
||||||
setView,
|
setView,
|
||||||
setLeftMenuShown,
|
setLeftMenuShown,
|
||||||
} = useLayoutContext(layoutContextPropTypes)
|
} = useLayoutContext()
|
||||||
|
|
||||||
const { markMessagesAsRead, unreadMessageCount } =
|
const { markMessagesAsRead, unreadMessageCount } =
|
||||||
useChatContext(chatContextPropTypes)
|
useChatContext(chatContextPropTypes)
|
||||||
|
|
|
@ -107,7 +107,7 @@ function LayoutDropdownButton() {
|
||||||
changeLayout,
|
changeLayout,
|
||||||
view,
|
view,
|
||||||
pdfLayout,
|
pdfLayout,
|
||||||
} = useLayoutContext(layoutContextPropTypes)
|
} = useLayoutContext()
|
||||||
|
|
||||||
const handleDetach = useCallback(() => {
|
const handleDetach = useCallback(() => {
|
||||||
detach()
|
detach()
|
||||||
|
@ -250,13 +250,3 @@ IconCheckmark.propTypes = {
|
||||||
view: PropTypes.string,
|
view: PropTypes.string,
|
||||||
detachRole: PropTypes.string,
|
detachRole: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const layoutContextPropTypes = {
|
|
||||||
reattach: PropTypes.func.isRequired,
|
|
||||||
detach: PropTypes.func.isRequired,
|
|
||||||
changeLayout: PropTypes.func.isRequired,
|
|
||||||
detachIsLinked: PropTypes.bool,
|
|
||||||
detachRole: PropTypes.string,
|
|
||||||
pdfLayout: PropTypes.string.isRequired,
|
|
||||||
view: PropTypes.string,
|
|
||||||
}
|
|
||||||
|
|
|
@ -211,7 +211,7 @@ const editorContextPropTypes = {
|
||||||
const isMac = /Mac/.test(window.navigator?.platform)
|
const isMac = /Mac/.test(window.navigator?.platform)
|
||||||
|
|
||||||
export function useSelectableEntity(id, isFile) {
|
export function useSelectableEntity(id, isFile) {
|
||||||
const { view, setView } = useLayoutContext(layoutContextPropTypes)
|
const { view, setView } = useLayoutContext()
|
||||||
const { setContextMenuCoords } = useFileTreeMainContext()
|
const { setContextMenuCoords } = useFileTreeMainContext()
|
||||||
const {
|
const {
|
||||||
selectedEntityIds,
|
selectedEntityIds,
|
||||||
|
@ -288,11 +288,6 @@ export function useSelectableEntity(id, isFile) {
|
||||||
return { isSelected, props }
|
return { isSelected, props }
|
||||||
}
|
}
|
||||||
|
|
||||||
const layoutContextPropTypes = {
|
|
||||||
view: PropTypes.string,
|
|
||||||
setView: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useFileTreeSelectable() {
|
export function useFileTreeSelectable() {
|
||||||
const context = useContext(FileTreeSelectableContext)
|
const context = useContext(FileTreeSelectableContext)
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,9 @@ type ConnectionContextValue = {
|
||||||
registerUserActivity: () => void
|
registerUserActivity: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConnectionContext = createContext<ConnectionContextValue | null>(null)
|
const ConnectionContext = createContext<ConnectionContextValue | undefined>(
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
|
||||||
export const ConnectionProvider: FC = ({ children }) => {
|
export const ConnectionProvider: FC = ({ children }) => {
|
||||||
const [connectionManager] = useState(() => new ConnectionManager())
|
const [connectionManager] = useState(() => new ConnectionManager())
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { useLayoutContext } from '../../../shared/context/layout-context'
|
import { useLayoutContext } from '../../../shared/context/layout-context'
|
||||||
import PdfSynctexControls from './pdf-synctex-controls'
|
import PdfSynctexControls from './pdf-synctex-controls'
|
||||||
|
|
||||||
export function DefaultSynctexControl() {
|
export function DefaultSynctexControl() {
|
||||||
const { detachRole } = useLayoutContext(layoutContextPropTypes)
|
const { detachRole } = useLayoutContext()
|
||||||
if (!detachRole) {
|
if (!detachRole) {
|
||||||
return <PdfSynctexControls />
|
return <PdfSynctexControls />
|
||||||
}
|
}
|
||||||
|
@ -11,9 +10,7 @@ export function DefaultSynctexControl() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DetacherSynctexControl() {
|
export function DetacherSynctexControl() {
|
||||||
const { detachRole, detachIsLinked } = useLayoutContext(
|
const { detachRole, detachIsLinked } = useLayoutContext()
|
||||||
layoutContextPropTypes
|
|
||||||
)
|
|
||||||
if (detachRole === 'detacher' && detachIsLinked) {
|
if (detachRole === 'detacher' && detachIsLinked) {
|
||||||
return <PdfSynctexControls />
|
return <PdfSynctexControls />
|
||||||
}
|
}
|
||||||
|
@ -21,16 +18,9 @@ export function DetacherSynctexControl() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DetachedSynctexControl() {
|
export function DetachedSynctexControl() {
|
||||||
const { detachRole, detachIsLinked } = useLayoutContext(
|
const { detachRole, detachIsLinked } = useLayoutContext()
|
||||||
layoutContextPropTypes
|
|
||||||
)
|
|
||||||
if (detachRole === 'detached' && detachIsLinked) {
|
if (detachRole === 'detached' && detachIsLinked) {
|
||||||
return <PdfSynctexControls />
|
return <PdfSynctexControls />
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const layoutContextPropTypes = {
|
|
||||||
detachRole: PropTypes.string,
|
|
||||||
detachIsLinked: PropTypes.bool,
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,104 +4,108 @@ import {
|
||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
Dispatch,
|
||||||
|
SetStateAction,
|
||||||
|
FC,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import useScopeValue from '../hooks/use-scope-value'
|
import useScopeValue from '../hooks/use-scope-value'
|
||||||
import useDetachLayout from '../hooks/use-detach-layout'
|
import useDetachLayout from '../hooks/use-detach-layout'
|
||||||
import { useIdeContext } from './ide-context'
|
|
||||||
import localStorage from '../../infrastructure/local-storage'
|
import localStorage from '../../infrastructure/local-storage'
|
||||||
import getMeta from '../../utils/meta'
|
import getMeta from '../../utils/meta'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
import { BinaryFile } from '@/features/file-view/types/binary-file'
|
||||||
|
|
||||||
|
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')
|
const debugPdfDetach = getMeta('ol-debugPdfDetach')
|
||||||
|
|
||||||
export const LayoutContext = createContext()
|
export const LayoutContext = createContext<LayoutContextValue | undefined>(
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
|
||||||
LayoutContext.Provider.propTypes = {
|
function setLayoutInLocalStorage(pdfLayout: IdeLayout) {
|
||||||
value: PropTypes.shape({
|
|
||||||
reattach: PropTypes.func.isRequired,
|
|
||||||
detach: PropTypes.func.isRequired,
|
|
||||||
detachIsLinked: PropTypes.bool,
|
|
||||||
detachRole: PropTypes.string,
|
|
||||||
changeLayout: PropTypes.func.isRequired,
|
|
||||||
view: PropTypes.oneOf(['editor', 'file', 'pdf', 'history']),
|
|
||||||
setView: PropTypes.func.isRequired,
|
|
||||||
chatIsOpen: PropTypes.bool,
|
|
||||||
setChatIsOpen: PropTypes.func.isRequired,
|
|
||||||
reviewPanelOpen: PropTypes.bool,
|
|
||||||
setReviewPanelOpen: PropTypes.func.isRequired,
|
|
||||||
leftMenuShown: PropTypes.bool,
|
|
||||||
setLeftMenuShown: PropTypes.func.isRequired,
|
|
||||||
pdfLayout: PropTypes.oneOf(['sideBySide', 'flat']).isRequired,
|
|
||||||
pdfPreviewOpen: PropTypes.bool,
|
|
||||||
}).isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLayoutInLocalStorage(pdfLayout) {
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'pdf.layout',
|
'pdf.layout',
|
||||||
pdfLayout === 'sideBySide' ? 'split' : 'flat'
|
pdfLayout === 'sideBySide' ? 'split' : 'flat'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LayoutProvider({ children }) {
|
export const LayoutProvider: FC = ({ children }) => {
|
||||||
const { $scope } = useIdeContext()
|
|
||||||
|
|
||||||
// what to show in the "flat" view (editor or pdf)
|
// what to show in the "flat" view (editor or pdf)
|
||||||
const [view, _setView] = useScopeValue('ui.view')
|
const [view, _setView] = useScopeValue<IdeView>('ui.view')
|
||||||
|
const [toggleHistory] = useScopeValue<() => void>('toggleHistory')
|
||||||
|
const [openFile] = useScopeValue<BinaryFile | null>('openFile')
|
||||||
|
|
||||||
const setView = useCallback(
|
const setView = useCallback(
|
||||||
value => {
|
(value: IdeView) => {
|
||||||
_setView(oldValue => {
|
_setView(oldValue => {
|
||||||
// ensure that the "history:toggle" event is broadcast when switching in or out of history view
|
// ensure that the "history:toggle" event is broadcast when switching in or out of history view
|
||||||
if (value === 'history' || oldValue === 'history') {
|
if (value === 'history' || oldValue === 'history') {
|
||||||
$scope.toggleHistory()
|
toggleHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value === 'editor' && $scope.openFile) {
|
if (value === 'editor' && openFile) {
|
||||||
// if a file is currently opened, ensure the view is 'file' instead of
|
// if a file is currently opened, ensure the view is 'file' instead of
|
||||||
// 'editor' when the 'editor' view is requested. This is to ensure
|
// '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 the entity selected in the file tree is the one visible and
|
||||||
// that docs don't take precendence over files.
|
// that docs don't take precedence over files.
|
||||||
return 'file'
|
return 'file'
|
||||||
}
|
}
|
||||||
|
|
||||||
return value
|
return value
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[$scope, _setView]
|
[_setView, openFile, toggleHistory]
|
||||||
)
|
)
|
||||||
|
|
||||||
// whether the chat pane is open
|
// whether the chat pane is open
|
||||||
const [chatIsOpen, setChatIsOpen] = useScopeValue('ui.chatOpen')
|
const [chatIsOpen, setChatIsOpen] = useScopeValue<boolean>('ui.chatOpen')
|
||||||
|
|
||||||
// whether the review pane is open
|
// whether the review pane is open
|
||||||
const [reviewPanelOpen, setReviewPanelOpen] =
|
const [reviewPanelOpen, setReviewPanelOpen] =
|
||||||
useScopeValue('ui.reviewPanelOpen')
|
useScopeValue('ui.reviewPanelOpen')
|
||||||
|
|
||||||
// whether the menu pane is open
|
// whether the menu pane is open
|
||||||
const [leftMenuShown, setLeftMenuShown] = useScopeValue('ui.leftMenuShown')
|
const [leftMenuShown, setLeftMenuShown] =
|
||||||
|
useScopeValue<boolean>('ui.leftMenuShown')
|
||||||
|
|
||||||
// whether to display the editor and preview side-by-side or full-width ("flat")
|
// whether to display the editor and preview side-by-side or full-width ("flat")
|
||||||
const [pdfLayout, setPdfLayout] = useScopeValue('ui.pdfLayout')
|
const [pdfLayout, setPdfLayout] = useScopeValue<IdeLayout>('ui.pdfLayout')
|
||||||
|
|
||||||
// whether stylesheet on theme is loading
|
// whether stylesheet on theme is loading
|
||||||
const [loadingStyleSheet, setLoadingStyleSheet] = useScopeValue(
|
const [loadingStyleSheet, setLoadingStyleSheet] = useScopeValue<boolean>(
|
||||||
'ui.loadingStyleSheet'
|
'ui.loadingStyleSheet'
|
||||||
)
|
)
|
||||||
|
|
||||||
// switch to either side-by-side or flat (full-width) layout
|
|
||||||
const switchLayout = useCallback(() => {
|
|
||||||
setPdfLayout(layout => {
|
|
||||||
const newLayout = layout === 'sideBySide' ? 'flat' : 'sideBySide'
|
|
||||||
setView(newLayout === 'sideBySide' ? 'editor' : 'pdf')
|
|
||||||
setPdfLayout(newLayout)
|
|
||||||
setLayoutInLocalStorage(newLayout)
|
|
||||||
})
|
|
||||||
}, [setPdfLayout, setView])
|
|
||||||
|
|
||||||
const changeLayout = useCallback(
|
const changeLayout = useCallback(
|
||||||
(newLayout, newView) => {
|
(newLayout: IdeLayout, newView: IdeView = 'editor') => {
|
||||||
setPdfLayout(newLayout)
|
setPdfLayout(newLayout)
|
||||||
setView(newLayout === 'sideBySide' ? 'editor' : newView)
|
setView(newLayout === 'sideBySide' ? 'editor' : newView)
|
||||||
setLayoutInLocalStorage(newLayout)
|
setLayoutInLocalStorage(newLayout)
|
||||||
|
@ -151,7 +155,7 @@ export function LayoutProvider({ children }) {
|
||||||
changeLayout,
|
changeLayout,
|
||||||
])
|
])
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo<LayoutContextValue>(
|
||||||
() => ({
|
() => ({
|
||||||
reattach,
|
reattach,
|
||||||
detach,
|
detach,
|
||||||
|
@ -170,7 +174,6 @@ export function LayoutProvider({ children }) {
|
||||||
setReviewPanelOpen,
|
setReviewPanelOpen,
|
||||||
setLoadingStyleSheet,
|
setLoadingStyleSheet,
|
||||||
setView,
|
setView,
|
||||||
switchLayout,
|
|
||||||
view,
|
view,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
|
@ -191,7 +194,6 @@ export function LayoutProvider({ children }) {
|
||||||
setReviewPanelOpen,
|
setReviewPanelOpen,
|
||||||
setLoadingStyleSheet,
|
setLoadingStyleSheet,
|
||||||
setView,
|
setView,
|
||||||
switchLayout,
|
|
||||||
view,
|
view,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -201,12 +203,10 @@ export function LayoutProvider({ children }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LayoutProvider.propTypes = {
|
export function useLayoutContext() {
|
||||||
children: PropTypes.any,
|
const context = useContext(LayoutContext)
|
||||||
}
|
if (!context) {
|
||||||
|
throw new Error('useLayoutContext is only available inside LayoutProvider')
|
||||||
export function useLayoutContext(propTypes) {
|
}
|
||||||
const data = useContext(LayoutContext)
|
return context
|
||||||
PropTypes.checkPropTypes(propTypes, data, 'data', 'LayoutContext.Provider')
|
|
||||||
return data
|
|
||||||
}
|
}
|
|
@ -2,7 +2,11 @@ import localStorage from '../../../../frontend/js/infrastructure/local-storage'
|
||||||
import PdfPreview from '../../../../frontend/js/features/pdf-preview/components/pdf-preview'
|
import PdfPreview from '../../../../frontend/js/features/pdf-preview/components/pdf-preview'
|
||||||
import { EditorProviders } from '../../helpers/editor-providers'
|
import { EditorProviders } from '../../helpers/editor-providers'
|
||||||
import { mockScope } from './scope'
|
import { mockScope } from './scope'
|
||||||
import { useLayoutContext } from '../../../../frontend/js/shared/context/layout-context'
|
import {
|
||||||
|
IdeLayout,
|
||||||
|
IdeView,
|
||||||
|
useLayoutContext,
|
||||||
|
} from '../../../../frontend/js/shared/context/layout-context'
|
||||||
import { FC, useEffect } from 'react'
|
import { FC, useEffect } from 'react'
|
||||||
|
|
||||||
const storeAndFireEvent = (win: typeof window, key: string, value: unknown) => {
|
const storeAndFireEvent = (win: typeof window, key: string, value: unknown) => {
|
||||||
|
@ -10,7 +14,10 @@ const storeAndFireEvent = (win: typeof window, key: string, value: unknown) => {
|
||||||
win.dispatchEvent(new StorageEvent('storage', { key }))
|
win.dispatchEvent(new StorageEvent('storage', { key }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const Layout: FC<{ layout: string; view?: string }> = ({ layout, view }) => {
|
const Layout: FC<{ layout: IdeLayout; view?: IdeView }> = ({
|
||||||
|
layout,
|
||||||
|
view,
|
||||||
|
}) => {
|
||||||
const { changeLayout } = useLayoutContext()
|
const { changeLayout } = useLayoutContext()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
Loading…
Reference in a new issue