Merge pull request #23502 from overleaf/dp-pdf-typescript

Convert PDF Preview components to typescript

GitOrigin-RevId: 34594d21143727fa42b8b595aa12125a4dd7ae5e
This commit is contained in:
David
2025-02-12 15:36:19 +00:00
committed by Copybot
parent 1a45b909c3
commit 1d648f9755
30 changed files with 253 additions and 228 deletions

View File

@@ -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 <DetachCompileButton />
}
const layoutContextPropTypes = {
detachRole: PropTypes.string,
detachIsLinked: PropTypes.bool,
}
export default memo(DetachCompileButtonWrapper)

View File

@@ -17,6 +17,10 @@ function PdfDownloadFilesButton() {
const { t } = useTranslation()
if (!fileList) {
return null
}
return (
<BootstrapVersionSwitcher
bs3={
@@ -42,7 +46,6 @@ function PdfDownloadFilesButton() {
<DropdownToggle
id="dropdown-files-logs-pane"
variant="secondary"
title={t('other_logs_and_files')}
size="sm"
disabled={compiling || !fileList}
>

View File

@@ -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 }) {
</li>
))}
{fileList.archive?.fileCount > 0 && (
<li role="menuitem">
<DropdownItem
role="link"
download={basename(fileList.archive)}
href={fileList.archive.url}
>
{t('download_all')} ({fileList.archive.fileCount})
</DropdownItem>
</li>
)}
{fileList.archive?.fileCount !== undefined &&
fileList.archive?.fileCount > 0 && (
<li role="menuitem">
<DropdownItem
role="link"
download={basename(fileList.archive)}
href={fileList.archive.url}
>
{t('download_all')} ({fileList.archive.fileCount})
</DropdownItem>
</li>
)}
</>
}
/>
)
}
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)

View File

@@ -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({
</div>
)
}
PdfLogEntryContent.propTypes = {
rawContent: PropTypes.string,
formattedContent: PropTypes.node,
extraInfoURL: PropTypes.string,
index: PropTypes.number,
logEntry: PropTypes.any,
}

View File

@@ -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({
</div>
)
}
PdfLogEntryRawContent.propTypes = {
rawContent: PropTypes.string.isRequired,
collapsedSize: PropTypes.number,
}

View File

@@ -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<HTMLButtonElement> =
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 (
<div
@@ -80,33 +105,4 @@ function PdfLogEntry({
)
}
PdfLogEntry.propTypes = {
ruleId: PropTypes.string,
sourceLocation: PreviewLogEntryHeader.propTypes.sourceLocation,
headerTitle: PreviewLogEntryHeader.propTypes.headerTitle,
headerIcon: PropTypes.element,
rawContent: PropTypes.string,
logType: PropTypes.string,
formattedContent: PropTypes.node,
extraInfoURL: PropTypes.string,
level: PropTypes.oneOf([
'error',
'warning',
'info',
'typesetting',
'raw',
'success',
]).isRequired,
customClass: PropTypes.string,
showSourceLocationLink: PropTypes.bool,
showCloseButton: PropTypes.bool,
entryAriaLabel: PropTypes.string,
contentDetails: PropTypes.arrayOf(PropTypes.string),
onSourceLocationClick: PropTypes.func,
onClose: PropTypes.func,
index: PropTypes.number,
logEntry: PropTypes.any,
id: PropTypes.string,
}
export default memo(PdfLogEntry)

View File

@@ -1,16 +1,27 @@
import { memo } from 'react'
import PropTypes from 'prop-types'
import { ElementType, memo } from 'react'
import { useTranslation } from 'react-i18next'
import PreviewLogsPaneMaxEntries from '../../preview/components/preview-logs-pane-max-entries'
import PdfLogEntry from './pdf-log-entry'
import { useDetachCompileContext } from '../../../shared/context/detach-compile-context'
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
import { LogEntry } from '../util/types'
const LOG_PREVIEW_LIMIT = 100
const pdfLogEntriesComponents = importOverleafModules('pdfLogEntriesComponents')
const pdfLogEntriesComponents = importOverleafModules(
'pdfLogEntriesComponents'
) as {
import: { default: ElementType }
path: string
}[]
function PdfLogsEntries({ entries, hasErrors }) {
function PdfLogsEntries({
entries,
hasErrors,
}: {
entries: LogEntry[]
hasErrors?: boolean
}) {
const { t } = useTranslation()
const { syncToEntry } = useDetachCompileContext()
const logEntries = entries.slice(0, LOG_PREVIEW_LIMIT)
@@ -57,9 +68,5 @@ function PdfLogsEntries({ entries, hasErrors }) {
</>
)
}
PdfLogsEntries.propTypes = {
entries: PropTypes.arrayOf(PropTypes.object),
hasErrors: PropTypes.bool,
}
export default memo(PdfLogsEntries)

View File

@@ -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), () => (
<PdfPreviewErrorBoundaryFallback type="logs" />
))

View File

@@ -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

View File

@@ -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() {
</ErrorLogEntry>
)
}
TimedOutLogEntry.propTypes = {}

View File

@@ -18,18 +18,18 @@ const ORPHAN_UI_TIMEOUT_MS = 5000
function PdfPreviewHybridToolbar() {
const { detachRole, detachIsLinked } = useLayoutContext()
const uiTimeoutRef = useRef()
const uiTimeoutRef = useRef<number>()
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 {

View File

@@ -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<CursorPosition | null>(
() => {
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,
}

View File

@@ -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 }) {
<>
<div>{t('project_too_large_please_reduce')}</div>
<ul className="list-no-margin-bottom">
{issue.resources.map(resource => (
<li key={resource.path}>
{resource.path} &mdash; {resource.kbSize}
kb
</li>
))}
{issue.resources.map(
(resource: { path: string; kbSize: number }) => (
<li key={resource.path}>
{resource.path} &mdash; {resource.kbSize}
kb
</li>
)
)}
</ul>
</>
}
@@ -42,7 +38,7 @@ function PdfValidationIssue({ issue, name }) {
<>
<div>{t('following_paths_conflict')}</div>
<ul className="list-no-margin-bottom">
{issue.map(detail => (
{issue.map((detail: { path: string }) => (
<li key={detail.path}>/{detail.path}</li>
))}
</ul>

View File

@@ -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
}

View File

@@ -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<HTMLButtonElement>
onClose?: () => void
}) {
const { t } = useTranslation()
const logLocationSpanRef = useRef()
const logLocationSpanRef = useRef<HTMLSpanElement>(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({
<div className="log-entry-header-icon-container">{headerIcon}</div>
) : null}
<h3 className="log-entry-header-title">{headerTitleText}</h3>
{locationSpanOverflown && locationLinkText ? (
{locationSpanOverflown && locationLinkText && locationLink ? (
<OLTooltip
id={locationLinkText}
description={locationLinkText}
@@ -130,23 +140,4 @@ function PreviewLogEntryHeader({
)
}
PreviewLogEntryHeader.propTypes = {
sourceLocation: PropTypes.shape({
file: PropTypes.string,
// `line should be either a number or null (i.e. not required), but currently sometimes we get
// an empty string (from BibTeX errors), which is why we're using `any` here. We should revert
// to PropTypes.number (not required) once we fix that.
line: PropTypes.any,
column: PropTypes.any,
}),
level: PropTypes.string.isRequired,
headerTitle: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
headerIcon: PropTypes.element,
logType: PropTypes.string,
showSourceLocationLink: PropTypes.bool,
showCloseButton: PropTypes.bool,
onSourceLocationClick: PropTypes.func,
onClose: PropTypes.func,
}
export default PreviewLogEntryHeader

View File

@@ -1,7 +1,11 @@
import { useLayoutEffect, useRef, useCallback } from 'react'
import { useLayoutEffect, useRef, useCallback, RefObject } from 'react'
function useResizeObserver(observedElement, observedData, callback) {
const resizeObserver = useRef()
function useResizeObserver(
observedElement: RefObject<HTMLElement>,
observedData: any,
callback: (observedElement: ResizeObserverEntry) => void
) {
const resizeObserver = useRef<ResizeObserver>()
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) {

View File

@@ -34,4 +34,5 @@ export type ButtonProps = {
| 'premium'
| 'premium-secondary'
| 'link'
| 'info'
}

View File

@@ -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 (
<a href={url} target="_blank" rel="noopener">
@@ -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',

View File

@@ -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<string, any>
@@ -54,7 +55,7 @@ export type CompileContext = {
deliveryLatencies: Record<string, any>
draft: boolean
error?: string
fileList?: Record<string, any>
fileList?: PdfFileDataList
hasChanges: boolean
hasShortCompileTimeout: boolean
highlights?: Record<string, any>[]
@@ -208,7 +209,7 @@ export const LocalCompileProvider: FC = ({ children }) => {
const [error, setError] = useState<string>()
// the list of files that can be downloaded
const [fileList, setFileList] = useState<Record<string, any[]>>()
const [fileList, setFileList] = useState<PdfFileDataList>()
// the raw contents of the log file
const [rawLog, setRawLog] = useState<string>()

View File

@@ -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

View File

@@ -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',

View File

@@ -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('<PdfLogsEntries/>', function () {
const fakeFindEntityResult: FindResult = {
@@ -48,7 +49,7 @@ describe('<PdfLogsEntries/>', function () {
)
}
const logEntries = [
const logEntries: LogEntry[] = [
{
file: 'main.tex',
line: 9,