rasengan
Version:
The modern React Framework
61 lines (60 loc) • 6.91 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useEffect, useState, useSyncExternalStore } from 'react';
import { createPortal } from 'react-dom';
import { errorStore } from './error-store.js';
import { parseStackFrame, fetchSourceSnippet } from './stack-utils.js';
import './error-overlay.css';
export function ErrorOverlay() {
const errors = useSyncExternalStore(errorStore.subscribe.bind(errorStore), () => errorStore.getErrors(), () => errorStore.getErrors());
const minimized = useSyncExternalStore(errorStore.subscribe.bind(errorStore), () => errorStore.isMinimized(), () => errorStore.isMinimized());
const [activeTab, setActiveTab] = useState(0);
const [codePreview, setCodePreview] = useState(null);
useEffect(() => {
if (errors.length > 0 && activeTab >= errors.length) {
setActiveTab(errors.length - 1);
}
}, [errors.length, activeTab]);
useEffect(() => {
const error = errors[activeTab];
if (!error || !error.error.stack) {
setCodePreview(null);
return;
}
const frame = parseStackFrame(error.error.stack);
if (!frame) {
setCodePreview(null);
return;
}
fetchSourceSnippet(frame.file, frame.line).then((result) => {
if (result) {
setCodePreview({
snippet: result.snippet,
errorLineIndex: result.errorLineIndex,
file: frame.file,
line: frame.line,
});
}
else {
setCodePreview(null);
}
});
}, [activeTab, errors]);
if (errors.length === 0)
return null;
if (minimized)
return createPortal(_jsxs("button", { className: "rasengan-error-fab", onClick: () => errorStore.toggleMinimize(), title: `${errors.length} error${errors.length > 1 ? 's' : ''} — click to view`, children: [_jsx("svg", { className: "rasengan-error-fab-icon", viewBox: "0 0 16 16", width: "18", height: "18", fill: "currentColor", children: _jsx("path", { d: "M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l5.782 10.832c.659 1.234-.165 2.771-1.543 2.771H2.218c-1.378 0-2.202-1.537-1.543-2.771L6.457 1.047zM8 4a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 018 4zm0 7a1 1 0 100-2 1 1 0 000 2z" }) }), _jsx("span", { className: "rasengan-error-fab-count", children: errors.length })] }), document.body);
const activeError = errors[activeTab];
if (!activeError)
return null;
const totalErrors = errors.length;
const isFirst = activeTab === 0;
const isLast = activeTab === totalErrors - 1;
const snippetLines = codePreview ? codePreview.snippet.split('\n') : [];
const relativeLineStart = codePreview
? codePreview.line - codePreview.errorLineIndex
: 0;
return createPortal(_jsxs(_Fragment, { children: [_jsx("div", { className: "rasengan-error-backdrop" }), _jsxs("div", { className: "rasengan-error-overlay", children: [_jsxs("div", { className: "rasengan-error-overlay-header", children: [_jsxs("div", { className: "rasengan-error-pagination", children: [_jsx("button", { className: "rasengan-error-pagination-btn", disabled: isFirst, onClick: () => setActiveTab(activeTab - 1), title: "Previous error", children: "\u2039" }), _jsxs("span", { className: "rasengan-error-pagination-text", children: [activeTab + 1, " of ", totalErrors] }), _jsx("button", { className: "rasengan-error-pagination-btn", disabled: isLast, onClick: () => setActiveTab(activeTab + 1), title: "Next error", children: "\u203A" })] }), _jsx("div", { className: "rasengan-error-overlay-actions", children: _jsx("button", { className: "rasengan-error-overlay-close", onClick: () => errorStore.toggleMinimize(), title: "Minimize", children: "\u00D7" }) })] }), _jsxs("div", { className: "rasengan-error-overlay-body", children: [_jsxs("div", { className: "rasengan-error-overlay-source", children: [_jsx("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "currentColor", style: { marginRight: 4, verticalAlign: -2 }, children: _jsx("path", { d: "M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l5.782 10.832c.659 1.234-.165 2.771-1.543 2.771H2.218c-1.378 0-2.202-1.537-1.543-2.771L6.457 1.047zM8 4a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 018 4zm0 7a1 1 0 100-2 1 1 0 000 2z" }) }), _jsx("p", { children: activeError.source })] }), _jsx("div", { className: "rasengan-error-overlay-message", children: activeError.error.message || 'Unknown error' }), codePreview && (_jsxs("div", { className: "rasengan-error-code-preview", children: [_jsxs("div", { className: "rasengan-error-code-preview-header", children: [_jsx("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "currentColor", style: { marginRight: 4, flexShrink: 0 }, children: _jsx("path", { d: "M2 1.75C2 .784 2.784 0 3.75 0h5.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0112.25 16h-8.5A1.75 1.75 0 012 14.25V1.75zM3.75 1.5a.25.25 0 00-.25.25v12.5c0 .138.112.25.25.25h8.5a.25.25 0 00.25-.25V5.5h-2.75A1.75 1.75 0 018 3.75V1.5H3.75zm5.75.56v2.19c0 .138.112.25.25.25h2.19L9.5 2.06z" }) }), _jsx("span", { className: "rasengan-error-code-preview-file", children: codePreview.file }), _jsxs("span", { className: "rasengan-error-code-preview-line", children: ["line ", codePreview.line] })] }), _jsx("div", { className: "rasengan-error-code-preview-content", children: snippetLines.map((lineContent, idx) => {
const isErrorLine = idx === codePreview.errorLineIndex;
return (_jsxs("div", { className: `rasengan-error-code-line${isErrorLine ? ' error' : ''}`, children: [_jsx("span", { className: "rasengan-error-code-line-number", children: relativeLineStart + idx }), isErrorLine && (_jsx("span", { className: "rasengan-error-code-line-arrow", children: "\u25B8" })), !isErrorLine && (_jsx("span", { className: "rasengan-error-code-line-arrow" })), _jsx("span", { className: "rasengan-error-code-line-content", children: lineContent })] }, idx));
}) })] })), activeError.componentStack && (_jsxs("details", { className: "rasengan-error-overlay-section", open: true, children: [_jsx("summary", { className: "rasengan-error-overlay-section-title", children: "Component Stack" }), _jsx("pre", { className: "rasengan-error-overlay-stack", children: activeError.componentStack })] })), activeError.error.stack && (_jsxs("details", { className: "rasengan-error-overlay-section", open: true, children: [_jsx("summary", { className: "rasengan-error-overlay-section-title", children: "Stack Trace" }), _jsx("pre", { className: "rasengan-error-overlay-stack", children: activeError.error.stack })] }))] })] })] }), document.body);
}