diff --git a/services/web/frontend/js/features/pdf-preview/components/detach-compile-button-wrapper.jsx b/services/web/frontend/js/features/pdf-preview/components/detach-compile-button-wrapper.tsx similarity index 61% rename from services/web/frontend/js/features/pdf-preview/components/detach-compile-button-wrapper.jsx rename to services/web/frontend/js/features/pdf-preview/components/detach-compile-button-wrapper.tsx index a9997206ef..983309b66a 100644 --- a/services/web/frontend/js/features/pdf-preview/components/detach-compile-button-wrapper.jsx +++ b/services/web/frontend/js/features/pdf-preview/components/detach-compile-button-wrapper.tsx @@ -1,12 +1,9 @@ import { memo } from 'react' -import PropTypes from 'prop-types' import { useLayoutContext } from '../../../shared/context/layout-context' import DetachCompileButton from './detach-compile-button' function DetachCompileButtonWrapper() { - const { detachRole, detachIsLinked } = useLayoutContext( - layoutContextPropTypes - ) + const { detachRole, detachIsLinked } = useLayoutContext() if (detachRole !== 'detacher' || !detachIsLinked) { return null @@ -15,9 +12,4 @@ function DetachCompileButtonWrapper() { return } -const layoutContextPropTypes = { - detachRole: PropTypes.string, - detachIsLinked: PropTypes.bool, -} - export default memo(DetachCompileButtonWrapper) diff --git a/services/web/frontend/js/features/pdf-preview/components/detach-synctex-control.jsx b/services/web/frontend/js/features/pdf-preview/components/detach-synctex-control.tsx similarity index 100% rename from services/web/frontend/js/features/pdf-preview/components/detach-synctex-control.jsx rename to services/web/frontend/js/features/pdf-preview/components/detach-synctex-control.tsx diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-clear-cache-button.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-clear-cache-button.tsx similarity index 100% rename from services/web/frontend/js/features/pdf-preview/components/pdf-clear-cache-button.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-clear-cache-button.tsx diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-code-check-failed-notice.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-code-check-failed-notice.tsx similarity index 100% rename from services/web/frontend/js/features/pdf-preview/components/pdf-code-check-failed-notice.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-code-check-failed-notice.tsx diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-download-files-button.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-download-files-button.tsx similarity index 97% rename from services/web/frontend/js/features/pdf-preview/components/pdf-download-files-button.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-download-files-button.tsx index f3c2b74846..a231d5b182 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-download-files-button.jsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-download-files-button.tsx @@ -17,6 +17,10 @@ function PdfDownloadFilesButton() { const { t } = useTranslation() + if (!fileList) { + return null + } + return ( diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-file-list.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-file-list.tsx similarity index 73% rename from services/web/frontend/js/features/pdf-preview/components/pdf-file-list.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-file-list.tsx index c4258c924b..0a635fac79 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-file-list.jsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-file-list.tsx @@ -1,22 +1,22 @@ import { MenuItem as BS3MenuItem } from 'react-bootstrap' import { useTranslation } from 'react-i18next' import { memo } from 'react' -import PropTypes from 'prop-types' import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher' import { DropdownDivider, DropdownHeader, DropdownItem, } from '@/features/ui/components/bootstrap-5/dropdown-menu' +import { PdfFileData, PdfFileDataList } from '../util/types' -function PdfFileList({ fileList }) { +function PdfFileList({ fileList }: { fileList: PdfFileDataList }) { const { t } = useTranslation() if (!fileList) { return null } - function basename(file) { + function basename(file: PdfFileData) { return file.path.split('/').pop() } @@ -94,40 +94,22 @@ function PdfFileList({ fileList }) { ))} - {fileList.archive?.fileCount > 0 && ( -
  • - - {t('download_all')} ({fileList.archive.fileCount}) - -
  • - )} + {fileList.archive?.fileCount !== undefined && + fileList.archive?.fileCount > 0 && ( +
  • + + {t('download_all')} ({fileList.archive.fileCount}) + +
  • + )} } /> ) } -const FilesArray = PropTypes.arrayOf( - PropTypes.shape({ - path: PropTypes.string.isRequired, - url: PropTypes.string.isRequired, - }) -) - -PdfFileList.propTypes = { - fileList: PropTypes.shape({ - top: FilesArray, - other: FilesArray, - archive: PropTypes.shape({ - path: PropTypes.string.isRequired, - url: PropTypes.string.isRequired, - fileCount: PropTypes.number.isRequired, - }), - }), -} - export default memo(PdfFileList) diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-code-check-button.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-code-check-button.tsx similarity index 100% rename from services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-code-check-button.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-code-check-button.tsx diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-content.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-content.tsx similarity index 75% rename from services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-content.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-content.tsx index 91b4e74528..8c9a9d7761 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-content.jsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-content.tsx @@ -1,9 +1,15 @@ import { useTranslation } from 'react-i18next' import PdfLogEntryRawContent from './pdf-log-entry-raw-content' -import PropTypes from 'prop-types' import importOverleafModules from '../../../../macros/import-overleaf-module.macro' +import { LogEntry } from '../util/types' +import { ElementType } from 'react' -const pdfLogEntryComponents = importOverleafModules('pdfLogEntryComponents') +const pdfLogEntryComponents = importOverleafModules( + 'pdfLogEntryComponents' +) as { + import: { default: ElementType } + path: string +}[] export default function PdfLogEntryContent({ rawContent, @@ -11,6 +17,12 @@ export default function PdfLogEntryContent({ extraInfoURL, index, logEntry, +}: { + rawContent?: string + formattedContent?: React.ReactNode + extraInfoURL?: string | null + index?: number + logEntry?: LogEntry }) { const { t } = useTranslation() @@ -41,11 +53,3 @@ export default function PdfLogEntryContent({ ) } - -PdfLogEntryContent.propTypes = { - rawContent: PropTypes.string, - formattedContent: PropTypes.node, - extraInfoURL: PropTypes.string, - index: PropTypes.number, - logEntry: PropTypes.any, -} diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-raw-content.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-raw-content.tsx similarity index 92% rename from services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-raw-content.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-raw-content.tsx index 3e0a80c67d..42e89215b9 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-raw-content.jsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-raw-content.tsx @@ -4,11 +4,13 @@ import { useTranslation } from 'react-i18next' import classNames from 'classnames' import OLButton from '@/features/ui/components/ol/ol-button' import Icon from '../../../shared/components/icon' -import PropTypes from 'prop-types' export default function PdfLogEntryRawContent({ rawContent, collapsedSize = 0, +}: { + rawContent: string + collapsedSize?: number }) { const [expanded, setExpanded] = useState(false) const [needsExpander, setNeedsExpander] = useState(true) @@ -68,8 +70,3 @@ export default function PdfLogEntryRawContent({ ) } - -PdfLogEntryRawContent.propTypes = { - rawContent: PropTypes.string.isRequired, - collapsedSize: PropTypes.number, -} diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry.tsx similarity index 58% rename from services/web/frontend/js/features/pdf-preview/components/pdf-log-entry.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-log-entry.tsx index dd678b798c..349ad79047 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry.jsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry.tsx @@ -1,11 +1,11 @@ -import PropTypes from 'prop-types' import classNames from 'classnames' -import { memo, useCallback } from 'react' +import { memo, MouseEventHandler, useCallback } from 'react' import PreviewLogEntryHeader from '../../preview/components/preview-log-entry-header' import PdfLogEntryContent from './pdf-log-entry-content' import HumanReadableLogsHints from '../../../ide/human-readable-logs/HumanReadableLogsHints' import { sendMB } from '@/infrastructure/event-tracking' import getMeta from '@/utils/meta' +import { ErrorLevel, LogEntry, SourceLocation } from '../util/types' function PdfLogEntry({ ruleId, @@ -19,7 +19,7 @@ function PdfLogEntry({ sourceLocation, showSourceLocationLink = true, showCloseButton = false, - entryAriaLabel = null, + entryAriaLabel = undefined, customClass, contentDetails, onSourceLocationClick, @@ -27,6 +27,26 @@ function PdfLogEntry({ index, logEntry, id, +}: { + headerTitle: string | React.ReactNode + level: ErrorLevel + ruleId?: string + headerIcon?: React.ReactElement + rawContent?: string + logType?: string + formattedContent?: React.ReactNode + extraInfoURL?: string | null + sourceLocation?: SourceLocation + showSourceLocationLink?: boolean + showCloseButton?: boolean + entryAriaLabel?: string + customClass?: string + contentDetails?: string[] + onSourceLocationClick?: (sourceLocation: SourceLocation) => void + onClose?: () => void + index?: number + logEntry?: LogEntry + id?: string }) { const showAiErrorAssistant = getMeta('ol-showAiErrorAssistant') @@ -36,17 +56,22 @@ function PdfLogEntry({ extraInfoURL = hint.extraInfoURL } - const handleLogEntryLinkClick = useCallback( - event => { - event.preventDefault() - onSourceLocationClick(sourceLocation) + const handleLogEntryLinkClick: MouseEventHandler = + useCallback( + event => { + event.preventDefault() - const parts = sourceLocation?.file?.split('.') - const extension = parts?.length > 1 ? parts.pop() : '' - sendMB('log-entry-link-click', { level, ruleId, extension }) - }, - [level, onSourceLocationClick, ruleId, sourceLocation] - ) + if (onSourceLocationClick && sourceLocation) { + onSourceLocationClick(sourceLocation) + + const parts = sourceLocation?.file?.split('.') + const extension = + parts?.length && parts?.length > 1 ? parts.pop() : '' + sendMB('log-entry-link-click', { level, ruleId, extension }) + } + }, + [level, onSourceLocationClick, ruleId, sourceLocation] + ) return (
    ) } -PdfLogsEntries.propTypes = { - entries: PropTypes.arrayOf(PropTypes.object), - hasErrors: PropTypes.bool, -} export default memo(PdfLogsEntries) diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.tsx similarity index 95% rename from services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.tsx index 8f8d44cc5c..77feb23ecc 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.jsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.tsx @@ -14,11 +14,10 @@ import PdfCodeCheckFailedNotice from './pdf-code-check-failed-notice' import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context' import PdfLogEntry from './pdf-log-entry' import { usePdfPreviewContext } from '@/features/pdf-preview/components/pdf-preview-provider' -import PropTypes from 'prop-types' import TimeoutUpgradePaywallPrompt from './timeout-upgrade-paywall-prompt' import getMeta from '@/utils/meta' -function PdfLogsViewer({ alwaysVisible = false }) { +function PdfLogsViewer({ alwaysVisible = false }: { alwaysVisible?: boolean }) { const { codeCheckFailed, error, @@ -91,10 +90,6 @@ function PdfLogsViewer({ alwaysVisible = false }) { ) } -PdfLogsViewer.propTypes = { - alwaysVisible: PropTypes.bool, -} - export default withErrorBoundary(memo(PdfLogsViewer), () => ( )) diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-orphan-refresh-button.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-orphan-refresh-button.tsx similarity index 100% rename from services/web/frontend/js/features/pdf-preview/components/pdf-orphan-refresh-button.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-orphan-refresh-button.tsx diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-detached-root.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-detached-root.tsx similarity index 100% rename from services/web/frontend/js/features/pdf-preview/components/pdf-preview-detached-root.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-preview-detached-root.tsx diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error-boundary-fallback.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error-boundary-fallback.tsx similarity index 85% rename from services/web/frontend/js/features/pdf-preview/components/pdf-preview-error-boundary-fallback.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-preview-error-boundary-fallback.tsx index f73a000bfb..8e374deab3 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error-boundary-fallback.jsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error-boundary-fallback.tsx @@ -1,8 +1,11 @@ -import PropTypes from 'prop-types' import { Trans, useTranslation } from 'react-i18next' import { ErrorBoundaryFallback } from '../../../shared/components/error-boundary-fallback' -function PdfPreviewErrorBoundaryFallback({ type }) { +function PdfPreviewErrorBoundaryFallback({ + type, +}: { + type: 'preview' | 'pdf' | 'logs' +}) { const { t } = useTranslation() const showInfoLink = ( @@ -47,8 +50,4 @@ function PdfPreviewErrorBoundaryFallback({ type }) { } } -PdfPreviewErrorBoundaryFallback.propTypes = { - type: PropTypes.oneOf(['preview', 'pdf', 'logs']).isRequired, -} - export default PdfPreviewErrorBoundaryFallback diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error.tsx similarity index 96% rename from services/web/frontend/js/features/pdf-preview/components/pdf-preview-error.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-preview-error.tsx index 6136413c49..87ccba2314 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error.jsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types' import { useTranslation, Trans } from 'react-i18next' import { memo, useCallback } from 'react' import OLButton from '@/features/ui/components/ol/ol-button' @@ -7,7 +6,7 @@ import { useDetachCompileContext as useCompileContext } from '../../../shared/co import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error' import getMeta from '../../../utils/meta' -function PdfPreviewError({ error }) { +function PdfPreviewError({ error }: { error: string }) { const { t } = useTranslation() const { startCompile } = useCompileContext() @@ -216,13 +215,17 @@ function PdfPreviewError({ error }) { } } -PdfPreviewError.propTypes = { - error: PropTypes.string.isRequired, -} - export default memo(PdfPreviewError) -function ErrorLogEntry({ title, headerIcon, children }) { +function ErrorLogEntry({ + title, + headerIcon, + children, +}: { + title: string + headerIcon?: React.ReactElement + children: React.ReactNode +}) { const { t } = useTranslation() return ( @@ -235,11 +238,6 @@ function ErrorLogEntry({ title, headerIcon, children }) { /> ) } -ErrorLogEntry.propTypes = { - title: PropTypes.string.isRequired, - headerIcon: PropTypes.element, - children: PropTypes.any.isRequired, -} function TimedOutLogEntry() { const { t } = useTranslation() @@ -307,4 +305,3 @@ function TimedOutLogEntry() { ) } -TimedOutLogEntry.propTypes = {} diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.tsx similarity index 95% rename from services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.tsx index 28db633194..b91778d63a 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.jsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.tsx @@ -18,18 +18,18 @@ const ORPHAN_UI_TIMEOUT_MS = 5000 function PdfPreviewHybridToolbar() { const { detachRole, detachIsLinked } = useLayoutContext() - const uiTimeoutRef = useRef() + const uiTimeoutRef = useRef() const [orphanPdfTabAfterDelay, setOrphanPdfTabAfterDelay] = useState(false) const orphanPdfTab = !detachIsLinked && detachRole === 'detached' useEffect(() => { if (uiTimeoutRef.current) { - clearTimeout(uiTimeoutRef.current) + window.clearTimeout(uiTimeoutRef.current) } if (orphanPdfTab) { - uiTimeoutRef.current = setTimeout(() => { + uiTimeoutRef.current = window.setTimeout(() => { setOrphanPdfTabAfterDelay(true) }, ORPHAN_UI_TIMEOUT_MS) } else { diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-preview.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-preview.tsx similarity index 100% rename from services/web/frontend/js/features/pdf-preview/components/pdf-preview.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-preview.tsx diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.tsx similarity index 89% rename from services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.tsx index bc51f307ba..fcb83344be 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.jsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.tsx @@ -1,6 +1,5 @@ import classNames from 'classnames' import { memo, useCallback, useEffect, useState, useRef } from 'react' -import PropTypes from 'prop-types' import { useProjectContext } from '../../../shared/context/project-context' import { getJSON } from '../../../infrastructure/fetch-json' import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context' @@ -25,12 +24,20 @@ import MaterialIcon from '@/shared/components/material-icon' import { Spinner } from 'react-bootstrap-5' import { bsVersion } from '@/features/utils/bootstrap-5' import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context' +import useEventListener from '@/shared/hooks/use-event-listener' +import { PdfScrollPosition } from '@/shared/hooks/use-pdf-scroll-position' +import { CursorPosition } from '@/features/ide-react/types/cursor-position' function GoToCodeButton({ position, syncToCode, syncToCodeInFlight, isDetachLayout, +}: { + position: PdfScrollPosition + syncToCode: (position: PdfScrollPosition, visualOffset?: number) => void + syncToCodeInFlight: boolean + isDetachLayout?: boolean }) { const { t } = useTranslation() const tooltipPlacement = isDetachLayout ? 'bottom' : 'right' @@ -89,7 +96,7 @@ function GoToCodeButton({ className={buttonClasses} aria-label={t('go_to_pdf_location_in_code')} bs3Props={{ - bsSize: 'xs', + bsSize: 'xsmall', }} > {buttonIcon} @@ -105,6 +112,12 @@ function GoToPdfButton({ syncToPdfInFlight, isDetachLayout, hasSingleSelectedDoc, +}: { + cursorPosition: CursorPosition | null + syncToPdf: (cursorPosition: CursorPosition | null) => void + syncToPdfInFlight: boolean + hasSingleSelectedDoc: boolean + isDetachLayout?: boolean }) { const { t } = useTranslation() const tooltipPlacement = isDetachLayout ? 'bottom' : 'right' @@ -159,7 +172,7 @@ function GoToPdfButton({ className={buttonClasses} aria-label={t('go_to_code_location_in_pdf')} bs3Props={{ - bsSize: 'xs', + bsSize: 'xsmall', }} > {buttonIcon} @@ -187,22 +200,24 @@ function PdfSynctexControls() { const { findEntityByPath, dirname, pathInFolder } = useFileTreePathContext() const { getCurrentDocumentId, openDocWithId } = useEditorManagerContext() - const [cursorPosition, setCursorPosition] = useState(() => { - const position = localStorage.getItem( - `doc.position.${getCurrentDocumentId()}` - ) - return position ? position.cursorPosition : null - }) + const [cursorPosition, setCursorPosition] = useState( + () => { + const position = localStorage.getItem( + `doc.position.${getCurrentDocumentId()}` + ) + return position ? position.cursorPosition : null + } + ) const isMounted = useIsMounted() const { signal } = useAbortController() - useEffect(() => { - const listener = event => setCursorPosition(event.detail) - window.addEventListener('cursor:editor:update', listener) - return () => window.removeEventListener('cursor:editor:update', listener) - }, []) + const editorUpdateListener = useCallback( + event => setCursorPosition(event.detail), + [] + ) + useEventListener('cursor:editor:update', editorUpdateListener) const [syncToPdfInFlight, setSyncToPdfInFlight] = useState(false) const [syncToCodeInFlight, setSyncToCodeInFlight] = useDetachState( @@ -216,8 +231,17 @@ function PdfSynctexControls() { 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) @@ -286,8 +310,10 @@ function PdfSynctexControls() { const syncToPdf = useCallback( cursorPosition => { + const file = getCurrentFilePath() + const params = new URLSearchParams({ - file: getCurrentFilePath(), + file: file ?? '', line: cursorPosition.row + 1, column: cursorPosition.column, }).toString() @@ -377,13 +403,11 @@ function PdfSynctexControls() { 'detacher' ) - useEffect(() => { - const listener = event => syncToCode(event.detail) - window.addEventListener('synctex:sync-to-position', listener) - return () => { - window.removeEventListener('synctex:sync-to-position', listener) - } - }, [syncToCode]) + const syncToPositionListener = useCallback( + event => syncToCode(event.detail), + [syncToCode] + ) + useEventListener('synctex:sync-to-position', syncToPositionListener) const [hasSingleSelectedDoc, setHasSingleSelectedDoc] = useDetachState( 'has-single-selected-doc', @@ -458,18 +482,3 @@ function PdfSynctexControls() { } export default memo(PdfSynctexControls) - -GoToCodeButton.propTypes = { - isDetachLayout: PropTypes.bool, - position: PropTypes.object.isRequired, - syncToCode: PropTypes.func.isRequired, - syncToCodeInFlight: PropTypes.bool.isRequired, -} - -GoToPdfButton.propTypes = { - cursorPosition: PropTypes.object, - isDetachLayout: PropTypes.bool, - syncToPdf: PropTypes.func.isRequired, - syncToPdfInFlight: PropTypes.bool.isRequired, - hasSingleSelectedDoc: PropTypes.bool.isRequired, -} diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-validation-issue.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-validation-issue.tsx similarity index 76% rename from services/web/frontend/js/features/pdf-preview/components/pdf-validation-issue.jsx rename to services/web/frontend/js/features/pdf-preview/components/pdf-validation-issue.tsx index 5edfef6ef4..6968faacb2 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-validation-issue.jsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-validation-issue.tsx @@ -1,14 +1,8 @@ import { memo } from 'react' import { useTranslation } from 'react-i18next' -import PropTypes from 'prop-types' import PdfLogEntry from './pdf-log-entry' -PdfValidationIssue.propTypes = { - name: PropTypes.string.isRequired, - issue: PropTypes.any, -} - -function PdfValidationIssue({ issue, name }) { +function PdfValidationIssue({ issue, name }: { issue: any; name: string }) { const { t } = useTranslation() switch (name) { @@ -20,12 +14,14 @@ function PdfValidationIssue({ issue, name }) { <>
    {t('project_too_large_please_reduce')}
      - {issue.resources.map(resource => ( -
    • - {resource.path} — {resource.kbSize} - kb -
    • - ))} + {issue.resources.map( + (resource: { path: string; kbSize: number }) => ( +
    • + {resource.path} — {resource.kbSize} + kb +
    • + ) + )}
    } @@ -42,7 +38,7 @@ function PdfValidationIssue({ issue, name }) { <>
    {t('following_paths_conflict')}
      - {issue.map(detail => ( + {issue.map((detail: { path: string }) => (
    • /{detail.path}
    • ))}
    diff --git a/services/web/frontend/js/features/pdf-preview/components/stop-on-first-error-prompt.jsx b/services/web/frontend/js/features/pdf-preview/components/stop-on-first-error-prompt.tsx similarity index 100% rename from services/web/frontend/js/features/pdf-preview/components/stop-on-first-error-prompt.jsx rename to services/web/frontend/js/features/pdf-preview/components/stop-on-first-error-prompt.tsx diff --git a/services/web/frontend/js/features/pdf-preview/util/types.ts b/services/web/frontend/js/features/pdf-preview/util/types.ts new file mode 100644 index 0000000000..78e953a3aa --- /dev/null +++ b/services/web/frontend/js/features/pdf-preview/util/types.ts @@ -0,0 +1,41 @@ +import React from 'react' + +export type LogEntry = { + raw: string + level: ErrorLevel + key: string + file?: string + column?: number + line?: number + ruleId?: string + message?: string + content?: string + type?: string + messageComponent?: React.ReactNode + contentDetails?: string[] +} + +export type ErrorLevel = + | 'error' + | 'warning' + | 'info' + | 'typesetting' + | 'raw' + | 'success' + +export type SourceLocation = { + file?: string + // `line should be either a number or null (i.e. not required), but currently sometimes we get + // an empty string (from BibTeX errors). + line?: number | string | null + column?: number +} + +export type PdfFileData = { path: string; url: string } +type PdfFileArchiveData = { path: string; url: string; fileCount: number } + +export type PdfFileDataList = { + top: PdfFileData[] + other: PdfFileData[] + archive?: PdfFileArchiveData +} diff --git a/services/web/frontend/js/features/preview/components/preview-log-entry-header.jsx b/services/web/frontend/js/features/preview/components/preview-log-entry-header.tsx similarity index 83% rename from services/web/frontend/js/features/preview/components/preview-log-entry-header.jsx rename to services/web/frontend/js/features/preview/components/preview-log-entry-header.tsx index 3c0c230c8f..8a6842f4ff 100644 --- a/services/web/frontend/js/features/preview/components/preview-log-entry-header.jsx +++ b/services/web/frontend/js/features/preview/components/preview-log-entry-header.tsx @@ -1,6 +1,5 @@ -import PropTypes from 'prop-types' import classNames from 'classnames' -import { useState, useRef } from 'react' +import { useState, useRef, MouseEventHandler } from 'react' import { useTranslation } from 'react-i18next' import useResizeObserver from '../hooks/use-resize-observer' import OLTooltip from '@/features/ui/components/ol/ol-tooltip' @@ -8,6 +7,7 @@ import Icon from '../../../shared/components/icon' import OLButton from '@/features/ui/components/ol/ol-button' import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher' import MaterialIcon from '@/shared/components/material-icon' +import { ErrorLevel, SourceLocation } from '@/features/pdf-preview/util/types' function PreviewLogEntryHeader({ sourceLocation, @@ -19,9 +19,19 @@ function PreviewLogEntryHeader({ showCloseButton = false, onSourceLocationClick, onClose, +}: { + headerTitle: string | React.ReactNode + level: ErrorLevel + headerIcon?: React.ReactElement + logType?: string + sourceLocation?: SourceLocation + showSourceLocationLink?: boolean + showCloseButton?: boolean + onSourceLocationClick?: MouseEventHandler + onClose?: () => void }) { const { t } = useTranslation() - const logLocationSpanRef = useRef() + const logLocationSpanRef = useRef(null) const [locationSpanOverflown, setLocationSpanOverflown] = useState(false) useResizeObserver( @@ -52,7 +62,7 @@ function PreviewLogEntryHeader({ location: file + (line ? `, ${line}` : ''), }) - function checkLocationSpanOverflow(observedElement) { + function checkLocationSpanOverflow(observedElement: ResizeObserverEntry) { const spanEl = observedElement.target const isOverflowing = spanEl.scrollWidth > spanEl.clientWidth setLocationSpanOverflown(isOverflowing) @@ -104,7 +114,7 @@ function PreviewLogEntryHeader({
    {headerIcon}
    ) : null}

    {headerTitleText}

    - {locationSpanOverflown && locationLinkText ? ( + {locationSpanOverflown && locationLinkText && locationLink ? ( , + observedData: any, + callback: (observedElement: ResizeObserverEntry) => void +) { + const resizeObserver = useRef() const observe = useCallback(() => { resizeObserver.current = new ResizeObserver(function (elementsObserved) { @@ -9,15 +13,17 @@ function useResizeObserver(observedElement, observedData, callback) { }) }, [callback]) - function unobserve(observedCurrent) { - resizeObserver.current.unobserve(observedCurrent) + function unobserve(observedCurrent: HTMLElement) { + if (resizeObserver.current) { + resizeObserver.current.unobserve(observedCurrent) + } } useLayoutEffect(() => { if ('ResizeObserver' in window) { const observedCurrent = observedElement && observedElement.current if (observedCurrent) { - observe(observedElement.current) + observe() } if (resizeObserver.current && observedCurrent) { diff --git a/services/web/frontend/js/features/ui/components/types/button-props.ts b/services/web/frontend/js/features/ui/components/types/button-props.ts index e98538e168..fad0c49c6a 100644 --- a/services/web/frontend/js/features/ui/components/types/button-props.ts +++ b/services/web/frontend/js/features/ui/components/types/button-props.ts @@ -34,4 +34,5 @@ export type ButtonProps = { | 'premium' | 'premium-secondary' | 'link' + | 'info' } diff --git a/services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsHints.jsx b/services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsHints.tsx similarity index 98% rename from services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsHints.jsx rename to services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsHints.tsx index 8c26517468..dd188ea54e 100644 --- a/services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsHints.jsx +++ b/services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsHints.tsx @@ -1,11 +1,16 @@ -import PropTypes from 'prop-types' import { packageSuggestionsForCommands, packageSuggestionsForEnvironments, } from './HumanReadableLogsPackageSuggestions' import getMeta from '@/utils/meta' -function WikiLink({ url, children }) { +function WikiLink({ + url, + children, +}: { + url: string + children: React.ReactNode +}) { if (getMeta('ol-wikiEnabled')) { return ( @@ -17,12 +22,12 @@ function WikiLink({ url, children }) { } } -WikiLink.propTypes = { - url: PropTypes.string.isRequired, - children: PropTypes.node.isRequired, +type LogHint = { + extraInfoURL?: string | null + formattedContent: (details?: string[]) => React.ReactNode } -const hints = { +const hints: { [ruleId: string]: LogHint } = { hint_misplaced_alignment_tab_character: { extraInfoURL: 'https://www.overleaf.com/learn/Errors/Misplaced_alignment_tab_character_%26', diff --git a/services/web/frontend/js/shared/context/local-compile-context.tsx b/services/web/frontend/js/shared/context/local-compile-context.tsx index 9eaec47428..aa4e6d6378 100644 --- a/services/web/frontend/js/shared/context/local-compile-context.tsx +++ b/services/web/frontend/js/shared/context/local-compile-context.tsx @@ -42,6 +42,7 @@ import { PdfScrollPosition, usePdfScrollPosition, } from '@/shared/hooks/use-pdf-scroll-position' +import { PdfFileDataList } from '@/features/pdf-preview/util/types' type PdfFile = Record @@ -54,7 +55,7 @@ export type CompileContext = { deliveryLatencies: Record draft: boolean error?: string - fileList?: Record + fileList?: PdfFileDataList hasChanges: boolean hasShortCompileTimeout: boolean highlights?: Record[] @@ -208,7 +209,7 @@ export const LocalCompileProvider: FC = ({ children }) => { const [error, setError] = useState() // the list of files that can be downloaded - const [fileList, setFileList] = useState>() + const [fileList, setFileList] = useState() // the raw contents of the log file const [rawLog, setRawLog] = useState() diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts index 8b63bbbe1f..c077bbfa00 100644 --- a/services/web/frontend/js/utils/meta.ts +++ b/services/web/frontend/js/utils/meta.ts @@ -126,6 +126,7 @@ export interface Meta { 'ol-inviterName': string 'ol-isExternalAuthenticationSystemUsed': boolean 'ol-isManagedAccount': boolean + 'ol-isPaywallChangeCompileTimeoutEnabled': boolean 'ol-isProfessional': boolean 'ol-isRegisteredViaGoogle': boolean 'ol-isRestrictedTokenMember': boolean diff --git a/services/web/frontend/stories/pdf-log-entry.stories.tsx b/services/web/frontend/stories/pdf-log-entry.stories.tsx index 0e4823a520..aeb735fcb3 100644 --- a/services/web/frontend/stories/pdf-log-entry.stories.tsx +++ b/services/web/frontend/stories/pdf-log-entry.stories.tsx @@ -7,6 +7,7 @@ import { useMeta } from './hooks/use-meta' import { FC, ReactNode } from 'react' import { useScope } from './hooks/use-scope' import { EditorView } from '@codemirror/view' +import { LogEntry } from '@/features/pdf-preview/util/types' const fakeSourceLocation = { file: 'file.tex', @@ -14,7 +15,7 @@ const fakeSourceLocation = { column: 5, } -const fakeLogEntry = { +const fakeLogEntry: LogEntry = { key: 'fake', ruleId: 'hint_misplaced_alignment_tab_character', message: 'Fake message', diff --git a/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx index d1c0a81c6f..5b4efdd650 100644 --- a/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx +++ b/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx @@ -11,6 +11,7 @@ import { } from '@/features/ide-react/context/editor-manager-context' import { EditorView } from '@codemirror/view' import { OpenDocuments } from '@/features/ide-react/editor/open-documents' +import { LogEntry } from '@/features/pdf-preview/util/types' describe('', function () { const fakeFindEntityResult: FindResult = { @@ -48,7 +49,7 @@ describe('', function () { ) } - const logEntries = [ + const logEntries: LogEntry[] = [ { file: 'main.tex', line: 9,