mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-04 06:39:02 +02:00
Merge pull request #25128 from overleaf/dp-synctex
Add synctex controls with buttons hidden to new editor GitOrigin-RevId: 27566210444ca6d83fef977290fa7c2700f2bb62
This commit is contained in:
@@ -12,6 +12,7 @@ import { useState } from 'react'
|
||||
import EditorPanel from './editor-panel'
|
||||
import { useRailContext } from '../contexts/rail-context'
|
||||
import HistoryContainer from '@/features/ide-react/components/history-container'
|
||||
import { DefaultSynctexControl } from '@/features/pdf-preview/components/detach-synctex-control'
|
||||
|
||||
export default function MainLayout() {
|
||||
const [resizing, setResizing] = useState(false)
|
||||
@@ -80,6 +81,11 @@ export default function MainLayout() {
|
||||
tooltipWhenOpen={t('tooltip_hide_pdf')}
|
||||
tooltipWhenClosed={t('tooltip_show_pdf')}
|
||||
/>
|
||||
{pdfLayout === 'sideBySide' && (
|
||||
<div className="synctex-controls" hidden>
|
||||
<DefaultSynctexControl />
|
||||
</div>
|
||||
)}
|
||||
</HorizontalResizeHandle>
|
||||
<Panel
|
||||
collapsible
|
||||
@@ -95,6 +101,11 @@ export default function MainLayout() {
|
||||
onCollapse={handlePdfPaneCollapse}
|
||||
>
|
||||
<PdfPreview />
|
||||
{pdfLayout === 'flat' && view === 'pdf' && (
|
||||
<div className="synctex-controls" hidden>
|
||||
<DefaultSynctexControl />
|
||||
</div>
|
||||
)}
|
||||
</Panel>
|
||||
</PanelGroup>
|
||||
</Panel>
|
||||
|
||||
+3
-1
@@ -2,6 +2,7 @@ import { memo } from 'react'
|
||||
import OlButtonToolbar from '@/features/ui/components/ol/ol-button-toolbar'
|
||||
import PdfCompileButton from '@/features/pdf-preview/components/pdf-compile-button'
|
||||
import PdfHybridDownloadButton from '@/features/pdf-preview/components/pdf-hybrid-download-button'
|
||||
import { DetachedSynctexControl } from '@/features/pdf-preview/components/detach-synctex-control'
|
||||
|
||||
function PdfPreviewHybridToolbar() {
|
||||
// TODO: add detached pdf logic
|
||||
@@ -13,7 +14,8 @@ function PdfPreviewHybridToolbar() {
|
||||
</div>
|
||||
<div className="toolbar-pdf-right">
|
||||
<div className="toolbar-pdf-controls" id="toolbar-pdf-controls" />
|
||||
{/* TODO: should we have switch to editor/code check/synctex buttons? */}
|
||||
<DetachedSynctexControl />
|
||||
{/* TODO: should we have switch to editor/code check? */}
|
||||
</div>
|
||||
</OlButtonToolbar>
|
||||
)
|
||||
@@ -1,31 +1,15 @@
|
||||
import classNames from 'classnames'
|
||||
import { memo, useCallback, useEffect, useState, useRef, useMemo } from 'react'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import { getJSON } from '../../../infrastructure/fetch-json'
|
||||
import { memo, useCallback, useMemo } from 'react'
|
||||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
||||
import { useLayoutContext } from '../../../shared/context/layout-context'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useIsMounted from '../../../shared/hooks/use-is-mounted'
|
||||
import useAbortController from '../../../shared/hooks/use-abort-controller'
|
||||
import useDetachState from '../../../shared/hooks/use-detach-state'
|
||||
import useDetachAction from '../../../shared/hooks/use-detach-action'
|
||||
import localStorage from '../../../infrastructure/local-storage'
|
||||
import { useFileTreeData } from '../../../shared/context/file-tree-data-context'
|
||||
import useScopeEventListener from '../../../shared/hooks/use-scope-event-listener'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { Spinner } from 'react-bootstrap-5'
|
||||
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
|
||||
import useEventListener from '@/shared/hooks/use-event-listener'
|
||||
import { CursorPosition } from '@/features/ide-react/types/cursor-position'
|
||||
import { isValidTeXFile } from '@/main/is-valid-tex-file'
|
||||
import { PdfScrollPosition } from '@/shared/hooks/use-pdf-scroll-position'
|
||||
import { Placement } from 'react-bootstrap-5/types'
|
||||
import { showFileErrorToast } from '@/features/pdf-preview/components/synctex-toasts'
|
||||
import useSynctex from '../hooks/use-synctex'
|
||||
|
||||
const GoToCodeButton = memo(function GoToCodeButton({
|
||||
syncToCode,
|
||||
@@ -138,263 +122,15 @@ const GoToPdfButton = memo(function GoToPdfButton({
|
||||
})
|
||||
|
||||
function PdfSynctexControls() {
|
||||
const { _id: projectId, rootDocId } = useProjectContext()
|
||||
|
||||
const { detachRole } = useLayoutContext()
|
||||
|
||||
const { pdfUrl, pdfViewer, position } = useCompileContext()
|
||||
const {
|
||||
clsiServerId,
|
||||
pdfFile,
|
||||
pdfUrl,
|
||||
pdfViewer,
|
||||
position,
|
||||
setShowLogs,
|
||||
setHighlights,
|
||||
} = useCompileContext()
|
||||
|
||||
const { selectedEntities } = useFileTreeData()
|
||||
const { findEntityByPath, dirname, pathInFolder } = useFileTreePathContext()
|
||||
const { getCurrentDocumentId, openDocWithId, openDocName } =
|
||||
useEditorManagerContext()
|
||||
|
||||
const [cursorPosition, setCursorPosition] = useState<CursorPosition | null>(
|
||||
() => {
|
||||
const position = localStorage.getItem(
|
||||
`doc.position.${getCurrentDocumentId()}`
|
||||
)
|
||||
return position ? position.cursorPosition : null
|
||||
}
|
||||
)
|
||||
|
||||
const isMounted = useIsMounted()
|
||||
|
||||
const { signal } = useAbortController()
|
||||
|
||||
useEventListener(
|
||||
'cursor:editor:update',
|
||||
useCallback(event => setCursorPosition(event.detail), [])
|
||||
)
|
||||
|
||||
const [syncToPdfInFlight, setSyncToPdfInFlight] = useState(false)
|
||||
const [syncToCodeInFlight, setSyncToCodeInFlight] = useDetachState(
|
||||
'sync-to-code-inflight',
|
||||
false,
|
||||
'detacher',
|
||||
'detached'
|
||||
)
|
||||
|
||||
const getCurrentFilePath = useCallback(() => {
|
||||
const docId = getCurrentDocumentId()
|
||||
|
||||
if (!docId || !rootDocId) {
|
||||
return null
|
||||
}
|
||||
|
||||
let path = pathInFolder(docId)
|
||||
|
||||
if (!path) {
|
||||
return null
|
||||
}
|
||||
|
||||
// If the root file is folder/main.tex, then synctex sees the path as folder/./main.tex
|
||||
const rootDocDirname = dirname(rootDocId)
|
||||
|
||||
if (rootDocDirname) {
|
||||
path = path.replace(RegExp(`^${rootDocDirname}`), `${rootDocDirname}/.`)
|
||||
}
|
||||
|
||||
return path
|
||||
}, [dirname, getCurrentDocumentId, pathInFolder, rootDocId])
|
||||
|
||||
const goToCodeLine = useCallback(
|
||||
(file, line) => {
|
||||
if (file) {
|
||||
const doc = findEntityByPath(file)?.entity
|
||||
if (doc) {
|
||||
openDocWithId(doc._id, {
|
||||
gotoLine: line,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
showFileErrorToast()
|
||||
},
|
||||
[findEntityByPath, openDocWithId]
|
||||
)
|
||||
|
||||
const goToPdfLocation = useCallback(
|
||||
params => {
|
||||
setSyncToPdfInFlight(true)
|
||||
|
||||
if (clsiServerId) {
|
||||
params += `&clsiserverid=${clsiServerId}`
|
||||
}
|
||||
if (pdfFile?.editorId) params += `&editorId=${pdfFile.editorId}`
|
||||
if (pdfFile?.build) params += `&buildId=${pdfFile.build}`
|
||||
|
||||
getJSON(`/project/${projectId}/sync/code?${params}`, { signal })
|
||||
.then(data => {
|
||||
setShowLogs(false)
|
||||
setHighlights(data.pdf)
|
||||
})
|
||||
.catch(debugConsole.error)
|
||||
.finally(() => {
|
||||
if (isMounted.current) {
|
||||
setSyncToPdfInFlight(false)
|
||||
}
|
||||
})
|
||||
},
|
||||
[
|
||||
pdfFile,
|
||||
clsiServerId,
|
||||
isMounted,
|
||||
projectId,
|
||||
setShowLogs,
|
||||
setHighlights,
|
||||
setSyncToPdfInFlight,
|
||||
signal,
|
||||
]
|
||||
)
|
||||
|
||||
const cursorPositionRef = useRef(cursorPosition)
|
||||
|
||||
useEffect(() => {
|
||||
cursorPositionRef.current = cursorPosition
|
||||
}, [cursorPosition])
|
||||
|
||||
const syncToPdf = useCallback(() => {
|
||||
const file = getCurrentFilePath()
|
||||
|
||||
if (cursorPositionRef.current) {
|
||||
const { row, column } = cursorPositionRef.current
|
||||
|
||||
const params = new URLSearchParams({
|
||||
file: file ?? '',
|
||||
line: String(row + 1),
|
||||
column: String(column),
|
||||
}).toString()
|
||||
|
||||
eventTracking.sendMB('jump-to-location', {
|
||||
direction: 'code-location-in-pdf',
|
||||
method: 'arrow',
|
||||
})
|
||||
|
||||
goToPdfLocation(params)
|
||||
}
|
||||
}, [getCurrentFilePath, goToPdfLocation])
|
||||
|
||||
useScopeEventListener(
|
||||
'cursor:editor:syncToPdf',
|
||||
useCallback(() => {
|
||||
syncToPdf()
|
||||
}, [syncToPdf])
|
||||
)
|
||||
|
||||
const positionRef = useRef(position)
|
||||
useEffect(() => {
|
||||
positionRef.current = position
|
||||
}, [position])
|
||||
|
||||
const _syncToCode = useCallback(
|
||||
({
|
||||
position = positionRef.current,
|
||||
visualOffset = 0,
|
||||
}: {
|
||||
position?: PdfScrollPosition
|
||||
visualOffset?: number
|
||||
}) => {
|
||||
if (!position) {
|
||||
return
|
||||
}
|
||||
|
||||
setSyncToCodeInFlight(true)
|
||||
// FIXME: this actually works better if it's halfway across the
|
||||
// page (or the visible part of the page). Synctex doesn't
|
||||
// always find the right place in the file when the point is at
|
||||
// the edge of the page, it sometimes returns the start of the
|
||||
// next paragraph instead.
|
||||
const h = position.offset.left
|
||||
|
||||
// Compute the vertical position to pass to synctex, which
|
||||
// works with coordinates increasing from the top of the page
|
||||
// down. This matches the browser's DOM coordinate of the
|
||||
// click point, but the pdf position is measured from the
|
||||
// bottom of the page so we need to invert it.
|
||||
let v = 0
|
||||
if (position.pageSize?.height) {
|
||||
v += position.pageSize.height - position.offset.top // measure from pdf point (inverted)
|
||||
} else {
|
||||
v += position.offset.top // measure from html click position
|
||||
}
|
||||
v += visualOffset
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: position.page + 1,
|
||||
h: h.toFixed(2),
|
||||
v: v.toFixed(2),
|
||||
})
|
||||
|
||||
if (clsiServerId) {
|
||||
params.set('clsiserverid', clsiServerId)
|
||||
}
|
||||
if (pdfFile?.editorId) params.set('editorId', pdfFile.editorId)
|
||||
if (pdfFile?.build) params.set('buildId', pdfFile.build)
|
||||
|
||||
getJSON(`/project/${projectId}/sync/pdf?${params}`, { signal })
|
||||
.then(data => {
|
||||
const [{ file, line }] = data.code
|
||||
goToCodeLine(file, line)
|
||||
})
|
||||
.catch(debugConsole.error)
|
||||
.finally(() => {
|
||||
if (isMounted.current) {
|
||||
setSyncToCodeInFlight(false)
|
||||
}
|
||||
})
|
||||
},
|
||||
[
|
||||
pdfFile,
|
||||
clsiServerId,
|
||||
projectId,
|
||||
signal,
|
||||
isMounted,
|
||||
setSyncToCodeInFlight,
|
||||
goToCodeLine,
|
||||
]
|
||||
)
|
||||
|
||||
const syncToCode = useDetachAction(
|
||||
'sync-to-code',
|
||||
_syncToCode,
|
||||
'detached',
|
||||
'detacher'
|
||||
)
|
||||
|
||||
useEventListener(
|
||||
'synctex:sync-to-position',
|
||||
useCallback(event => syncToCode({ position: event.detail }), [syncToCode])
|
||||
)
|
||||
|
||||
const [hasSingleSelectedDoc, setHasSingleSelectedDoc] = useDetachState(
|
||||
'has-single-selected-doc',
|
||||
false,
|
||||
'detacher',
|
||||
'detached'
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEntities.length !== 1) {
|
||||
setHasSingleSelectedDoc(false)
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedEntities[0].type !== 'doc') {
|
||||
setHasSingleSelectedDoc(false)
|
||||
return
|
||||
}
|
||||
|
||||
setHasSingleSelectedDoc(true)
|
||||
}, [selectedEntities, setHasSingleSelectedDoc])
|
||||
syncToCode,
|
||||
syncToPdf,
|
||||
syncToCodeInFlight,
|
||||
syncToPdfInFlight,
|
||||
canSyncToPdf,
|
||||
} = useSynctex()
|
||||
|
||||
if (!position) {
|
||||
return null
|
||||
@@ -404,12 +140,6 @@ function PdfSynctexControls() {
|
||||
return null
|
||||
}
|
||||
|
||||
const canSyncToPdf: boolean =
|
||||
hasSingleSelectedDoc &&
|
||||
cursorPosition &&
|
||||
openDocName &&
|
||||
isValidTeXFile(openDocName)
|
||||
|
||||
if (detachRole === 'detacher') {
|
||||
return (
|
||||
<GoToPdfButton
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
import { useCallback, useEffect, useState, useRef } from 'react'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import { getJSON } from '../../../infrastructure/fetch-json'
|
||||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
||||
import useIsMounted from '../../../shared/hooks/use-is-mounted'
|
||||
import useAbortController from '../../../shared/hooks/use-abort-controller'
|
||||
import useDetachState from '../../../shared/hooks/use-detach-state'
|
||||
import useDetachAction from '../../../shared/hooks/use-detach-action'
|
||||
import localStorage from '../../../infrastructure/local-storage'
|
||||
import { useFileTreeData } from '../../../shared/context/file-tree-data-context'
|
||||
import useScopeEventListener from '../../../shared/hooks/use-scope-event-listener'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
|
||||
import useEventListener from '@/shared/hooks/use-event-listener'
|
||||
import { CursorPosition } from '@/features/ide-react/types/cursor-position'
|
||||
import { isValidTeXFile } from '@/main/is-valid-tex-file'
|
||||
import { PdfScrollPosition } from '@/shared/hooks/use-pdf-scroll-position'
|
||||
import { showFileErrorToast } from '@/features/pdf-preview/components/synctex-toasts'
|
||||
|
||||
export default function useSynctex(): {
|
||||
syncToPdf: () => void
|
||||
syncToCode: ({ visualOffset }: { visualOffset?: number }) => void
|
||||
syncToPdfInFlight: boolean
|
||||
syncToCodeInFlight: boolean
|
||||
canSyncToPdf: boolean
|
||||
} {
|
||||
const { _id: projectId, rootDocId } = useProjectContext()
|
||||
|
||||
const { clsiServerId, pdfFile, position, setShowLogs, setHighlights } =
|
||||
useCompileContext()
|
||||
|
||||
const { selectedEntities } = useFileTreeData()
|
||||
const { findEntityByPath, dirname, pathInFolder } = useFileTreePathContext()
|
||||
const { getCurrentDocumentId, openDocWithId, openDocName } =
|
||||
useEditorManagerContext()
|
||||
|
||||
const [cursorPosition, setCursorPosition] = useState<CursorPosition | null>(
|
||||
() => {
|
||||
const position = localStorage.getItem(
|
||||
`doc.position.${getCurrentDocumentId()}`
|
||||
)
|
||||
return position ? position.cursorPosition : null
|
||||
}
|
||||
)
|
||||
|
||||
const isMounted = useIsMounted()
|
||||
|
||||
const { signal } = useAbortController()
|
||||
|
||||
useEventListener(
|
||||
'cursor:editor:update',
|
||||
useCallback(event => setCursorPosition(event.detail), [])
|
||||
)
|
||||
|
||||
const [syncToPdfInFlight, setSyncToPdfInFlight] = useState(false)
|
||||
const [syncToCodeInFlight, setSyncToCodeInFlight] = useDetachState(
|
||||
'sync-to-code-inflight',
|
||||
false,
|
||||
'detacher',
|
||||
'detached'
|
||||
)
|
||||
|
||||
const getCurrentFilePath = useCallback(() => {
|
||||
const docId = getCurrentDocumentId()
|
||||
|
||||
if (!docId || !rootDocId) {
|
||||
return null
|
||||
}
|
||||
|
||||
let path = pathInFolder(docId)
|
||||
|
||||
if (!path) {
|
||||
return null
|
||||
}
|
||||
|
||||
// If the root file is folder/main.tex, then synctex sees the path as folder/./main.tex
|
||||
const rootDocDirname = dirname(rootDocId)
|
||||
|
||||
if (rootDocDirname) {
|
||||
path = path.replace(RegExp(`^${rootDocDirname}`), `${rootDocDirname}/.`)
|
||||
}
|
||||
|
||||
return path
|
||||
}, [dirname, getCurrentDocumentId, pathInFolder, rootDocId])
|
||||
|
||||
const goToCodeLine = useCallback(
|
||||
(file, line) => {
|
||||
if (file) {
|
||||
const doc = findEntityByPath(file)?.entity
|
||||
if (doc) {
|
||||
openDocWithId(doc._id, {
|
||||
gotoLine: line,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
showFileErrorToast()
|
||||
},
|
||||
[findEntityByPath, openDocWithId]
|
||||
)
|
||||
|
||||
const goToPdfLocation = useCallback(
|
||||
params => {
|
||||
setSyncToPdfInFlight(true)
|
||||
|
||||
if (clsiServerId) {
|
||||
params += `&clsiserverid=${clsiServerId}`
|
||||
}
|
||||
if (pdfFile?.editorId) params += `&editorId=${pdfFile.editorId}`
|
||||
if (pdfFile?.build) params += `&buildId=${pdfFile.build}`
|
||||
|
||||
getJSON(`/project/${projectId}/sync/code?${params}`, { signal })
|
||||
.then(data => {
|
||||
setShowLogs(false)
|
||||
setHighlights(data.pdf)
|
||||
})
|
||||
.catch(debugConsole.error)
|
||||
.finally(() => {
|
||||
if (isMounted.current) {
|
||||
setSyncToPdfInFlight(false)
|
||||
}
|
||||
})
|
||||
},
|
||||
[
|
||||
pdfFile,
|
||||
clsiServerId,
|
||||
isMounted,
|
||||
projectId,
|
||||
setShowLogs,
|
||||
setHighlights,
|
||||
setSyncToPdfInFlight,
|
||||
signal,
|
||||
]
|
||||
)
|
||||
|
||||
const cursorPositionRef = useRef(cursorPosition)
|
||||
|
||||
useEffect(() => {
|
||||
cursorPositionRef.current = cursorPosition
|
||||
}, [cursorPosition])
|
||||
|
||||
const syncToPdf = useCallback(() => {
|
||||
const file = getCurrentFilePath()
|
||||
|
||||
if (cursorPositionRef.current) {
|
||||
const { row, column } = cursorPositionRef.current
|
||||
|
||||
const params = new URLSearchParams({
|
||||
file: file ?? '',
|
||||
line: String(row + 1),
|
||||
column: String(column),
|
||||
}).toString()
|
||||
|
||||
eventTracking.sendMB('jump-to-location', {
|
||||
direction: 'code-location-in-pdf',
|
||||
method: 'arrow',
|
||||
})
|
||||
|
||||
goToPdfLocation(params)
|
||||
}
|
||||
}, [getCurrentFilePath, goToPdfLocation])
|
||||
|
||||
useScopeEventListener(
|
||||
'cursor:editor:syncToPdf',
|
||||
useCallback(() => {
|
||||
syncToPdf()
|
||||
}, [syncToPdf])
|
||||
)
|
||||
|
||||
const positionRef = useRef(position)
|
||||
useEffect(() => {
|
||||
positionRef.current = position
|
||||
}, [position])
|
||||
|
||||
const _syncToCode = useCallback(
|
||||
({
|
||||
position = positionRef.current,
|
||||
visualOffset = 0,
|
||||
}: {
|
||||
position?: PdfScrollPosition
|
||||
visualOffset?: number
|
||||
}) => {
|
||||
if (!position) {
|
||||
return
|
||||
}
|
||||
|
||||
setSyncToCodeInFlight(true)
|
||||
// FIXME: this actually works better if it's halfway across the
|
||||
// page (or the visible part of the page). Synctex doesn't
|
||||
// always find the right place in the file when the point is at
|
||||
// the edge of the page, it sometimes returns the start of the
|
||||
// next paragraph instead.
|
||||
const h = position.offset.left
|
||||
|
||||
// Compute the vertical position to pass to synctex, which
|
||||
// works with coordinates increasing from the top of the page
|
||||
// down. This matches the browser's DOM coordinate of the
|
||||
// click point, but the pdf position is measured from the
|
||||
// bottom of the page so we need to invert it.
|
||||
let v = 0
|
||||
if (position.pageSize?.height) {
|
||||
v += position.pageSize.height - position.offset.top // measure from pdf point (inverted)
|
||||
} else {
|
||||
v += position.offset.top // measure from html click position
|
||||
}
|
||||
v += visualOffset
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: position.page + 1,
|
||||
h: h.toFixed(2),
|
||||
v: v.toFixed(2),
|
||||
})
|
||||
|
||||
if (clsiServerId) {
|
||||
params.set('clsiserverid', clsiServerId)
|
||||
}
|
||||
if (pdfFile?.editorId) params.set('editorId', pdfFile.editorId)
|
||||
if (pdfFile?.build) params.set('buildId', pdfFile.build)
|
||||
|
||||
getJSON(`/project/${projectId}/sync/pdf?${params}`, { signal })
|
||||
.then(data => {
|
||||
const [{ file, line }] = data.code
|
||||
goToCodeLine(file, line)
|
||||
})
|
||||
.catch(debugConsole.error)
|
||||
.finally(() => {
|
||||
if (isMounted.current) {
|
||||
setSyncToCodeInFlight(false)
|
||||
}
|
||||
})
|
||||
},
|
||||
[
|
||||
pdfFile,
|
||||
clsiServerId,
|
||||
projectId,
|
||||
signal,
|
||||
isMounted,
|
||||
setSyncToCodeInFlight,
|
||||
goToCodeLine,
|
||||
]
|
||||
)
|
||||
|
||||
const syncToCode = useDetachAction(
|
||||
'sync-to-code',
|
||||
_syncToCode,
|
||||
'detached',
|
||||
'detacher'
|
||||
)
|
||||
|
||||
useEventListener(
|
||||
'synctex:sync-to-position',
|
||||
useCallback(event => syncToCode({ position: event.detail }), [syncToCode])
|
||||
)
|
||||
|
||||
const [hasSingleSelectedDoc, setHasSingleSelectedDoc] = useDetachState(
|
||||
'has-single-selected-doc',
|
||||
false,
|
||||
'detacher',
|
||||
'detached'
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEntities.length !== 1) {
|
||||
setHasSingleSelectedDoc(false)
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedEntities[0].type !== 'doc') {
|
||||
setHasSingleSelectedDoc(false)
|
||||
return
|
||||
}
|
||||
|
||||
setHasSingleSelectedDoc(true)
|
||||
}, [selectedEntities, setHasSingleSelectedDoc])
|
||||
|
||||
const canSyncToPdf: boolean =
|
||||
hasSingleSelectedDoc &&
|
||||
cursorPosition &&
|
||||
openDocName &&
|
||||
isValidTeXFile(openDocName)
|
||||
|
||||
return {
|
||||
syncToCode,
|
||||
syncToPdf,
|
||||
syncToPdfInFlight,
|
||||
syncToCodeInFlight,
|
||||
canSyncToPdf,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user