@flanksource/clicky-ui
Version:
Flanksource Clicky UI — React component library built on shadcn/ui with light/dark and density theming.
236 lines (235 loc) • 9.53 kB
JavaScript
import { jsxs, jsx } from "react/jsx-runtime";
import { useMemo, useState, useEffect } from "react";
import { parseJavaStackTrace } from "./stacktrace-parse.js";
import { loadShikiTransformers, highlightCode } from "../code-highlight.js";
import { Icon } from "../Icon.js";
function StackTrace({
input,
language = "java",
resolver,
contextLines = 3,
hideRuntimeOnly = false,
include,
exclude,
className
}) {
const parsed = useMemo(() => {
if (typeof input === "string") {
return parseJavaStackTrace(input);
}
return input;
}, [input, language]);
const enrichedFrames = useMemo(() => {
if (!resolver) return parsed.frames;
return parsed.frames.map((frame) => {
if (frame.sourceLines && frame.sourceLines.length > 0) return frame;
const resolved = resolver(frame, contextLines);
if (!resolved) return frame;
const next = {
...frame,
sourceLines: resolved.lines,
sourceStartLine: resolved.startLine
};
if (resolved.language !== void 0) next.sourceLanguage = resolved.language;
return next;
});
}, [parsed.frames, resolver, contextLines]);
const visibleFrames = useMemo(() => {
return enrichedFrames.filter((frame) => {
if (hideRuntimeOnly && frame.runtime) return false;
if (exclude && exclude.some((p) => {
var _a;
return (_a = frame.class) == null ? void 0 : _a.startsWith(p);
})) return false;
if (include && include.length > 0) {
return include.some((p) => {
var _a;
return (_a = frame.class) == null ? void 0 : _a.startsWith(p);
});
}
return true;
});
}, [enrichedFrames, hideRuntimeOnly, include, exclude]);
if (visibleFrames.length === 0 && !parsed.exceptionClass) return null;
const hasFilteredFrames = visibleFrames.length !== enrichedFrames.length;
return /* @__PURE__ */ jsxs(
"div",
{
className: [
"overflow-hidden rounded-md border border-border bg-background text-foreground",
className
].filter(Boolean).join(" "),
children: [
(parsed.exceptionClass || parsed.message) && /* @__PURE__ */ jsx("div", { className: "border-b border-border bg-red-500/5 px-3 py-2", children: /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-2", children: [
/* @__PURE__ */ jsx(
Icon,
{
name: "codicon:error",
className: "mt-0.5 shrink-0 text-sm text-red-600 dark:text-red-400"
}
),
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
parsed.exceptionClass && /* @__PURE__ */ jsx("div", { className: "break-all font-mono text-xs font-semibold text-red-700 dark:text-red-300", children: parsed.exceptionClass }),
parsed.message && /* @__PURE__ */ jsx("div", { className: "mt-0.5 whitespace-pre-wrap break-words text-xs text-foreground", children: parsed.message })
] })
] }) }),
parsed.causedBy.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-1 border-b border-border bg-orange-500/5 px-3 py-2", children: parsed.causedBy.map((cause, i) => /* @__PURE__ */ jsxs(
"div",
{
className: "flex min-w-0 items-start gap-2 font-mono text-[11px] text-orange-700 dark:text-orange-300",
children: [
/* @__PURE__ */ jsx(Icon, { name: "codicon:debug-stackframe-dot", className: "mt-0.5 shrink-0 text-xs" }),
/* @__PURE__ */ jsx("span", { className: "shrink-0 opacity-75", children: "Caused by" }),
/* @__PURE__ */ jsx("span", { className: "min-w-0 break-all", children: cause })
]
},
i
)) }),
/* @__PURE__ */ jsx("div", { className: "divide-y divide-border/60", children: visibleFrames.map((frame, idx) => /* @__PURE__ */ jsx(FrameWithSource, { frame, index: idx }, `${frame.functionName}-${idx}`)) }),
hasFilteredFrames && /* @__PURE__ */ jsxs("div", { className: "border-t border-border bg-muted/30 px-3 py-1.5 text-[11px] text-muted-foreground", children: [
"Showing ",
visibleFrames.length,
" of ",
enrichedFrames.length,
" frames"
] })
]
}
);
}
function FrameWithSource({ frame, index }) {
const hasSource = frame.sourceLines && frame.sourceLines.length > 0;
return /* @__PURE__ */ jsxs("div", { className: hasSource ? "bg-amber-500/[0.03]" : void 0, children: [
/* @__PURE__ */ jsx(StackFrameRow, { frame, index }),
hasSource ? /* @__PURE__ */ jsx(SourceWindow, { frame }) : null
] });
}
function StackFrameRow({ frame, index }) {
const iconName = frame.nativeMethod ? "codicon:chip" : frame.runtime ? "codicon:debug-step-over" : "codicon:symbol-method";
const className = frame.class;
const methodName = frame.displayName || frame.method || frame.functionName;
return /* @__PURE__ */ jsxs(
"div",
{
className: [
"grid grid-cols-[2rem_minmax(0,1fr)] gap-2 px-3 py-1.5 text-xs",
frame.runtime ? "text-muted-foreground" : "text-foreground"
].join(" "),
children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-end gap-1 pt-0.5 font-mono text-[10px] text-muted-foreground", children: [
/* @__PURE__ */ jsx("span", { children: index + 1 }),
/* @__PURE__ */ jsx(Icon, { name: iconName, className: "mt-px shrink-0 text-[11px]" })
] }),
/* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5", children: [
/* @__PURE__ */ jsx("span", { className: "min-w-0 break-all font-mono font-semibold leading-4", children: methodName }),
className && /* @__PURE__ */ jsx("span", { className: "min-w-0 break-all font-mono text-[11px] text-muted-foreground", children: className }),
frame.location && /* @__PURE__ */ jsx("span", { className: "rounded border border-border bg-muted/40 px-1.5 py-px font-mono text-[10px] text-muted-foreground", children: frame.location })
] }) })
]
}
);
}
function inlineCommentPrefix(language) {
switch (language) {
case "python":
case "py":
case "bash":
case "sh":
case "shell":
case "ruby":
case "rb":
case "yaml":
case "yml":
case "toml":
return "#";
default:
return "//";
}
}
function buildAnnotatedSource(lines, startLine, focalLine, language) {
const focalIdx = focalLine - startLine;
if (focalIdx < 0 || focalIdx >= lines.length) {
return lines.join("\n");
}
const prefix = inlineCommentPrefix(language);
return lines.map((src, i) => i === focalIdx ? `${src} ${prefix} [!code error]` : src).join("\n");
}
function SourceWindow({ frame }) {
const start = frame.sourceStartLine ?? 0;
const focal = frame.line ?? -1;
const lines = frame.sourceLines ?? [];
const lineNumbers = frame.sourceLineNumbers;
const language = frame.sourceLanguage ?? "java";
const rows = useMemo(
() => lines.map((src, i) => ({
source: src,
lineNumber: (lineNumbers == null ? void 0 : lineNumbers[i]) ?? start + i
})),
[lineNumbers, lines, start]
);
const hasExplicitLineNumbers = lineNumbers != null && lineNumbers.length > 0;
const annotatedSource = useMemo(
() => buildAnnotatedSource(lines, start, focal, language),
[lines, start, focal, language]
);
const [html, setHtml] = useState(null);
useEffect(() => {
if (!annotatedSource || hasExplicitLineNumbers) {
setHtml(null);
return;
}
let cancelled = false;
loadShikiTransformers().then(
({ transformerNotationErrorLevel }) => highlightCode(annotatedSource, {
lang: language,
transformers: [transformerNotationErrorLevel()]
})
).then((out) => {
if (!cancelled) setHtml(out);
});
return () => {
cancelled = true;
};
}, [annotatedSource, hasExplicitLineNumbers, language]);
const counterStart = start - 1;
if (!html || hasExplicitLineNumbers) {
return /* @__PURE__ */ jsx(SourceRows, { rows, focal });
}
return /* @__PURE__ */ jsx(
"div",
{
className: "stacktrace-source ml-6 overflow-x-auto rounded border border-border/50 bg-muted/30 p-2 text-[11px] leading-relaxed",
style: { ["--shiki-start"]: counterStart },
dangerouslySetInnerHTML: { __html: html }
}
);
}
function SourceRows({
rows,
focal
}) {
return /* @__PURE__ */ jsx("div", { className: "mx-3 mb-2 ml-12 overflow-x-auto rounded border border-border/60 bg-muted/30 py-1 font-mono text-[11px] leading-relaxed", children: rows.map((row, i) => {
const isFocal = row.lineNumber === focal;
return /* @__PURE__ */ jsxs(
"div",
{
className: [
"grid min-w-max grid-cols-[3.5rem_minmax(24rem,1fr)] gap-3 px-2",
isFocal ? "bg-red-500/10 font-semibold text-red-800 dark:text-red-300" : "text-foreground"
].join(" "),
children: [
/* @__PURE__ */ jsxs("span", { className: "select-none text-right text-muted-foreground", children: [
isFocal ? ">" : "",
row.lineNumber
] }),
/* @__PURE__ */ jsx("code", { className: "whitespace-pre", children: row.source || " " })
]
},
`${row.lineNumber}-${i}`
);
}) });
}
export {
StackTrace
};
//# sourceMappingURL=RenderedStackTrace.js.map