From db98f5132b262a10f9a659859686524780b28233 Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Wed, 4 Jun 2025 10:46:58 +0100 Subject: [PATCH] Merge pull request #25939 from overleaf/dp-error-logs Update error logs designs for new editor GitOrigin-RevId: 0de3a54446a0ff114a1debb7b5f274d3a8f19c42 --- .../src/Features/Project/ProjectController.js | 1 + services/web/config/settings.defaults.js | 1 + .../web/frontend/extracted-translations.json | 4 +- ...alSymbolsRoundedUnfilledPartialSlice.woff2 | Bin 4444 -> 4612 bytes .../material-symbols/unfilled-symbols.mjs | 1 + .../ide-redesign/components/chat/chat.tsx | 2 +- .../error-indicator.tsx} | 14 +- .../error-logs/error-logs-header.tsx | 98 ++++++ .../error-logs/error-logs-panel.tsx | 14 + .../components/error-logs/error-logs.tsx | 133 ++++++++ .../error-logs/log-entry-header.tsx | 153 +++++++++ .../components/error-logs/log-entry.tsx | 109 ++++++ .../components/error-logs/old-error-pane.tsx | 10 + .../integrations-panel/integrations-panel.tsx | 2 +- .../components/rail-panel-header.tsx | 31 ++ .../features/ide-redesign/components/rail.tsx | 41 ++- .../components/pdf-log-entry-content.tsx | 8 +- .../components/pdf-log-entry-raw-content.tsx | 12 +- .../pdf-preview/components/pdf-log-entry.tsx | 39 ++- .../components/pdf-preview-error.tsx | 312 ++++++++++-------- .../timeout-upgrade-paywall-prompt.tsx | 10 +- .../components/preview-log-entry-header.tsx | 14 - .../shared/context/local-compile-context.tsx | 9 +- .../stories/pdf-log-entry.stories.tsx | 1 - .../bootstrap-5/pages/editor/logs.scss | 244 +++++++++++--- .../bootstrap-5/pages/editor/rail.scss | 16 +- services/web/locales/da.json | 1 - services/web/locales/de.json | 1 - services/web/locales/en.json | 4 +- services/web/locales/fr.json | 1 - services/web/locales/sv.json | 1 - services/web/locales/zh-CN.json | 1 - 32 files changed, 1021 insertions(+), 267 deletions(-) rename services/web/frontend/js/features/ide-redesign/components/{errors.tsx => error-logs/error-indicator.tsx} (56%) create mode 100644 services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs-header.tsx create mode 100644 services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs-panel.tsx create mode 100644 services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs.tsx create mode 100644 services/web/frontend/js/features/ide-redesign/components/error-logs/log-entry-header.tsx create mode 100644 services/web/frontend/js/features/ide-redesign/components/error-logs/log-entry.tsx create mode 100644 services/web/frontend/js/features/ide-redesign/components/error-logs/old-error-pane.tsx create mode 100644 services/web/frontend/js/features/ide-redesign/components/rail-panel-header.tsx diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index 842215d80e..e88cb53449 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -352,6 +352,7 @@ const _ProjectController = { 'overleaf-assist-bundle', 'word-count-client', 'editor-popup-ux-survey', + 'new-editor-error-logs-redesign', ].filter(Boolean) const getUserValues = async userId => diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index d8892e70ff..0d3ea86314 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -966,6 +966,7 @@ module.exports = { editorToolbarButtons: [], sourceEditorExtensions: [], sourceEditorComponents: [], + pdfLogEntryHeaderActionComponents: [], pdfLogEntryComponents: [], pdfLogEntriesComponents: [], pdfPreviewPromotions: [], diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index e5bb2fced3..cad43ed4e1 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -411,7 +411,6 @@ "discount": "", "discount_of": "", "discover_the_fastest_way_to_search_and_cite": "", - "dismiss_error_popup": "", "display": "", "display_deleted_user": "", "display_math": "", @@ -680,6 +679,7 @@ "go_page": "", "go_prev_page": "", "go_to_account_settings": "", + "go_to_code_location": "", "go_to_code_location_in_pdf": "", "go_to_overleaf": "", "go_to_pdf_location_in_code": "", @@ -969,6 +969,7 @@ "login_count": "", "login_to_accept_invitation": "", "login_with_service": "", + "logs": "", "logs_and_output_files": "", "looking_multiple_licenses": "", "looks_like_youre_at": "", @@ -1039,6 +1040,7 @@ "more_compile_time": "", "more_editor_toolbar_item": "", "more_info": "", + "more_logs_and_files": "", "more_options": "", "my_library": "", "n_items": "", diff --git a/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 b/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 index 8e72799b077bd442cf7211bd1488813826bc9018..bc2ec87d3ab482401e7f59c91abac055c319e539 100644 GIT binary patch literal 4612 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
~K%gi_OiI+v~ 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 diff --git a/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs b/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs index 1c41421910..68f0918301 100644 --- a/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs +++ b/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs @@ -4,6 +4,7 @@ // You may need to hard reload your browser window to see the changes. export default /** @type {const} */ ([ + 'auto_delete', 'book_5', 'brush', 'code', diff --git a/services/web/frontend/js/features/ide-redesign/components/chat/chat.tsx b/services/web/frontend/js/features/ide-redesign/components/chat/chat.tsx index 9ebe33e065..54d098c6c8 100644 --- a/services/web/frontend/js/features/ide-redesign/components/chat/chat.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/chat/chat.tsx @@ -9,8 +9,8 @@ import { useUserContext } from '@/shared/context/user-context' import { lazy, Suspense, useEffect } from 'react' import { useTranslation } from 'react-i18next' import classNames from 'classnames' -import { RailPanelHeader } from '../rail' import { RailIndicator } from '../rail-indicator' +import RailPanelHeader from '../rail-panel-header' const MessageList = lazy(() => import('../../../chat/components/message-list')) diff --git a/services/web/frontend/js/features/ide-redesign/components/errors.tsx b/services/web/frontend/js/features/ide-redesign/components/error-logs/error-indicator.tsx similarity index 56% rename from services/web/frontend/js/features/ide-redesign/components/errors.tsx rename to services/web/frontend/js/features/ide-redesign/components/error-logs/error-indicator.tsx index 2313022d3c..7b721a1d51 100644 --- a/services/web/frontend/js/features/ide-redesign/components/errors.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/error-logs/error-indicator.tsx @@ -1,9 +1,7 @@ -import PdfLogsViewer from '@/features/pdf-preview/components/pdf-logs-viewer' -import { PdfPreviewProvider } from '@/features/pdf-preview/components/pdf-preview-provider' import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context' -import { RailIndicator } from './rail-indicator' +import { RailIndicator } from '../rail-indicator' -export const ErrorIndicator = () => { +export default function ErrorIndicator() { const { logEntries } = useCompileContext() if (!logEntries) { @@ -25,11 +23,3 @@ export const ErrorIndicator = () => { /> ) } - -export const ErrorPane = () => { - return ( - - - - ) -} diff --git a/services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs-header.tsx b/services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs-header.tsx new file mode 100644 index 0000000000..2f3a54b095 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs-header.tsx @@ -0,0 +1,98 @@ +import { useTranslation } from 'react-i18next' +import RailPanelHeader from '../rail-panel-header' +import OLIconButton from '@/features/ui/components/ol/ol-icon-button' +import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context' +import { + Dropdown, + DropdownMenu, + DropdownToggle, +} from '@/features/ui/components/bootstrap-5/dropdown-menu' +import PdfFileList from '@/features/pdf-preview/components/pdf-file-list' +import { forwardRef } from 'react' +import OLTooltip from '@/features/ui/components/ol/ol-tooltip' + +export default function ErrorLogsHeader() { + const { t } = useTranslation() + + return ( + , + , + ]} + /> + ) +} + +const ClearCacheButton = () => { + const { compiling, clearCache, clearingCache } = useCompileContext() + const { t } = useTranslation() + + return ( + + clearCache()} + className="rail-panel-header-button-subdued" + icon="auto_delete" + isLoading={clearingCache} + disabled={clearingCache || compiling} + accessibilityLabel={t('clear_cached_files')} + size="sm" + /> + + ) +} + +const DownloadFileDropdown = () => { + const { fileList } = useCompileContext() + + const { t } = useTranslation() + + return ( + + + {t('other_logs_and_files')} + + {fileList && ( + + + + )} + + ) +} + +const DownloadFileDropdownToggleButton = forwardRef< + HTMLButtonElement, + { onClick: React.MouseEventHandler } +>(function DownloadFileDropdownToggleButton({ onClick }, ref) { + const { compiling, fileList } = useCompileContext() + const { t } = useTranslation() + + return ( + + + + ) +}) diff --git a/services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs-panel.tsx b/services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs-panel.tsx new file mode 100644 index 0000000000..2cff048256 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs-panel.tsx @@ -0,0 +1,14 @@ +import { PdfPreviewProvider } from '@/features/pdf-preview/components/pdf-preview-provider' +import ErrorLogs from './error-logs' +import ErrorLogsHeader from './error-logs-header' + +export default function ErrorLogsPanel() { + return ( + +
+ + +
+
+ ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs.tsx b/services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs.tsx new file mode 100644 index 0000000000..7b54785295 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs.tsx @@ -0,0 +1,133 @@ +import { useTranslation } from 'react-i18next' +import { memo, useMemo, useState } from 'react' +import { usePdfPreviewContext } from '@/features/pdf-preview/components/pdf-preview-provider' +import StopOnFirstErrorPrompt from '@/features/pdf-preview/components/stop-on-first-error-prompt' +import PdfPreviewError from '@/features/pdf-preview/components/pdf-preview-error' +import PdfValidationIssue from '@/features/pdf-preview/components/pdf-validation-issue' +import PdfLogsEntries from '@/features/pdf-preview/components/pdf-logs-entries' +import PdfPreviewErrorBoundaryFallback from '@/features/pdf-preview/components/pdf-preview-error-boundary-fallback' +import withErrorBoundary from '@/infrastructure/error-boundary' +import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context' +import { Nav, NavLink, TabContainer, TabContent } from 'react-bootstrap' +import { LogEntry as LogEntryData } from '@/features/pdf-preview/util/types' +import LogEntry from './log-entry' + +type ErrorLogTab = { + key: string + label: string + entries: LogEntryData[] | undefined +} + +function ErrorLogs() { + const { error, logEntries, rawLog, validationIssues, stoppedOnFirstError } = + useCompileContext() + + const tabs = useMemo(() => { + return [ + { key: 'all', label: 'All', entries: logEntries?.all }, + { key: 'errors', label: 'Errors', entries: logEntries?.errors }, + { key: 'warnings', label: 'Warnings', entries: logEntries?.warnings }, + { key: 'info', label: 'Info', entries: logEntries?.typesetting }, + ] + }, [logEntries]) + + const { loadingError } = usePdfPreviewContext() + + const { t } = useTranslation() + + const [activeTab, setActiveTab] = useState('all') + + const entries = useMemo(() => { + return tabs.find(tab => tab.key === activeTab)?.entries || [] + }, [activeTab, tabs]) + + const includeErrors = activeTab === 'all' || activeTab === 'errors' + const includeWarnings = activeTab === 'all' || activeTab === 'warnings' + const includeInfo = activeTab === 'all' || activeTab === 'info' + + return ( + + + +
+ {stoppedOnFirstError && includeErrors && } + + {loadingError && ( + + )} + + {error && ( + + )} + + {includeErrors && + validationIssues && + Object.entries(validationIssues).map(([name, issue]) => ( + + ))} + + {entries && ( + 0} + /> + )} + + {rawLog && includeInfo && ( + + )} +
+
+
+ ) +} + +function formatErrorNumber(num: number | undefined) { + if (num === undefined) { + return undefined + } + + if (num > 99) { + return '99+' + } + + return Math.floor(num).toString() +} + +const TabHeader = ({ tab, active }: { tab: ErrorLogTab; active: boolean }) => { + return ( + + {tab.label} +
+ {/* TODO: it would be nice if this number included custom errors */} + {formatErrorNumber(tab.entries?.length)} +
+
+ ) +} + +export default withErrorBoundary(memo(ErrorLogs), () => ( + +)) diff --git a/services/web/frontend/js/features/ide-redesign/components/error-logs/log-entry-header.tsx b/services/web/frontend/js/features/ide-redesign/components/error-logs/log-entry-header.tsx new file mode 100644 index 0000000000..ff60fc63b9 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/error-logs/log-entry-header.tsx @@ -0,0 +1,153 @@ +import classNames from 'classnames' +import { useState, useRef, MouseEventHandler, ElementType } from 'react' +import { useTranslation } from 'react-i18next' +import OLTooltip from '@/features/ui/components/ol/ol-tooltip' +import { + ErrorLevel, + SourceLocation, + LogEntry as LogEntryData, +} from '@/features/pdf-preview/util/types' +import useResizeObserver from '@/features/preview/hooks/use-resize-observer' +import OLIconButton from '@/features/ui/components/ol/ol-icon-button' +import importOverleafModules from '../../../../../macros/import-overleaf-module.macro' + +const actionComponents = importOverleafModules( + 'pdfLogEntryHeaderActionComponents' +) as { + import: { default: ElementType } + path: string +}[] + +function LogEntryHeader({ + sourceLocation, + level, + headerTitle, + logType, + showSourceLocationLink = true, + onSourceLocationClick, + collapsed, + onToggleCollapsed, + id, + logEntry, +}: { + headerTitle: string | React.ReactNode + level: ErrorLevel + logType?: string + sourceLocation?: SourceLocation + showSourceLocationLink?: boolean + onSourceLocationClick?: MouseEventHandler + collapsed: boolean + onToggleCollapsed: () => void + id?: string + logEntry?: LogEntryData +}) { + const { t } = useTranslation() + const logLocationSpanRef = useRef(null) + const [locationSpanOverflown, setLocationSpanOverflown] = useState(false) + + useResizeObserver( + logLocationSpanRef, + locationSpanOverflown, + checkLocationSpanOverflow + ) + + const file = sourceLocation ? sourceLocation.file : null + const line = sourceLocation ? sourceLocation.line : null + const logEntryHeaderTextClasses = classNames('log-entry-header-text', { + 'log-entry-header-text-error': level === 'error', + 'log-entry-header-text-warning': level === 'warning', + 'log-entry-header-text-info': + level === 'info' || level === 'typesetting' || level === 'raw', + 'log-entry-header-text-success': level === 'success', + }) + + function checkLocationSpanOverflow(observedElement: ResizeObserverEntry) { + const spanEl = observedElement.target + const isOverflowing = spanEl.scrollWidth > spanEl.clientWidth + setLocationSpanOverflown(isOverflowing) + } + + const locationText = + showSourceLocationLink && file ? `${file}${line ? `, ${line}` : ''}` : null + + // Because we want an ellipsis on the left-hand side (e.g. "...longfilename.tex"), the + // `log-entry-location` class has text laid out from right-to-left using the CSS + // rule `direction: rtl;`. + // This works most of the times, except when the first character of the filename is considered + // a punctuation mark, like `/` (e.g. `/foo/bar/baz.sty`). In this case, because of + // right-to-left writing rules, the punctuation mark is moved to the right-side of the string, + // resulting in `...bar/baz.sty/` instead of `...bar/baz.sty`. + // To avoid this edge-case, we wrap the `logLocationLinkText` in two directional formatting + // characters: + // * \u202A LEFT-TO-RIGHT EMBEDDING Treat the following text as embedded left-to-right. + // * \u202C POP DIRECTIONAL FORMATTING End the scope of the last LRE, RLE, RLO, or LRO. + // 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 formattedLocationText = locationText ? ( + + {`\u202A${locationText}\u202C`} + + ) : null + + const headerTitleText = logType ? `${logType} ${headerTitle}` : headerTitle + + return ( +
+ + + +
+

{headerTitleText}

+ {locationSpanOverflown && formattedLocationText && locationText ? ( + + {formattedLocationText} + + ) : ( + formattedLocationText + )} +
+
+ {showSourceLocationLink && ( + + + + )} + {actionComponents.map(({ import: { default: Component }, path }) => ( + + ))} +
+
+ ) +} + +export default LogEntryHeader diff --git a/services/web/frontend/js/features/ide-redesign/components/error-logs/log-entry.tsx b/services/web/frontend/js/features/ide-redesign/components/error-logs/log-entry.tsx new file mode 100644 index 0000000000..0986343131 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/error-logs/log-entry.tsx @@ -0,0 +1,109 @@ +import { memo, MouseEventHandler, useCallback, useState } from 'react' +import HumanReadableLogsHints from '../../../../ide/human-readable-logs/HumanReadableLogsHints' +import { sendMB } from '@/infrastructure/event-tracking' +import { + ErrorLevel, + LogEntry as LogEntryData, + SourceLocation, +} from '@/features/pdf-preview/util/types' +import LogEntryHeader from './log-entry-header' +import PdfLogEntryContent from '@/features/pdf-preview/components/pdf-log-entry-content' + +function LogEntry({ + ruleId, + headerTitle, + rawContent, + logType, + formattedContent, + extraInfoURL, + level, + sourceLocation, + showSourceLocationLink = true, + entryAriaLabel = undefined, + contentDetails, + onSourceLocationClick, + index, + logEntry, + id, + alwaysExpandRawContent = false, +}: { + headerTitle: string | React.ReactNode + level: ErrorLevel + ruleId?: string + rawContent?: string + logType?: string + formattedContent?: React.ReactNode + extraInfoURL?: string | null + sourceLocation?: SourceLocation + showSourceLocationLink?: boolean + entryAriaLabel?: string + contentDetails?: string[] + onSourceLocationClick?: (sourceLocation: SourceLocation) => void + index?: number + logEntry?: LogEntryData + id?: string + alwaysExpandRawContent?: boolean +}) { + const [collapsed, setCollapsed] = useState(true) + + if (ruleId && HumanReadableLogsHints[ruleId]) { + const hint = HumanReadableLogsHints[ruleId] + formattedContent = hint.formattedContent(contentDetails) + extraInfoURL = hint.extraInfoURL + } + + const handleLogEntryLinkClick: MouseEventHandler = + useCallback( + event => { + event.preventDefault() + + 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 ( +
+ setCollapsed(collapsed => !collapsed)} + id={id} + logEntry={logEntry} + /> + + {!collapsed && ( + <> +
+ + + )} +
+ ) +} + +export default memo(LogEntry) diff --git a/services/web/frontend/js/features/ide-redesign/components/error-logs/old-error-pane.tsx b/services/web/frontend/js/features/ide-redesign/components/error-logs/old-error-pane.tsx new file mode 100644 index 0000000000..7794747d30 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/error-logs/old-error-pane.tsx @@ -0,0 +1,10 @@ +import PdfLogsViewer from '@/features/pdf-preview/components/pdf-logs-viewer' +import { PdfPreviewProvider } from '@/features/pdf-preview/components/pdf-preview-provider' + +export default function OldErrorPane() { + return ( + + + + ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/integrations-panel/integrations-panel.tsx b/services/web/frontend/js/features/ide-redesign/components/integrations-panel/integrations-panel.tsx index d1e4358907..e477602e3e 100644 --- a/services/web/frontend/js/features/ide-redesign/components/integrations-panel/integrations-panel.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/integrations-panel/integrations-panel.tsx @@ -1,7 +1,7 @@ import { ElementType } from 'react' import importOverleafModules from '../../../../../macros/import-overleaf-module.macro' -import { RailPanelHeader } from '../rail' import { useTranslation } from 'react-i18next' +import RailPanelHeader from '../rail-panel-header' const integrationPanelComponents = importOverleafModules( 'integrationPanelComponents' diff --git a/services/web/frontend/js/features/ide-redesign/components/rail-panel-header.tsx b/services/web/frontend/js/features/ide-redesign/components/rail-panel-header.tsx new file mode 100644 index 0000000000..94ac2f42af --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/rail-panel-header.tsx @@ -0,0 +1,31 @@ +import { useTranslation } from 'react-i18next' +import { useRailContext } from '../contexts/rail-context' +import OLIconButton from '@/features/ui/components/ol/ol-icon-button' +import React from 'react' + +export default function RailPanelHeader({ + title, + actions, +}: { + title: string + actions?: React.ReactNode[] +}) { + const { t } = useTranslation() + const { handlePaneCollapse } = useRailContext() + return ( +
+

{title}

+ +
+ {actions} + +
+
+ ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/rail.tsx b/services/web/frontend/js/features/ide-redesign/components/rail.tsx index 9bd70ac4bb..5edcba55a9 100644 --- a/services/web/frontend/js/features/ide-redesign/components/rail.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/rail.tsx @@ -6,7 +6,7 @@ import MaterialIcon, { } from '@/shared/components/material-icon' import { Panel } from 'react-resizable-panels' import { useLayoutContext } from '@/shared/context/layout-context' -import { ErrorIndicator, ErrorPane } from './errors' +import ErrorIndicator from './error-logs/error-indicator' import { RailModalKey, RailTabKey, @@ -39,6 +39,10 @@ import { hasFullProjectSearch, } from './full-project-search-panel' import { sendSearchEvent } from '@/features/event-tracking/search-events' +import ErrorLogsPanel from './error-logs/error-logs-panel' +import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context' +import OldErrorPane from './error-logs/old-error-pane' +import { useFeatureFlag } from '@/shared/context/split-test-context' type RailElement = { icon: AvailableUnfilledIcon @@ -47,6 +51,7 @@ type RailElement = { indicator?: ReactElement title: string hide?: boolean + disabled?: boolean } type RailActionButton = { @@ -96,6 +101,8 @@ export const RailLayout = () => { togglePane, setResizing, } = useRailContext() + const { logEntries } = useCompileContext() + const errorLogsDisabled = !logEntries const { view, setLeftMenuShown } = useLayoutContext() @@ -103,6 +110,8 @@ export const RailLayout = () => { const isHistoryView = view === 'history' + const newErrorlogs = useFeatureFlag('new-editor-error-logs-redesign') + const railTabs: RailElement[] = useMemo( () => [ { @@ -142,11 +151,12 @@ export const RailLayout = () => { key: 'errors', icon: 'report', title: t('error_log'), - component: , + component: newErrorlogs ? : , indicator: , + disabled: errorLogsDisabled, }, ], - [t] + [t, errorLogsDisabled, newErrorlogs] ) const railActions: RailAction[] = useMemo( @@ -217,7 +227,7 @@ export const RailLayout = () => {