mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2025-07-29 14:00:05 +02:00

Persist currently selected rail tab on refresh GitOrigin-RevId: a609bed93340d950a1fba8358fd5ed20afe6a4ce
170 lines
4 KiB
TypeScript
170 lines
4 KiB
TypeScript
import { sendSearchEvent } from '@/features/event-tracking/search-events'
|
|
import useEventListener from '@/shared/hooks/use-event-listener'
|
|
import usePersistedState from '@/shared/hooks/use-persisted-state'
|
|
import { isMac } from '@/shared/utils/os'
|
|
import {
|
|
createContext,
|
|
Dispatch,
|
|
FC,
|
|
SetStateAction,
|
|
useCallback,
|
|
useContext,
|
|
useLayoutEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
} from 'react'
|
|
import { ImperativePanelHandle } from 'react-resizable-panels'
|
|
|
|
export type RailTabKey =
|
|
| 'file-tree'
|
|
| 'integrations'
|
|
| 'review-panel'
|
|
| 'chat'
|
|
| 'errors'
|
|
| 'full-project-search'
|
|
|
|
export type RailModalKey = 'keyboard-shortcuts' | 'contact-us' | 'dictionary'
|
|
|
|
const RailContext = createContext<
|
|
| {
|
|
selectedTab: RailTabKey
|
|
isOpen: boolean
|
|
setIsOpen: Dispatch<SetStateAction<boolean>>
|
|
panelRef: React.RefObject<ImperativePanelHandle>
|
|
togglePane: () => void
|
|
handlePaneExpand: () => void
|
|
handlePaneCollapse: () => void
|
|
resizing: boolean
|
|
setResizing: Dispatch<SetStateAction<boolean>>
|
|
activeModal: RailModalKey | null
|
|
setActiveModal: Dispatch<SetStateAction<RailModalKey | null>>
|
|
openTab: (tab: RailTabKey) => void
|
|
}
|
|
| undefined
|
|
>(undefined)
|
|
|
|
export const RailProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
|
const [isOpen, setIsOpen] = usePersistedState('rail-is-open', true)
|
|
const [resizing, setResizing] = useState(false)
|
|
const [activeModal, setActiveModalInternal] = useState<RailModalKey | null>(
|
|
null
|
|
)
|
|
const setActiveModal: Dispatch<SetStateAction<RailModalKey | null>> =
|
|
useCallback(modalKey => {
|
|
setActiveModalInternal(modalKey)
|
|
}, [])
|
|
|
|
const panelRef = useRef<ImperativePanelHandle>(null)
|
|
|
|
const togglePane = useCallback(() => {
|
|
setIsOpen(value => !value)
|
|
}, [setIsOpen])
|
|
|
|
const handlePaneExpand = useCallback(() => {
|
|
setIsOpen(true)
|
|
}, [setIsOpen])
|
|
|
|
const handlePaneCollapse = useCallback(() => {
|
|
setIsOpen(false)
|
|
}, [setIsOpen])
|
|
|
|
const [selectedTab, setSelectedTab] = usePersistedState<RailTabKey>(
|
|
'selected-rail-tab',
|
|
'file-tree'
|
|
)
|
|
|
|
// Keep the panel collapse/expanded state in sync with isOpen and selectedTab
|
|
useLayoutEffect(() => {
|
|
const panelHandle = panelRef.current
|
|
|
|
if (panelHandle) {
|
|
if (isOpen) {
|
|
panelHandle.expand()
|
|
} else {
|
|
panelHandle.collapse()
|
|
}
|
|
}
|
|
}, [isOpen, selectedTab])
|
|
|
|
const openTab = useCallback(
|
|
(tab: RailTabKey) => {
|
|
setSelectedTab(tab)
|
|
setIsOpen(true)
|
|
},
|
|
[setIsOpen, setSelectedTab]
|
|
)
|
|
|
|
useEventListener(
|
|
'ui.toggle-review-panel',
|
|
useCallback(() => {
|
|
if (isOpen && selectedTab === 'review-panel') {
|
|
handlePaneCollapse()
|
|
} else {
|
|
openTab('review-panel')
|
|
}
|
|
}, [handlePaneCollapse, selectedTab, isOpen, openTab])
|
|
)
|
|
|
|
useEventListener(
|
|
'keydown',
|
|
useCallback(
|
|
(event: KeyboardEvent) => {
|
|
if (
|
|
(isMac ? event.metaKey : event.ctrlKey) &&
|
|
event.shiftKey &&
|
|
event.code === 'KeyF'
|
|
) {
|
|
event.preventDefault()
|
|
sendSearchEvent('search-open', {
|
|
searchType: 'full-project',
|
|
method: 'keyboard',
|
|
})
|
|
openTab('full-project-search')
|
|
}
|
|
},
|
|
[openTab]
|
|
)
|
|
)
|
|
|
|
const value = useMemo(
|
|
() => ({
|
|
selectedTab,
|
|
isOpen,
|
|
setIsOpen,
|
|
panelRef,
|
|
togglePane,
|
|
handlePaneExpand,
|
|
handlePaneCollapse,
|
|
resizing,
|
|
setResizing,
|
|
activeModal,
|
|
setActiveModal,
|
|
openTab,
|
|
}),
|
|
[
|
|
selectedTab,
|
|
isOpen,
|
|
setIsOpen,
|
|
panelRef,
|
|
togglePane,
|
|
handlePaneExpand,
|
|
handlePaneCollapse,
|
|
resizing,
|
|
setResizing,
|
|
activeModal,
|
|
setActiveModal,
|
|
openTab,
|
|
]
|
|
)
|
|
|
|
return <RailContext.Provider value={value}>{children}</RailContext.Provider>
|
|
}
|
|
|
|
export const useRailContext = () => {
|
|
const context = useContext(RailContext)
|
|
if (!context) {
|
|
throw new Error('useRailContext is only available inside RailProvider')
|
|
}
|
|
return context
|
|
}
|