mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
[web] Migrate PDF Logs to BS5 (#21062)
* [web] Migrate Logs components JSX to Bootstrap 5 * [web] Migrate logs.less to logs.scss * [web] Remove unused class names * [storybook] Define default Bootstrap version in Storybook This prevents some warning in the console * [storybook] Update pdf-preview.stories.jsx * [storybook] Add pdf-log-entry.stories.tsx * [storybook] Force re-renders when switching BS version * [web] Keep files dropdown menu in bounds * [web] Make files dropdown items not bold in BS5 * [web] Revert unrelated change * [web] Fixup PreviewLogsPaneMaxEntries * [web] Add style for log-entry-content-link * [web] Replace log-entry by OLNotification in `PdfCodeCheckFailedNotice` * [web] Use `BootstrapVersionSwitcher` instead of `isBootstrap5` * [web] Rename `DropdownBS3` to `BS3Dropdown` * [web] Reuse variables for `toolbar-height` and `toolbar-small-height` * [web] Set `id` on `DropdownToggle` not `Dropdown` * [web] Set `log-entry-btn-expand-collapse` in BS3 only * [web] Remove `block: true` from StartFreeTrialButton in BS3 * [web] Remove unnecessary CSS in `.log-entry-header-link` * [web] Use semantic color names * Migrate the downloadable pdf file list to Bootstrap 5 * Remove nested BootstrapVersionSwitcher, fix "key" prop * Update roles to: `<li role="menuitem">` `<a role="link">` * Update `log-entry-header-link`: variant ghost and fix colors --------- Co-authored-by: Rebeka <o.dekany@gmail.com> GitOrigin-RevId: 89848970ab5d8a8c135335386caf24363f69a34c
This commit is contained in:
parent
d77ca18b0b
commit
30860ae9f9
22 changed files with 578 additions and 239 deletions
|
@ -177,7 +177,11 @@ const preview: Preview = {
|
|||
return (
|
||||
<>
|
||||
{activeStyle && <style>{activeStyle.default}</style>}
|
||||
<Story {...context} />
|
||||
<Story
|
||||
{...context}
|
||||
// force re-renders when switching between Bootstrap versions
|
||||
key={bootstrapVersion}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
},
|
||||
|
|
|
@ -9,6 +9,9 @@ export const bsVersionDecorator: Meta = {
|
|||
description: 'Bootstrap version for components',
|
||||
control: { type: 'inline-radio' },
|
||||
options: ['3', '5'],
|
||||
table: {
|
||||
defaultValue: { summary: '3' },
|
||||
},
|
||||
},
|
||||
},
|
||||
args: {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import Icon from '../../../shared/components/icon'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { memo } from 'react'
|
||||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
function PdfClearCacheButton() {
|
||||
const { compiling, clearCache, clearingCache } = useCompileContext()
|
||||
|
@ -10,17 +11,29 @@ function PdfClearCacheButton() {
|
|||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Button
|
||||
bsSize="small"
|
||||
bsStyle="danger"
|
||||
<OLButton
|
||||
size="sm"
|
||||
variant="danger"
|
||||
className="logs-pane-actions-clear-cache"
|
||||
onClick={() => clearCache()}
|
||||
isLoading={clearingCache}
|
||||
disabled={clearingCache || compiling}
|
||||
leadingIcon="delete"
|
||||
>
|
||||
{clearingCache ? <Icon type="refresh" spin /> : <Icon type="trash-o" />}
|
||||
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<>
|
||||
{clearingCache ? (
|
||||
<Icon type="refresh" spin />
|
||||
) : (
|
||||
<Icon type="trash-o" />
|
||||
)}
|
||||
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<span>{t('clear_cached_files')}</span>
|
||||
</Button>
|
||||
</OLButton>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,29 @@
|
|||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
function PdfCodeCheckFailedNotice() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="log-entry">
|
||||
<div className="log-entry-header log-entry-header-error">
|
||||
<div className="log-entry-header-icon-container">
|
||||
<Icon type="exclamation-triangle" fw />
|
||||
</div>
|
||||
<h3 className="log-entry-header-title">
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={
|
||||
<>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<>
|
||||
<Icon type="exclamation-triangle" fw />{' '}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{t('code_check_failed_explanation')}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
className={bsVersion({ bs5: 'm-0', bs3: 'mb-2' })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import { Dropdown } from 'react-bootstrap'
|
||||
import { Dropdown as BS3Dropdown } from 'react-bootstrap'
|
||||
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import PdfFileList from './pdf-file-list'
|
||||
import ControlledDropdown from '../../../shared/components/controlled-dropdown'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
function PdfDownloadFilesButton() {
|
||||
const { compiling, fileList } = useCompileContext()
|
||||
|
@ -11,22 +18,42 @@ function PdfDownloadFilesButton() {
|
|||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<ControlledDropdown
|
||||
id="dropdown-files-logs-pane"
|
||||
dropup
|
||||
pullRight
|
||||
disabled={compiling || !fileList}
|
||||
>
|
||||
<Dropdown.Toggle
|
||||
className="dropdown-toggle btn-secondary-info btn-secondary"
|
||||
title={t('other_logs_and_files')}
|
||||
bsSize="small"
|
||||
bsStyle={null}
|
||||
/>
|
||||
<Dropdown.Menu id="dropdown-files-logs-pane-list">
|
||||
<PdfFileList fileList={fileList} />
|
||||
</Dropdown.Menu>
|
||||
</ControlledDropdown>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<ControlledDropdown
|
||||
id="dropdown-files-logs-pane"
|
||||
dropup
|
||||
pullRight
|
||||
disabled={compiling || !fileList}
|
||||
>
|
||||
<BS3Dropdown.Toggle
|
||||
className="dropdown-toggle btn-secondary-info btn-secondary"
|
||||
title={t('other_logs_and_files')}
|
||||
bsSize="small"
|
||||
bsStyle={null}
|
||||
/>
|
||||
<BS3Dropdown.Menu id="dropdown-files-logs-pane-list">
|
||||
<PdfFileList fileList={fileList} />
|
||||
</BS3Dropdown.Menu>
|
||||
</ControlledDropdown>
|
||||
}
|
||||
bs5={
|
||||
<Dropdown drop="up">
|
||||
<DropdownToggle
|
||||
id="dropdown-files-logs-pane"
|
||||
variant="secondary"
|
||||
title={t('other_logs_and_files')}
|
||||
size="sm"
|
||||
disabled={compiling || !fileList}
|
||||
>
|
||||
{t('other_logs_and_files')}
|
||||
</DropdownToggle>
|
||||
<DropdownMenu id="dropdown-files-logs-pane-list">
|
||||
<PdfFileList fileList={fileList} />
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import { MenuItem } from 'react-bootstrap'
|
||||
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'
|
||||
|
||||
function PdfFileList({ fileList }) {
|
||||
const { t } = useTranslation()
|
||||
|
@ -15,37 +21,93 @@ function PdfFileList({ fileList }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem header>{t('other_output_files')}</MenuItem>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<>
|
||||
<BS3MenuItem header>{t('other_output_files')}</BS3MenuItem>
|
||||
|
||||
{fileList.top.map(file => (
|
||||
<MenuItem download={basename(file)} href={file.url} key={file.path}>
|
||||
<b>{file.path}</b>
|
||||
</MenuItem>
|
||||
))}
|
||||
{fileList.top.map(file => (
|
||||
<BS3MenuItem
|
||||
download={basename(file)}
|
||||
href={file.url}
|
||||
key={file.path}
|
||||
>
|
||||
<b>{file.path}</b>
|
||||
</BS3MenuItem>
|
||||
))}
|
||||
|
||||
{fileList.other.length > 0 && fileList.top.length > 0 && (
|
||||
<MenuItem divider />
|
||||
)}
|
||||
{fileList.other.length > 0 && fileList.top.length > 0 && (
|
||||
<BS3MenuItem divider />
|
||||
)}
|
||||
|
||||
{fileList.other.map(file => (
|
||||
<MenuItem download={basename(file)} href={file.url} key={file.path}>
|
||||
<b>{file.path}</b>
|
||||
</MenuItem>
|
||||
))}
|
||||
{fileList.other.map(file => (
|
||||
<BS3MenuItem
|
||||
download={basename(file)}
|
||||
href={file.url}
|
||||
key={file.path}
|
||||
>
|
||||
<b>{file.path}</b>
|
||||
</BS3MenuItem>
|
||||
))}
|
||||
|
||||
{fileList.archive?.fileCount > 0 && (
|
||||
<MenuItem
|
||||
download={basename(fileList.archive)}
|
||||
href={fileList.archive.url}
|
||||
key={fileList.archive.path}
|
||||
>
|
||||
<b>
|
||||
{t('download_all')} ({fileList.archive.fileCount})
|
||||
</b>
|
||||
</MenuItem>
|
||||
)}
|
||||
</>
|
||||
{fileList.archive?.fileCount && fileList.archive?.fileCount > 0 && (
|
||||
<BS3MenuItem
|
||||
download={basename(fileList.archive)}
|
||||
href={fileList.archive.url}
|
||||
>
|
||||
<b>
|
||||
{t('download_all')} ({fileList.archive.fileCount})
|
||||
</b>
|
||||
</BS3MenuItem>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
bs5={
|
||||
<>
|
||||
<DropdownHeader>{t('other_output_files')}</DropdownHeader>
|
||||
|
||||
{fileList.top.map(file => (
|
||||
<li key={file.path} role="menuitem">
|
||||
<DropdownItem
|
||||
role="link"
|
||||
download={basename(file)}
|
||||
href={file.url}
|
||||
>
|
||||
{file.path}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
))}
|
||||
|
||||
{fileList.other.length > 0 && fileList.top.length > 0 && (
|
||||
<DropdownDivider divider />
|
||||
)}
|
||||
|
||||
{fileList.other.map(file => (
|
||||
<li key={file.path} role="menuitem">
|
||||
<DropdownItem
|
||||
role="link"
|
||||
download={basename(file)}
|
||||
href={file.url}
|
||||
>
|
||||
{file.path}
|
||||
</DropdownItem>
|
||||
</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>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useCallback, useState } from 'react'
|
|||
import { useResizeObserver } from '../../../shared/hooks/use-resize-observer'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import classNames from 'classnames'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
|
@ -44,10 +44,13 @@ export default function PdfLogEntryRawContent({
|
|||
'log-entry-content-button-container-collapsed': !expanded,
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
bsSize="xs"
|
||||
bsStyle={null}
|
||||
className="log-entry-btn-expand-collapse btn-secondary"
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
bs3Props={{
|
||||
bsSize: 'xsmall',
|
||||
className: 'log-entry-btn-expand-collapse',
|
||||
}}
|
||||
onClick={() => setExpanded(value => !value)}
|
||||
>
|
||||
{expanded ? (
|
||||
|
@ -59,7 +62,7 @@ export default function PdfLogEntryRawContent({
|
|||
<Icon type="angle-down" /> {t('expand')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</OLButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import PdfLogEntry from './pdf-log-entry'
|
||||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
||||
import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
|
||||
|
@ -23,9 +23,10 @@ function PdfPreviewError({ error }) {
|
|||
i18nKey="something_went_wrong_rendering_pdf_expected"
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<Button
|
||||
bsSize="xs"
|
||||
bsStyle="info"
|
||||
<OLButton
|
||||
variant="info"
|
||||
size="sm"
|
||||
bs3Props={{ bsSize: 'xsmall' }}
|
||||
onClick={() => startCompile()}
|
||||
/>,
|
||||
]}
|
||||
|
@ -157,7 +158,7 @@ function PdfPreviewError({ error }) {
|
|||
<ErrorLogEntry title={t('no_pdf_error_title')}>
|
||||
{t('no_pdf_error_explanation')}
|
||||
|
||||
<ul className="log-entry-formatted-content-list">
|
||||
<ul className="my-1 ps-3">
|
||||
<li>{t('no_pdf_error_reason_unrecoverable_error')}</li>
|
||||
<li>
|
||||
<Trans
|
||||
|
@ -282,9 +283,10 @@ function TimedOutLogEntry() {
|
|||
i18nKey="project_timed_out_enable_stop_on_first_error"
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<Button
|
||||
bsSize="xs"
|
||||
bsStyle="info"
|
||||
<OLButton
|
||||
variant="info"
|
||||
size="sm"
|
||||
bs3Props={{ bsSize: 'xsmall' }}
|
||||
onClick={handleEnableStopOnFirstErrorClick}
|
||||
/>,
|
||||
]}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback } from 'react'
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import PdfLogEntry from './pdf-log-entry'
|
||||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
||||
import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
|
||||
|
@ -28,9 +28,14 @@ export default function StopOnFirstErrorPrompt() {
|
|||
// eslint-disable-next-line react/jsx-key
|
||||
components={[<strong />]}
|
||||
/>{' '}
|
||||
<Button bsSize="xs" bsStyle="info" onClick={handleDisableButtonClick}>
|
||||
<OLButton
|
||||
variant="info"
|
||||
size="sm"
|
||||
onClick={handleDisableButtonClick}
|
||||
bs3Props={{ bsSize: 'xsmall' }}
|
||||
>
|
||||
{t('disable_stop_on_first_error')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
</>
|
||||
}
|
||||
level="info"
|
||||
|
|
|
@ -4,7 +4,7 @@ import StartFreeTrialButton from '../../../shared/components/start-free-trial-bu
|
|||
import { memo, useCallback } from 'react'
|
||||
import PdfLogEntry from './pdf-log-entry'
|
||||
import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
import getMeta from '@/utils/meta'
|
||||
|
@ -84,13 +84,7 @@ const CompileTimeout = memo(function CompileTimeout({
|
|||
<p className="text-center">
|
||||
<StartFreeTrialButton
|
||||
source="compile-timeout"
|
||||
buttonProps={{
|
||||
variant: 'primary',
|
||||
className: 'row-spaced-small',
|
||||
bs3Props: {
|
||||
block: true,
|
||||
},
|
||||
}}
|
||||
buttonProps={{ variant: 'primary', className: 'w-100' }}
|
||||
>
|
||||
{hasNewPaywallCta
|
||||
? t('get_more_compile_time')
|
||||
|
@ -178,10 +172,12 @@ const PreventTimeoutHelpMessage = memo(function PreventTimeoutHelpMessage({
|
|||
i18nKey="enable_stop_on_first_error_under_recompile_dropdown_menu"
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<Button
|
||||
bsSize="xs"
|
||||
bsStyle="info-ghost-inline"
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link fw-bold"
|
||||
size="sm"
|
||||
onClick={handleEnableStopOnFirstErrorClick}
|
||||
bs3Props={{ bsSize: 'xsmall' }}
|
||||
/>,
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
|
|
|
@ -3,8 +3,11 @@ import classNames from 'classnames'
|
|||
import { useState, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useResizeObserver from '../hooks/use-resize-observer'
|
||||
import Tooltip from '../../../shared/components/tooltip'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
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'
|
||||
|
||||
function PreviewLogEntryHeader({
|
||||
sourceLocation,
|
||||
|
@ -72,18 +75,25 @@ function PreviewLogEntryHeader({
|
|||
// This essentially tells the browser that, althought the text is laid out from right-to-left,
|
||||
// the wrapped portion of text should follow left-to-right writing rules.
|
||||
const locationLink = locationLinkText ? (
|
||||
<button
|
||||
<OLButton
|
||||
variant="ghost"
|
||||
className={logEntryLocationBtnClasses}
|
||||
type="button"
|
||||
aria-label={headerLogLocationTitle}
|
||||
onClick={onSourceLocationClick}
|
||||
>
|
||||
<Icon type="chain" />
|
||||
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<>
|
||||
<Icon type="chain" />
|
||||
|
||||
</>
|
||||
}
|
||||
bs5={<MaterialIcon type="link" />}
|
||||
/>
|
||||
<span ref={logLocationSpanRef} className="log-entry-header-link-location">
|
||||
{`\u202A${locationLinkText}\u202C`}
|
||||
</span>
|
||||
</button>
|
||||
</OLButton>
|
||||
) : null
|
||||
|
||||
const headerTitleText = logType ? `${logType} ${headerTitle}` : headerTitle
|
||||
|
@ -95,26 +105,26 @@ function PreviewLogEntryHeader({
|
|||
) : null}
|
||||
<h3 className="log-entry-header-title">{headerTitleText}</h3>
|
||||
{locationSpanOverflown && locationLinkText ? (
|
||||
<Tooltip
|
||||
<OLTooltip
|
||||
id={locationLinkText}
|
||||
description={locationLinkText}
|
||||
overlayProps={{ placement: 'left' }}
|
||||
tooltipProps={{ className: 'log-location-tooltip' }}
|
||||
>
|
||||
{locationLink}
|
||||
</Tooltip>
|
||||
</OLTooltip>
|
||||
) : (
|
||||
locationLink
|
||||
)}
|
||||
{showCloseButton ? (
|
||||
<button
|
||||
<OLButton
|
||||
variant="link"
|
||||
className="btn-inline-link log-entry-header-link"
|
||||
type="button"
|
||||
aria-label={t('dismiss_error_popup')}
|
||||
onClick={onClose}
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</OLButton>
|
||||
) : null}
|
||||
</header>
|
||||
)
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import PreviewLogEntryHeader from './preview-log-entry-header'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
||||
import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
function PreviewLogsPaneMaxEntries({ totalEntries, entriesShown, hasErrors }) {
|
||||
const { t } = useTranslation()
|
||||
|
@ -34,16 +36,22 @@ function PreviewLogsPaneMaxEntries({ totalEntries, entriesShown, hasErrors }) {
|
|||
{hasErrors && !stoppedOnFirstError ? (
|
||||
<>
|
||||
<p>
|
||||
<Icon type="lightbulb-o" />
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="lightbulb-o" />}
|
||||
bs5={
|
||||
<MaterialIcon type="lightbulb" className="align-middle" />
|
||||
}
|
||||
/>
|
||||
|
||||
<strong>{t('tip')}: </strong>
|
||||
<Trans
|
||||
i18nKey="log_entry_maximum_entries_enable_stop_on_first_error"
|
||||
components={[
|
||||
<Button
|
||||
<OLButton
|
||||
variant="info"
|
||||
size="sm"
|
||||
key="enable-stop-on-first-error"
|
||||
bsSize="xs"
|
||||
bsStyle="info"
|
||||
bs3Props={{ bsSize: 'xsmall' }}
|
||||
onClick={handleEnableStopOnFirstErrorClick}
|
||||
/>,
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||
|
@ -58,7 +66,10 @@ function PreviewLogsPaneMaxEntries({ totalEntries, entriesShown, hasErrors }) {
|
|||
</>
|
||||
) : (
|
||||
<p>
|
||||
<Icon type="lightbulb-o" />
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<Icon type="lightbulb-o" />}
|
||||
bs5={<MaterialIcon type="lightbulb" className="align-middle" />}
|
||||
/>
|
||||
|
||||
<strong>{t('tip')}: </strong>
|
||||
{t('log_entry_maximum_entries_see_full_logs')}
|
||||
|
|
|
@ -508,6 +508,8 @@ const hints = {
|
|||
},
|
||||
}
|
||||
|
||||
export const ruleIds = Object.keys(hints)
|
||||
|
||||
if (!getMeta('ol-wikiEnabled')) {
|
||||
Object.keys(hints).forEach(ruleId => {
|
||||
hints[ruleId].extraInfoURL = null
|
||||
|
|
75
services/web/frontend/stories/pdf-log-entry.stories.tsx
Normal file
75
services/web/frontend/stories/pdf-log-entry.stories.tsx
Normal file
|
@ -0,0 +1,75 @@
|
|||
import PdfLogEntry from '@/features/pdf-preview/components/pdf-log-entry'
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { bsVersionDecorator } from '../../.storybook/utils/with-bootstrap-switcher'
|
||||
import { ruleIds } from '@/ide/human-readable-logs/HumanReadableLogsHints'
|
||||
import { ScopeDecorator } from './decorators/scope'
|
||||
import { useMeta } from './hooks/use-meta'
|
||||
import { FC, ReactNode } from 'react'
|
||||
import { useScope } from './hooks/use-scope'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
|
||||
const fakeSourceLocation = {
|
||||
file: 'file.tex',
|
||||
line: 12,
|
||||
column: 5,
|
||||
}
|
||||
|
||||
const fakeLogEntry = {
|
||||
key: 'fake',
|
||||
ruleId: 'hint_misplaced_alignment_tab_character',
|
||||
message: 'Fake message',
|
||||
messageComponent: 'Fake message component',
|
||||
content: 'Fake content',
|
||||
type: 'Error: ',
|
||||
level: 'error',
|
||||
contentDetails: ['Fake detail 1', 'Fake detail 2'],
|
||||
file: 'fake.tex',
|
||||
line: 12,
|
||||
column: 5,
|
||||
raw: 'Fake raw',
|
||||
}
|
||||
|
||||
const fakeArgs = {
|
||||
headerTitle: 'PDF Preview',
|
||||
formattedContent: 'This is a log entry',
|
||||
level: 'error' as const,
|
||||
extraInfoURL: 'https://example.com',
|
||||
showCloseButton: true,
|
||||
showSourceLocationLink: true,
|
||||
rawContent: 'This is a raw log entry',
|
||||
contentDetails: ['detail 1', 'detail 2'],
|
||||
ruleId: 'hint_misplaced_alignment_tab_character' as const,
|
||||
sourceLocation: fakeSourceLocation,
|
||||
logEntry: fakeLogEntry,
|
||||
logType: 'Fake type',
|
||||
}
|
||||
|
||||
const meta: Meta<typeof PdfLogEntry> = {
|
||||
title: 'Editor / PDF Preview / Logs',
|
||||
component: PdfLogEntry,
|
||||
// @ts-ignore
|
||||
decorators: [ScopeDecorator],
|
||||
argTypes: {
|
||||
ruleId: { control: 'select', options: [...ruleIds, 'other'] },
|
||||
...bsVersionDecorator.argTypes,
|
||||
},
|
||||
args: fakeArgs,
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof PdfLogEntry>
|
||||
|
||||
const Provider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
useMeta({ 'ol-showAiErrorAssistant': true })
|
||||
useScope({ 'editor.view': new EditorView({ doc: '\\begin{document' }) })
|
||||
return <div className="logs-pane p-2">{children}</div>
|
||||
}
|
||||
|
||||
export const PdfLogEntryWithControls: Story = {
|
||||
render: args => (
|
||||
<Provider>
|
||||
<PdfLogEntry {...args} />
|
||||
</Provider>
|
||||
),
|
||||
}
|
|
@ -23,6 +23,11 @@ import { cloneDeep } from 'lodash'
|
|||
import { ScopeDecorator } from './decorators/scope'
|
||||
import { PdfPreviewProvider } from '@/features/pdf-preview/components/pdf-preview-provider'
|
||||
import { bsVersionDecorator } from '../../.storybook/utils/with-bootstrap-switcher'
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownMenu,
|
||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
export default {
|
||||
title: 'Editor / PDF Preview',
|
||||
|
@ -286,7 +291,7 @@ export const DisplayError = () => {
|
|||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="logs-pane">
|
||||
{compileErrors.map(error => (
|
||||
<div
|
||||
key={error}
|
||||
|
@ -296,7 +301,7 @@ export const DisplayError = () => {
|
|||
<PdfPreviewError error={error} />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -320,11 +325,22 @@ export const FileList = () => {
|
|||
}, [])
|
||||
|
||||
return (
|
||||
<div className="dropdown open">
|
||||
<div className="dropdown-menu">
|
||||
<PdfFileList fileList={fileList} />
|
||||
</div>
|
||||
</div>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<div className="dropdown open">
|
||||
<div className="dropdown-menu">
|
||||
<PdfFileList fileList={fileList} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
bs5={
|
||||
<Dropdown>
|
||||
<DropdownMenu id="dropdown-files-logs-pane-list" show>
|
||||
<PdfFileList fileList={fileList} />
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -32,11 +32,6 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-entry-first-error-popup {
|
||||
border-radius: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.log-entry-header {
|
||||
padding: 3px @padding-sm;
|
||||
display: flex;
|
||||
|
@ -182,40 +177,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.log-entry-formatted-content-list {
|
||||
margin: @margin-xs 0;
|
||||
padding-left: @padding-md;
|
||||
}
|
||||
|
||||
.first-error-popup {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: @toolbar-small-height + 2px;
|
||||
right: @padding-xs;
|
||||
width: 90%;
|
||||
max-width: 450px;
|
||||
box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25);
|
||||
animation: fade-in 0.15s linear 0s 1 none;
|
||||
background-color: #fff;
|
||||
&::before {
|
||||
content: '';
|
||||
.triangle(top, @padding-sm, @padding-xs, @ol-red);
|
||||
top: -@padding-xs;
|
||||
right: @padding-xl;
|
||||
}
|
||||
}
|
||||
|
||||
.first-error-popup-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 @padding-sm @padding-sm @padding-sm;
|
||||
border-radius: 0 0 @border-radius-base @border-radius-base;
|
||||
}
|
||||
|
||||
.first-error-btn {
|
||||
.no-outline-ring-on-click;
|
||||
}
|
||||
|
||||
.log-location-tooltip {
|
||||
word-break: break-all;
|
||||
&.tooltip.in {
|
||||
|
|
|
@ -113,14 +113,12 @@
|
|||
}
|
||||
|
||||
.pdf-viewer,
|
||||
.pdf-logs,
|
||||
.pdf-errors,
|
||||
.pdf-uncompiled {
|
||||
.full-size;
|
||||
top: @pdf-top-offset;
|
||||
}
|
||||
|
||||
.pdf-logs,
|
||||
.pdf-errors,
|
||||
.pdf-uncompiled,
|
||||
.pdf-validation-problems {
|
||||
|
@ -327,72 +325,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.pdf-logs {
|
||||
overflow: auto;
|
||||
.alert {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: @line-height-computed / 2;
|
||||
cursor: pointer;
|
||||
.line-no {
|
||||
float: right;
|
||||
color: @log-line-no-color;
|
||||
font-weight: 700;
|
||||
|
||||
.fa {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.entry-message {
|
||||
font-weight: 700;
|
||||
//font-family: @font-family-monospace;
|
||||
}
|
||||
.entry-content {
|
||||
white-space: pre-wrap;
|
||||
font-size: 0.8rem;
|
||||
//font-family: @font-family-monospace;
|
||||
}
|
||||
|
||||
&:hover .line-no {
|
||||
color: inherit;
|
||||
.fa {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.alert-danger {
|
||||
background-color: tint(@alert-danger-bg, 15%);
|
||||
&:hover {
|
||||
background-color: @alert-danger-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&.alert-warning {
|
||||
background-color: tint(@alert-warning-bg, 15%);
|
||||
&:hover {
|
||||
background-color: @alert-warning-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&.alert-info {
|
||||
background-color: tint(@alert-info-bg, 15%);
|
||||
&:hover {
|
||||
background-color: @alert-info-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
pre {
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.dropdown {
|
||||
position: relative;
|
||||
}
|
||||
.force-recompile {
|
||||
margin-top: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.synctex-controls {
|
||||
margin-right: -8px;
|
||||
position: absolute;
|
||||
|
@ -443,9 +375,6 @@
|
|||
}
|
||||
|
||||
.editor-dark {
|
||||
.pdf-logs {
|
||||
background-color: lighten(@editor-dark-background-color, 10%);
|
||||
}
|
||||
.pdfjs-viewer {
|
||||
background-color: lighten(@editor-dark-background-color, 10%);
|
||||
}
|
||||
|
@ -604,21 +533,13 @@
|
|||
|
||||
@editor-and-logs-pane-toolbars-height: @toolbar-small-height + @toolbar-height;
|
||||
@btn-small-height: (@padding-small-vertical * 2)+ (@font-size-small *
|
||||
@line-height-small);
|
||||
@line-height-small); // 5px * 2 + 14px * 1.5 = 31px
|
||||
|
||||
#download-dropdown-list,
|
||||
#dropdown-files-logs-pane-list {
|
||||
overflow-y: auto;
|
||||
.dropdown-header {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
#download-dropdown-list {
|
||||
max-height: calc(
|
||||
~'100vh - ' @editor-and-logs-pane-toolbars-height ~' - ' @margin-md
|
||||
);
|
||||
}
|
||||
#dropdown-files-logs-pane-list {
|
||||
max-height: calc(
|
||||
~'100vh - ' @editor-and-logs-pane-toolbars-height ~' - ' @btn-small-height ~' - '
|
||||
@margin-md
|
||||
|
|
|
@ -3,6 +3,8 @@ $footer-height: 50px;
|
|||
|
||||
// Header
|
||||
$header-height: 68px;
|
||||
$toolbar-height: 40px;
|
||||
$toolbar-small-height: 32px;
|
||||
|
||||
// Forms
|
||||
$form-group-margin-bottom: $spacing-06;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
@import 'editor/hotkeys';
|
||||
@import 'editor/left-menu';
|
||||
@import 'editor/loading-screen';
|
||||
@import 'editor/logs';
|
||||
@import 'editor/outline';
|
||||
@import 'editor/file-tree';
|
||||
@import 'editor/file-view';
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
@import '../../foundations/colors';
|
||||
|
||||
.logs-pane {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow-y: auto;
|
||||
background-color: var(--bg-dark-secondary);
|
||||
z-index: 11; // above the PDF viewer + controls
|
||||
top: var(--toolbar-small-height);
|
||||
|
||||
.logs-pane-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
gap: 10px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.logs-pane-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
place-content: flex-end flex-end;
|
||||
padding: var(--spacing-03) 0;
|
||||
flex-grow: 1;
|
||||
align-items: flex-end;
|
||||
gap: var(--spacing-04);
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
border-radius: var(--border-radius-base);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-entry-header {
|
||||
padding: var(--spacing-02) var(--spacing-04);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-04);
|
||||
border-radius: var(--border-radius-base) var(--border-radius-base) 0 0;
|
||||
color: var(--content-primary-dark);
|
||||
|
||||
.material-symbols {
|
||||
@include body-base;
|
||||
}
|
||||
}
|
||||
|
||||
.log-entry-header-error {
|
||||
background-color: var(--content-danger);
|
||||
}
|
||||
|
||||
.log-entry-header-link-error {
|
||||
@include ol-button-variant(
|
||||
$color: var(--content-primary-dark),
|
||||
$background: var(--bg-danger-02),
|
||||
$hover-background: var(--red-70)
|
||||
);
|
||||
}
|
||||
|
||||
.log-entry-header-warning {
|
||||
background-color: var(--content-warning-dark);
|
||||
}
|
||||
|
||||
.log-entry-header-link-warning {
|
||||
@include ol-button-variant(
|
||||
$color: var(--content-primary-dark),
|
||||
$background: var(--bg-warning-01),
|
||||
$hover-background: var(--bg-warning-02)
|
||||
);
|
||||
}
|
||||
|
||||
.log-entry-header-typesetting {
|
||||
background-color: var(--blue-50);
|
||||
}
|
||||
|
||||
.log-entry-header-link-typesetting {
|
||||
@include ol-button-variant(
|
||||
$color: var(--content-primary-dark),
|
||||
$background: var(--blue-60),
|
||||
$hover-background: var(--blue-70)
|
||||
);
|
||||
}
|
||||
|
||||
.log-entry-header-raw,
|
||||
.log-entry-header-info {
|
||||
background-color: var(--bg-dark-tertiary);
|
||||
}
|
||||
|
||||
.log-entry-header-link-raw,
|
||||
.log-entry-header-link-info {
|
||||
@include ol-button-variant(
|
||||
$color: var(--content-primary-dark),
|
||||
$background: var(--bg-dark-secondary),
|
||||
$hover-background: var(--bg-dark-primary)
|
||||
);
|
||||
}
|
||||
|
||||
.log-entry-header-success {
|
||||
background-color: var(--green-50);
|
||||
}
|
||||
|
||||
.log-entry-header-link-success {
|
||||
@include ol-button-variant(
|
||||
$color: var(--content-primary-dark),
|
||||
$background: var(--green-60),
|
||||
$hover-background: var(--green-70)
|
||||
);
|
||||
}
|
||||
|
||||
.log-entry-header-title {
|
||||
@include body-base;
|
||||
|
||||
flex-grow: 1;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
color: var(--content-primary-dark);
|
||||
}
|
||||
|
||||
.log-entry-header-link {
|
||||
color: var(--content-primary-dark);
|
||||
border-width: 0;
|
||||
max-width: 33%;
|
||||
text-decoration: none; // needed for the "close button"
|
||||
padding: 0 var(--spacing-03);
|
||||
|
||||
.button-content {
|
||||
min-width: 0; // needed to display the ellipsis on overflow
|
||||
}
|
||||
}
|
||||
|
||||
.log-entry-header-link-location {
|
||||
white-space: nowrap;
|
||||
direction: rtl;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-entry-content {
|
||||
background-color: var(--bg-light-primary);
|
||||
padding: var(--spacing-04);
|
||||
}
|
||||
|
||||
.log-entry-content-raw {
|
||||
font-size: var(--font-size-01);
|
||||
color: var(--content-secondary);
|
||||
padding: var(--spacing-03);
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.log-entry-content-button-container {
|
||||
position: relative;
|
||||
height: 40px;
|
||||
margin-top: 0;
|
||||
transition:
|
||||
margin 0.15s ease-in-out,
|
||||
opacity 0.15s ease-in-out;
|
||||
padding-bottom: var(--spacing-04);
|
||||
text-align: center;
|
||||
background-image: linear-gradient(
|
||||
0deg,
|
||||
var(--bg-light-tertiary) 0%,
|
||||
transparent 100%
|
||||
);
|
||||
border-radius: 0 0 var(--border-radius-base) var(--border-radius-base);
|
||||
}
|
||||
|
||||
.log-entry-content-button-container-collapsed {
|
||||
margin-top: -40px;
|
||||
}
|
||||
|
||||
.log-entry-content-raw-container {
|
||||
background-color: var(--bg-light-tertiary);
|
||||
border-radius: var(--border-radius-base);
|
||||
overflow: hidden;
|
||||
margin-top: var(--spacing-03);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-secondary-compile-timeout-override {
|
||||
color: var(--content-primary);
|
||||
background-color: var(--bg-light-primary);
|
||||
border-color: var(--border-primary);
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.log-entry-formatted-content,
|
||||
.log-entry-content-link {
|
||||
font-size: var(--font-size-02);
|
||||
margin-top: var(--spacing-02);
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.log-location-tooltip {
|
||||
word-break: break-all;
|
||||
|
||||
& > .tooltip-inner {
|
||||
max-width: 450px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
|
@ -136,7 +136,6 @@
|
|||
}
|
||||
|
||||
.pdf-viewer,
|
||||
.pdf-logs,
|
||||
.pdf-errors,
|
||||
.pdf-uncompiled {
|
||||
@extend .full-size;
|
||||
|
@ -378,3 +377,17 @@
|
|||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
#dropdown-files-logs-pane-list {
|
||||
overflow-y: auto;
|
||||
|
||||
.dropdown-header {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// This keeps the dropdown menu inside the Logs div.
|
||||
// "spacing-11" is there to compensate the button height and the margin around the logs.
|
||||
max-height: calc(
|
||||
100vh - #{$toolbar-small-height + $toolbar-height + $spacing-11}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import '../../abstracts/variables';
|
||||
|
||||
:root {
|
||||
--toolbar-border-color: var(--neutral-80);
|
||||
--toolbar-header-bg-color: var(--neutral-90);
|
||||
|
@ -7,7 +9,8 @@
|
|||
--toolbar-btn-hover-color: var(--white);
|
||||
--toolbar-btn-active-color: var(--white);
|
||||
--toolbar-btn-active-bg-color: var(--green-50);
|
||||
--toolbar-small-height: 32px;
|
||||
--toolbar-height: #{$toolbar-height};
|
||||
--toolbar-small-height: #{$toolbar-small-height};
|
||||
--toolbar-alt-bg-color: var(--neutral-80);
|
||||
--formatting-btn-color: var(--white);
|
||||
--formatting-btn-bg: var(--neutral-80);
|
||||
|
@ -208,8 +211,6 @@
|
|||
}
|
||||
|
||||
&.toolbar-header {
|
||||
--toolbar-height: 40px;
|
||||
|
||||
align-items: stretch;
|
||||
background-color: var(--toolbar-header-bg-color);
|
||||
position: absolute;
|
||||
|
@ -315,7 +316,7 @@
|
|||
}
|
||||
|
||||
.toolbar-editor {
|
||||
height: 32px;
|
||||
height: var(--toolbar-small-height);
|
||||
background-color: var(--editor-toolbar-bg);
|
||||
padding: 0 5px;
|
||||
overflow: hidden;
|
||||
|
|
Loading…
Reference in a new issue