| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- import {
- createContext,
- Dispatch,
- FC,
- SetStateAction,
- useCallback,
- useContext,
- useMemo,
- useState,
- } from 'react'
- import useScopeEventEmitter from '@/shared/hooks/use-scope-event-emitter'
- import useEventListener from '@/shared/hooks/use-event-listener'
- import * as eventTracking from '@/infrastructure/event-tracking'
- import useScopeValue from '@/shared/hooks/use-scope-value'
- import { isValidTeXFile } from '@/main/is-valid-tex-file'
- import localStorage from '@/infrastructure/local-storage'
- import { useProjectContext } from '@/shared/context/project-context'
- export type PartialFlatOutline = {
- level: number
- title: string
- line: number
- }[]
- export type FlatOutlineState =
- | {
- items: PartialFlatOutline
- partial: boolean
- }
- | undefined
- const OutlineContext = createContext<
- | {
- flatOutline: FlatOutlineState
- setFlatOutline: Dispatch<SetStateAction<FlatOutlineState>>
- highlightedLine: number
- jumpToLine: (lineNumber: number, syncToPdf: boolean) => void
- canShowOutline: boolean
- outlineExpanded: boolean
- toggleOutlineExpanded: () => void
- }
- | undefined
- >(undefined)
- export const OutlineProvider: FC = ({ children }) => {
- const [flatOutline, setFlatOutline] = useState<FlatOutlineState>(undefined)
- const [currentlyHighlightedLine, setCurrentlyHighlightedLine] =
- useState<number>(-1)
- const [binaryFileOpened, setBinaryFileOpened] = useState<boolean>(false)
- const [ignoreNextCursorUpdate, setIgnoreNextCursorUpdate] =
- useState<boolean>(false)
- const [ignoreNextScroll, setIgnoreNextScroll] = useState<boolean>(false)
- const goToLineEmitter = useScopeEventEmitter('editor:gotoLine', true)
- useEventListener(
- 'file-view:file-opened',
- useCallback(_ => {
- setBinaryFileOpened(true)
- }, [])
- )
- useEventListener(
- 'scroll:editor:update',
- useCallback(
- evt => {
- if (ignoreNextScroll) {
- setIgnoreNextScroll(false)
- return
- }
- setCurrentlyHighlightedLine(evt.detail + 1)
- },
- [ignoreNextScroll]
- )
- )
- useEventListener(
- 'cursor:editor:update',
- useCallback(
- evt => {
- if (ignoreNextCursorUpdate) {
- setIgnoreNextCursorUpdate(false)
- return
- }
- setCurrentlyHighlightedLine(evt.detail.row + 1)
- },
- [ignoreNextCursorUpdate]
- )
- )
- useEventListener(
- 'doc:after-opened',
- useCallback(evt => {
- if (evt.detail.isNewDoc) {
- setIgnoreNextCursorUpdate(true)
- }
- setBinaryFileOpened(false)
- setIgnoreNextScroll(true)
- }, [])
- )
- const jumpToLine = useCallback(
- (lineNumber: number, syncToPdf: boolean) => {
- setIgnoreNextScroll(true)
- goToLineEmitter(lineNumber, 0, syncToPdf)
- eventTracking.sendMB('outline-jump-to-line')
- },
- [goToLineEmitter]
- )
- const highlightedLine = useMemo(
- () =>
- closestSectionLineNumber(flatOutline?.items, currentlyHighlightedLine),
- [flatOutline, currentlyHighlightedLine]
- )
- const [docName] = useScopeValue<string | null>('editor.open_doc_name')
- const isTexFile = useMemo(
- () => (docName ? isValidTeXFile(docName) : false),
- [docName]
- )
- const { _id: projectId } = useProjectContext()
- const storageKey = `file_outline.expanded.${projectId}`
- const [outlineExpanded, setOutlineExpanded] = useState(
- () => localStorage.getItem(storageKey) !== false
- )
- const canShowOutline = isTexFile && !binaryFileOpened
- const toggleOutlineExpanded = useCallback(() => {
- if (canShowOutline) {
- localStorage.setItem(storageKey, !outlineExpanded)
- eventTracking.sendMB(
- outlineExpanded ? 'outline-collapse' : 'outline-expand'
- )
- setOutlineExpanded(!outlineExpanded)
- }
- }, [canShowOutline, outlineExpanded, storageKey])
- const value = useMemo(
- () => ({
- flatOutline,
- setFlatOutline,
- highlightedLine,
- jumpToLine,
- canShowOutline,
- outlineExpanded,
- toggleOutlineExpanded,
- }),
- [
- flatOutline,
- highlightedLine,
- jumpToLine,
- canShowOutline,
- outlineExpanded,
- toggleOutlineExpanded,
- ]
- )
- return (
- <OutlineContext.Provider value={value}>{children}</OutlineContext.Provider>
- )
- }
- export const useOutlineContext = () => {
- const context = useContext(OutlineContext)
- if (!context) {
- throw new Error(
- 'useOutlineProvider is only available inside OutlineProvider'
- )
- }
- return context
- }
- const closestSectionLineNumber = (
- outline: { line: number }[] | undefined,
- lineNumber: number
- ): number => {
- if (!outline) {
- return -1
- }
- let highestLine = -1
- for (const section of outline) {
- if (section.line > lineNumber) {
- return highestLine
- }
- highestLine = section.line
- }
- return highestLine
- }
|