@solidjs/start 2.0.3 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/shared/dev-toolbar/error-viewer/CodeView.jsx +9 -4
- package/dist/shared/dev-toolbar/error-viewer/create-stack-frame.d.ts +2 -1
- package/dist/shared/dev-toolbar/error-viewer/create-stack-frame.js +29 -19
- package/dist/shared/dev-toolbar/error-viewer/get-source-map.d.ts +2 -2
- package/dist/shared/dev-toolbar/error-viewer/get-source-map.js +3 -2
- package/dist/shared/dev-toolbar/error-viewer/index.jsx +52 -35
- package/dist/shared/dev-toolbar/error-viewer/styles.css +91 -43
- package/dist/shared/dev-toolbar/functions/BlobViewer.css +50 -2
- package/dist/shared/dev-toolbar/functions/BlobViewer.jsx +23 -4
- package/dist/shared/dev-toolbar/functions/FormDataViewer.jsx +9 -6
- package/dist/shared/dev-toolbar/functions/HeadersViewer.css +0 -4
- package/dist/shared/dev-toolbar/functions/HeadersViewer.jsx +9 -7
- package/dist/shared/dev-toolbar/functions/HexViewer.css +36 -17
- package/dist/shared/dev-toolbar/functions/HexViewer.jsx +7 -6
- package/dist/shared/dev-toolbar/functions/SerovalValue.css +21 -0
- package/dist/shared/dev-toolbar/functions/SerovalValue.d.ts +1 -0
- package/dist/shared/dev-toolbar/functions/SerovalValue.jsx +4 -2
- package/dist/shared/dev-toolbar/functions/SerovalViewer.css +98 -36
- package/dist/shared/dev-toolbar/functions/SerovalViewer.jsx +268 -279
- package/dist/shared/dev-toolbar/functions/URLSearchParamsViewer.jsx +9 -6
- package/dist/shared/dev-toolbar/functions/index.jsx +96 -69
- package/dist/shared/dev-toolbar/functions/styles.css +143 -10
- package/dist/shared/dev-toolbar/index.css +54 -14
- package/dist/shared/dev-toolbar/index.jsx +8 -1
- package/dist/shared/ui/Badge.css +21 -10
- package/dist/shared/ui/Button.css +7 -6
- package/dist/shared/ui/IconButton.css +16 -10
- package/dist/shared/ui/Placeholder.css +2 -0
- package/dist/shared/ui/Section.css +42 -3
- package/dist/shared/ui/Section.d.ts +3 -1
- package/dist/shared/ui/Section.jsx +14 -6
- package/dist/shared/ui/Select.css +11 -4
- package/dist/shared/ui/Tabs.css +30 -16
- package/package.json +5 -5
- package/dist/shared/ui/Cascade.css +0 -29
- package/dist/shared/ui/Cascade.d.ts +0 -4
- package/dist/shared/ui/Cascade.jsx +0 -4
- package/dist/shared/ui/Dialog.css +0 -25
- package/dist/shared/ui/Dialog.d.ts +0 -5
- package/dist/shared/ui/Dialog.jsx +0 -5
- package/dist/shared/ui/Table.css +0 -22
- package/dist/shared/ui/Table.d.ts +0 -11
- package/dist/shared/ui/Table.jsx +0 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @solidjs/start
|
|
2
2
|
|
|
3
|
+
## 2.0.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ac3f321: Parse stack traces in the dev toolbar's error overlay with `error-stack-parser-es/lite` instead of `error-stack-parser`. The lite entry point is a much smaller, ESM-only parser that returns plain frame objects rather than class instances, and it no longer pulls in the `stackframe` package. Errors that carry no stack are now handled as an empty frame list instead of throwing.
|
|
8
|
+
- ac3f321: Redesign the dev toolbar. The toolbar and its panels share a common set of design tokens, giving them a consistent dark palette, translucent surfaces, and elevation. The server function inspector is now a master-detail split with a persistent call list beside the request/response pane, and the error overlay places the stack frame list beside a code preview that fills the panel. Headers, form data, and URL search params render as aligned key-value tables, the hex viewer gains an offset gutter with the ASCII column aligned per row, and blobs are shown as file cards with their type and size. The seroval body inspector is now an expandable tree with collapsed previews, syntax-colored values, cycle detection, and live promise and stream state, replacing the previous column-based drill-down.
|
|
9
|
+
|
|
10
|
+
Along with the redesign, the toolbar only starts a drag from the toolbar itself rather than from its panels, unhandled promise rejections are captured by the error overlay, the code preview shows more surrounding lines, and a stack frame whose source cannot be loaded now stays listed and reports that its source is unavailable instead of silently rendering nothing.
|
|
11
|
+
|
|
12
|
+
- ac3f321: Use `@jridgewell/trace-mapping` instead of `source-map-js` to resolve original sources in the dev toolbar's error overlay. It decodes mappings lazily, so only the positions actually inspected are resolved, it is significantly smaller in the browser bundle, and it understands indexed source maps.
|
|
13
|
+
- f619c7f: Pre-bundle the dev toolbar's CommonJS dependencies so it no longer throws on every dev page load
|
|
14
|
+
- d43ad1b: Update Solid to 1.9.15 to include the latest lazy loading and hydration fixes
|
|
15
|
+
|
|
3
16
|
## 2.0.3
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -19,7 +19,7 @@ async function loadHighlighter() {
|
|
|
19
19
|
}
|
|
20
20
|
return HIGHLIGHTER;
|
|
21
21
|
}
|
|
22
|
-
const RANGE =
|
|
22
|
+
const RANGE = 15;
|
|
23
23
|
export function CodeView(props) {
|
|
24
24
|
const lines = () => props.content.split("\n").map((item, index) => ({
|
|
25
25
|
index: index + 1,
|
|
@@ -34,9 +34,14 @@ export function CodeView(props) {
|
|
|
34
34
|
.join("\n"), async (value) => {
|
|
35
35
|
const highlighter = await loadHighlighter();
|
|
36
36
|
const fileExtension = props.fileName.split(/[#?]/)[0].split(".").pop()?.trim();
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
// Only these grammars are loaded — anything else would make shiki
|
|
38
|
+
// throw. Fall back to plain JS highlighting for unknown sources.
|
|
39
|
+
let lang = "js";
|
|
40
|
+
if (fileExtension === "jsx" ||
|
|
41
|
+
fileExtension === "ts" ||
|
|
42
|
+
fileExtension === "tsx" ||
|
|
43
|
+
fileExtension === "js") {
|
|
44
|
+
lang = fileExtension;
|
|
40
45
|
}
|
|
41
46
|
return highlighter.codeToHtml(value, {
|
|
42
47
|
theme: "dark-plus",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StackFrameLite } from "error-stack-parser-es/lite";
|
|
1
2
|
import { type Accessor } from "solid-js";
|
|
2
3
|
export interface StackFrameSource {
|
|
3
4
|
content: string;
|
|
@@ -6,4 +7,4 @@ export interface StackFrameSource {
|
|
|
6
7
|
line: number;
|
|
7
8
|
column: number;
|
|
8
9
|
}
|
|
9
|
-
export declare function createStackFrame(stackframe:
|
|
10
|
+
export declare function createStackFrame(stackframe: StackFrameLite, isCompiled: () => boolean): Accessor<StackFrameSource>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { originalPositionFor, sourceContentFor } from "@jridgewell/trace-mapping";
|
|
1
2
|
import { createMemo, createResource } from "solid-js";
|
|
2
3
|
import getSourceMap from "./get-source-map.js";
|
|
3
4
|
const HTTP_URL_REGEX = /^https?:\/\//;
|
|
@@ -20,27 +21,36 @@ function getActualFileSource(path) {
|
|
|
20
21
|
}
|
|
21
22
|
export function createStackFrame(stackframe, isCompiled) {
|
|
22
23
|
const [data] = createResource(() => ({
|
|
23
|
-
fileName: stackframe.
|
|
24
|
-
line: stackframe.
|
|
25
|
-
column: stackframe.
|
|
26
|
-
functionName: stackframe.
|
|
24
|
+
fileName: stackframe.file,
|
|
25
|
+
line: stackframe.line,
|
|
26
|
+
column: stackframe.col,
|
|
27
|
+
functionName: stackframe.function,
|
|
27
28
|
}), async (source) => {
|
|
28
29
|
if (!source.fileName) {
|
|
29
30
|
return null;
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
// Sources can be unreachable — node internals, extension scripts,
|
|
33
|
+
// files outside the dev server's allowlist. Treat any failure as
|
|
34
|
+
// "no source" instead of throwing into the error boundary.
|
|
35
|
+
try {
|
|
36
|
+
const url = getActualFileSource(source.fileName);
|
|
37
|
+
const response = await fetch(url);
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const content = await response.text();
|
|
42
|
+
const sourceMap = await getSourceMap(url, content);
|
|
43
|
+
return {
|
|
44
|
+
source,
|
|
45
|
+
content,
|
|
46
|
+
sourceMap,
|
|
47
|
+
isServer: isServerSource(source.fileName),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.warn("[start dev toolbar] failed to load source for stack frame", error);
|
|
34
52
|
return null;
|
|
35
53
|
}
|
|
36
|
-
const content = await response.text();
|
|
37
|
-
const sourceMap = await getSourceMap(url, content);
|
|
38
|
-
return {
|
|
39
|
-
source,
|
|
40
|
-
content,
|
|
41
|
-
sourceMap,
|
|
42
|
-
isServer: isServerSource(source.fileName),
|
|
43
|
-
};
|
|
44
54
|
});
|
|
45
55
|
const info = createMemo(() => {
|
|
46
56
|
const current = data();
|
|
@@ -52,8 +62,8 @@ export function createStackFrame(stackframe, isCompiled) {
|
|
|
52
62
|
if (isServer) {
|
|
53
63
|
// The position is already original; only the original content needs
|
|
54
64
|
// to be pulled out of the source map.
|
|
55
|
-
const originalContent = sourceMap.sources.length
|
|
56
|
-
?
|
|
65
|
+
const originalContent = sourceMap.sources.length && sourceMap.sources[0] != null
|
|
66
|
+
? sourceContentFor(sourceMap, sourceMap.sources[0])
|
|
57
67
|
: null;
|
|
58
68
|
if (originalContent) {
|
|
59
69
|
return {
|
|
@@ -66,14 +76,14 @@ export function createStackFrame(stackframe, isCompiled) {
|
|
|
66
76
|
}
|
|
67
77
|
}
|
|
68
78
|
else {
|
|
69
|
-
const result =
|
|
79
|
+
const result = originalPositionFor(sourceMap, {
|
|
70
80
|
line: source.line,
|
|
71
81
|
column: source.column,
|
|
72
82
|
});
|
|
73
83
|
if (result.source) {
|
|
74
84
|
return {
|
|
75
85
|
...result,
|
|
76
|
-
content:
|
|
86
|
+
content: sourceContentFor(sourceMap, result.source),
|
|
77
87
|
};
|
|
78
88
|
}
|
|
79
89
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default function getSourceMap(url: string, content: string): Promise<
|
|
1
|
+
import { type TraceMap } from "@jridgewell/trace-mapping";
|
|
2
|
+
export default function getSourceMap(url: string, content: string): Promise<TraceMap | null>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AnyMap } from "@jridgewell/trace-mapping";
|
|
2
2
|
const INLINE_SOURCEMAP_REGEX = /^data:application\/json[^,]+base64,/;
|
|
3
3
|
const SOURCEMAP_REGEX = /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^*]+?)[ \t]*(?:\*\/)[ \t]*$)/;
|
|
4
4
|
export default async function getSourceMap(url, content) {
|
|
@@ -21,5 +21,6 @@ export default async function getSourceMap(url, content) {
|
|
|
21
21
|
}
|
|
22
22
|
const response = await fetch(sourceMapUrl);
|
|
23
23
|
const rawSourceMap = await response.json();
|
|
24
|
-
|
|
24
|
+
// AnyMap also handles indexed ("sections") source maps
|
|
25
|
+
return new AnyMap(rawSourceMap);
|
|
25
26
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @refresh skip
|
|
2
|
-
import
|
|
2
|
+
import { parse as parseErrorStack } from "error-stack-parser-es/lite";
|
|
3
3
|
import * as htmlToImage from "html-to-image";
|
|
4
4
|
import { createMemo, createSignal, ErrorBoundary, For, Show, Suspense } from "solid-js";
|
|
5
5
|
import IconButton from "../../ui/IconButton.jsx";
|
|
@@ -48,47 +48,39 @@ function CodeFallback() {
|
|
|
48
48
|
<span>Source not available.</span>
|
|
49
49
|
</div>);
|
|
50
50
|
}
|
|
51
|
+
// Frame rendered from the raw (unmapped) stack info — used while the source
|
|
52
|
+
// is loading or when it is unreachable, so the frame never vanishes from
|
|
53
|
+
// the list.
|
|
54
|
+
function RawStackFrameOption(props) {
|
|
55
|
+
const fileName = props.frame.file;
|
|
56
|
+
return (<SelectOption value={props.frame} disabled={props.disabled} data-start-error-viewer-stack-frame>
|
|
57
|
+
<span data-start-error-viewer-stack-frame-function>
|
|
58
|
+
{props.frame.function ?? "<anonymous>"}
|
|
59
|
+
</span>
|
|
60
|
+
<span data-start-error-viewer-stack-frame-file>
|
|
61
|
+
{fileName
|
|
62
|
+
? getFilePath({
|
|
63
|
+
source: fileName,
|
|
64
|
+
content: "",
|
|
65
|
+
line: props.frame.line,
|
|
66
|
+
column: props.frame.col,
|
|
67
|
+
name: props.frame.function,
|
|
68
|
+
})
|
|
69
|
+
: "<unknown source>"}
|
|
70
|
+
</span>
|
|
71
|
+
</SelectOption>);
|
|
72
|
+
}
|
|
51
73
|
function StackFramesContent(props) {
|
|
52
|
-
const stackframes =
|
|
74
|
+
const stackframes = parseErrorStack(props.error, { allowEmpty: true });
|
|
53
75
|
const [selectedFrame, setSelectedFrame] = createSignal(stackframes[0]);
|
|
54
76
|
return (<div data-start-error-viewer-stack-frames-content>
|
|
55
|
-
<div data-start-error-viewer-stack-frames-code>
|
|
56
|
-
<ErrorBoundary fallback={null}>
|
|
57
|
-
{(() => {
|
|
58
|
-
const data = createStackFrame(selectedFrame(), () => props.isCompiled);
|
|
59
|
-
return (<Suspense fallback={<CodeFallback />}>
|
|
60
|
-
<Show when={data()} keyed fallback={<CodeFallback />}>
|
|
61
|
-
{source => (<>
|
|
62
|
-
<span data-start-error-viewer-stack-frames-code-source>{source.source}</span>
|
|
63
|
-
<div data-start-error-viewer-stack-frames-code-container>
|
|
64
|
-
<CodeView fileName={source.source} line={source.line} content={source.content}/>
|
|
65
|
-
</div>
|
|
66
|
-
</>)}
|
|
67
|
-
</Show>
|
|
68
|
-
</Suspense>);
|
|
69
|
-
})()}
|
|
70
|
-
</ErrorBoundary>
|
|
71
|
-
</div>
|
|
72
77
|
<Select data-start-error-viewer-stack-frames value={selectedFrame()} onChange={setSelectedFrame}>
|
|
73
78
|
<For each={stackframes}>
|
|
74
|
-
{current => (<ErrorBoundary fallback={<
|
|
75
|
-
<span data-start-error-viewer-stack-frame-function>
|
|
76
|
-
{current.functionName ?? "<anonymous>"}
|
|
77
|
-
</span>
|
|
78
|
-
<span data-start-error-viewer-stack-frame-file>
|
|
79
|
-
{getFilePath({
|
|
80
|
-
source: current.getFileName(),
|
|
81
|
-
content: "",
|
|
82
|
-
line: current.getLineNumber(),
|
|
83
|
-
column: current.getColumnNumber(),
|
|
84
|
-
name: current.getFunctionName(),
|
|
85
|
-
})}
|
|
86
|
-
</span>
|
|
87
|
-
</SelectOption>}>
|
|
79
|
+
{current => (<ErrorBoundary fallback={<RawStackFrameOption frame={current} disabled/>}>
|
|
88
80
|
{(() => {
|
|
89
81
|
const data = createStackFrame(current, () => props.isCompiled);
|
|
90
|
-
return (<Suspense>
|
|
91
|
-
<Show when={data()} keyed>
|
|
82
|
+
return (<Suspense fallback={<RawStackFrameOption frame={current}/>}>
|
|
83
|
+
<Show when={data()} keyed fallback={<RawStackFrameOption frame={current}/>}>
|
|
92
84
|
{source => (<SelectOption data-start-error-viewer-stack-frame value={current}>
|
|
93
85
|
<span data-start-error-viewer-stack-frame-function>
|
|
94
86
|
{source.name ?? "<anonymous>"}
|
|
@@ -103,6 +95,31 @@ function StackFramesContent(props) {
|
|
|
103
95
|
</ErrorBoundary>)}
|
|
104
96
|
</For>
|
|
105
97
|
</Select>
|
|
98
|
+
<div data-start-error-viewer-stack-frames-code>
|
|
99
|
+
<Show when={selectedFrame()} keyed>
|
|
100
|
+
{frame => (
|
|
101
|
+
// Keyed on the frame so a failure inspecting one frame (an
|
|
102
|
+
// unreachable source, a failed highlight) resets when another
|
|
103
|
+
// frame is selected instead of leaving the pane stuck.
|
|
104
|
+
<ErrorBoundary fallback={<CodeFallback />}>
|
|
105
|
+
{(() => {
|
|
106
|
+
const data = createStackFrame(frame, () => props.isCompiled);
|
|
107
|
+
return (<Suspense fallback={<CodeFallback />}>
|
|
108
|
+
<Show when={data()} keyed fallback={<CodeFallback />}>
|
|
109
|
+
{source => (<>
|
|
110
|
+
<span data-start-error-viewer-stack-frames-code-source>
|
|
111
|
+
{source.source}
|
|
112
|
+
</span>
|
|
113
|
+
<div data-start-error-viewer-stack-frames-code-container>
|
|
114
|
+
<CodeView fileName={source.source} line={source.line} content={source.content}/>
|
|
115
|
+
</div>
|
|
116
|
+
</>)}
|
|
117
|
+
</Show>
|
|
118
|
+
</Suspense>);
|
|
119
|
+
})()}
|
|
120
|
+
</ErrorBoundary>)}
|
|
121
|
+
</Show>
|
|
122
|
+
</div>
|
|
106
123
|
</div>);
|
|
107
124
|
}
|
|
108
125
|
function StackFrames(props) {
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
flex-direction: column;
|
|
4
4
|
|
|
5
5
|
width: 100%;
|
|
6
|
+
height: 100%;
|
|
7
|
+
min-height: 0;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
[data-start-error-viewer-navbar] {
|
|
@@ -11,6 +13,16 @@
|
|
|
11
13
|
align-items: center;
|
|
12
14
|
justify-content: space-between;
|
|
13
15
|
gap: 0.5rem;
|
|
16
|
+
|
|
17
|
+
padding: 0.5rem 0.75rem;
|
|
18
|
+
|
|
19
|
+
position: sticky;
|
|
20
|
+
top: 0;
|
|
21
|
+
z-index: 1;
|
|
22
|
+
|
|
23
|
+
background: var(--start-dt-bg-glass);
|
|
24
|
+
backdrop-filter: blur(16px) saturate(140%);
|
|
25
|
+
-webkit-backdrop-filter: blur(16px) saturate(140%);
|
|
14
26
|
}
|
|
15
27
|
|
|
16
28
|
[data-start-error-viewer-pagination] {
|
|
@@ -20,8 +32,8 @@
|
|
|
20
32
|
justify-content: center;
|
|
21
33
|
gap: 0.5rem;
|
|
22
34
|
border-radius: 9999px;
|
|
23
|
-
color:
|
|
24
|
-
background-color:
|
|
35
|
+
color: var(--start-dt-text);
|
|
36
|
+
background-color: var(--start-dt-surface);
|
|
25
37
|
padding: 0.25rem;
|
|
26
38
|
}
|
|
27
39
|
|
|
@@ -30,23 +42,23 @@
|
|
|
30
42
|
}
|
|
31
43
|
|
|
32
44
|
[data-start-error-viewer-button]:hover {
|
|
33
|
-
color:
|
|
34
|
-
background-color:
|
|
45
|
+
color: var(--start-dt-text);
|
|
46
|
+
background-color: var(--start-dt-surface-hover);
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
[data-start-error-viewer-button]:focus {
|
|
38
50
|
outline: 2px solid transparent;
|
|
39
51
|
outline-offset: 2px;
|
|
40
|
-
outline-color:
|
|
52
|
+
outline-color: var(--start-dt-accent);
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
[data-start-error-viewer-button]:focus-visible {
|
|
44
|
-
box-shadow: 0 0 0
|
|
56
|
+
box-shadow: 0 0 0 2px var(--start-dt-accent);
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
[data-start-error-viewer-button]:active {
|
|
48
|
-
color:
|
|
49
|
-
background-color:
|
|
60
|
+
color: var(--start-dt-accent);
|
|
61
|
+
background-color: var(--start-dt-surface-active);
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
[data-start-error-viewer-button] > svg {
|
|
@@ -55,9 +67,11 @@
|
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
[data-start-error-viewer-page-counter] {
|
|
58
|
-
font-size: 0.
|
|
70
|
+
font-size: 0.8125rem;
|
|
59
71
|
line-height: 1.25rem;
|
|
60
72
|
font-weight: 600;
|
|
73
|
+
font-variant-numeric: tabular-nums;
|
|
74
|
+
color: var(--start-dt-text-muted);
|
|
61
75
|
}
|
|
62
76
|
|
|
63
77
|
[data-start-error-viewer-navbar-left] {
|
|
@@ -73,7 +87,7 @@
|
|
|
73
87
|
justify-content: center;
|
|
74
88
|
gap: 0.25rem;
|
|
75
89
|
border-radius: 9999px;
|
|
76
|
-
color:
|
|
90
|
+
color: var(--start-dt-text);
|
|
77
91
|
padding: 0.25rem;
|
|
78
92
|
}
|
|
79
93
|
|
|
@@ -83,9 +97,13 @@
|
|
|
83
97
|
gap: 0.75rem;
|
|
84
98
|
padding: 1rem;
|
|
85
99
|
|
|
100
|
+
flex: 1;
|
|
101
|
+
min-height: 0;
|
|
102
|
+
overflow: hidden;
|
|
103
|
+
|
|
86
104
|
border-top-width: 1px;
|
|
87
105
|
border-top-style: solid;
|
|
88
|
-
border-top-color:
|
|
106
|
+
border-top-color: var(--start-dt-border-soft);
|
|
89
107
|
}
|
|
90
108
|
|
|
91
109
|
[data-start-error-viewer-error-info] {
|
|
@@ -95,107 +113,137 @@
|
|
|
95
113
|
}
|
|
96
114
|
|
|
97
115
|
[data-start-error-viewer-error-info-name] {
|
|
98
|
-
font-weight:
|
|
99
|
-
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
font-size: 0.8125rem;
|
|
118
|
+
text-transform: uppercase;
|
|
119
|
+
letter-spacing: 0.06em;
|
|
120
|
+
color: var(--start-dt-text-muted);
|
|
100
121
|
}
|
|
101
122
|
|
|
102
123
|
[data-start-error-viewer-error-info-message] {
|
|
103
|
-
font-size: 1.
|
|
104
|
-
line-height:
|
|
105
|
-
color:
|
|
124
|
+
font-size: 1.25rem;
|
|
125
|
+
line-height: 1.75rem;
|
|
126
|
+
color: var(--start-dt-danger);
|
|
106
127
|
font-weight: 700;
|
|
128
|
+
font-family:
|
|
129
|
+
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
|
|
130
|
+
monospace;
|
|
107
131
|
}
|
|
108
132
|
|
|
109
133
|
[data-start-error-viewer-stack-frames-content] {
|
|
110
134
|
display: flex;
|
|
111
|
-
flex-direction:
|
|
135
|
+
flex-direction: row;
|
|
112
136
|
gap: 0.75rem;
|
|
137
|
+
|
|
138
|
+
flex: 1;
|
|
139
|
+
min-height: 0;
|
|
113
140
|
}
|
|
114
141
|
|
|
115
142
|
[data-start-error-viewer-stack-frames] {
|
|
116
|
-
|
|
117
|
-
max-
|
|
143
|
+
width: 38%;
|
|
144
|
+
max-width: 20rem;
|
|
145
|
+
flex-shrink: 0;
|
|
118
146
|
overflow-y: auto;
|
|
147
|
+
|
|
148
|
+
border: 1px var(--start-dt-border-soft) solid;
|
|
149
|
+
border-radius: 0.5rem;
|
|
119
150
|
}
|
|
120
151
|
|
|
121
152
|
[data-start-error-viewer-stack-frame] {
|
|
122
153
|
display: flex;
|
|
123
|
-
flex-direction:
|
|
124
|
-
align-items:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
color: rgb(249 250 251);
|
|
154
|
+
flex-direction: column;
|
|
155
|
+
align-items: flex-start;
|
|
156
|
+
gap: 0.125rem;
|
|
157
|
+
color: var(--start-dt-text);
|
|
128
158
|
cursor: default;
|
|
129
159
|
user-select: none;
|
|
130
160
|
padding-top: 0.5rem;
|
|
131
161
|
padding-bottom: 0.5rem;
|
|
132
|
-
padding-left:
|
|
133
|
-
padding-right:
|
|
162
|
+
padding-left: 0.75rem;
|
|
163
|
+
padding-right: 0.75rem;
|
|
134
164
|
font-size: 0.875rem;
|
|
135
165
|
line-height: 1.25rem;
|
|
136
166
|
outline: none;
|
|
167
|
+
|
|
168
|
+
min-width: 0;
|
|
169
|
+
max-width: 100%;
|
|
137
170
|
}
|
|
138
171
|
|
|
139
172
|
[data-start-error-viewer-stack-frame][tc-active],
|
|
140
173
|
[data-start-error-viewer-stack-frame][tc-selected],
|
|
141
174
|
[data-start-error-viewer-stack-frame]:focus {
|
|
142
|
-
color:
|
|
143
|
-
background-color:
|
|
175
|
+
color: var(--start-dt-text);
|
|
176
|
+
background-color: var(--start-dt-accent-soft);
|
|
177
|
+
border-left-color: var(--start-dt-accent);
|
|
144
178
|
}
|
|
145
179
|
|
|
146
180
|
[data-start-error-viewer-stack-frame-function] {
|
|
147
|
-
font-weight:
|
|
181
|
+
font-weight: 600;
|
|
182
|
+
font-family:
|
|
183
|
+
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
|
|
184
|
+
monospace;
|
|
148
185
|
}
|
|
149
186
|
|
|
150
187
|
[data-start-error-viewer-stack-frame-file] {
|
|
151
188
|
white-space: nowrap;
|
|
189
|
+
overflow: hidden;
|
|
190
|
+
text-overflow: ellipsis;
|
|
191
|
+
max-width: 100%;
|
|
152
192
|
font-size: 0.75rem;
|
|
153
|
-
/* 12px */
|
|
154
193
|
line-height: 1rem;
|
|
155
|
-
|
|
194
|
+
color: var(--start-dt-text-muted);
|
|
156
195
|
}
|
|
157
196
|
|
|
158
197
|
[data-start-error-viewer-stack-frames-code] {
|
|
159
|
-
padding: 0.5rem
|
|
160
|
-
gap: 0.
|
|
198
|
+
padding: 0.5rem 0.75rem;
|
|
199
|
+
gap: 0.375rem;
|
|
161
200
|
flex: 1 1 0%;
|
|
201
|
+
min-width: 0;
|
|
202
|
+
min-height: 0;
|
|
162
203
|
display: flex;
|
|
163
204
|
flex-direction: column;
|
|
164
|
-
align-items:
|
|
165
|
-
justify-content:
|
|
205
|
+
align-items: stretch;
|
|
206
|
+
justify-content: flex-start;
|
|
166
207
|
border-radius: 0.5rem;
|
|
167
|
-
background-color:
|
|
208
|
+
background-color: var(--start-dt-surface);
|
|
209
|
+
border: 1px var(--start-dt-border-soft) solid;
|
|
168
210
|
}
|
|
169
211
|
|
|
170
212
|
[data-start-error-viewer-stack-frames-code-fallback] {
|
|
171
213
|
display: flex;
|
|
172
214
|
align-items: center;
|
|
173
215
|
justify-content: center;
|
|
174
|
-
color:
|
|
175
|
-
|
|
216
|
+
color: var(--start-dt-text-muted);
|
|
217
|
+
flex: 1;
|
|
218
|
+
min-height: 8rem;
|
|
176
219
|
font-size: 0.875rem;
|
|
177
220
|
line-height: 1.25rem;
|
|
178
221
|
}
|
|
179
222
|
|
|
180
223
|
[data-start-error-viewer-stack-frames-code-source] {
|
|
181
|
-
font-size: 0.
|
|
224
|
+
font-size: 0.8125rem;
|
|
182
225
|
line-height: 1.25rem;
|
|
183
|
-
color:
|
|
226
|
+
color: var(--start-dt-text-muted);
|
|
227
|
+
font-family:
|
|
228
|
+
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
|
|
229
|
+
monospace;
|
|
184
230
|
}
|
|
185
231
|
|
|
186
232
|
[data-start-error-viewer-stack-frames-code-container] {
|
|
187
233
|
width: 100%;
|
|
188
234
|
overflow: hidden;
|
|
189
235
|
min-width: 100%;
|
|
190
|
-
|
|
236
|
+
flex: 1;
|
|
237
|
+
min-height: 0;
|
|
191
238
|
border-top-width: 1px;
|
|
192
239
|
border-top-style: solid;
|
|
193
|
-
border-top-color:
|
|
240
|
+
border-top-color: var(--start-dt-border-soft);
|
|
194
241
|
}
|
|
195
242
|
|
|
196
243
|
[data-start-error-viewer-code-view] {
|
|
197
244
|
overflow: auto;
|
|
198
245
|
min-width: 100%;
|
|
246
|
+
height: 100%;
|
|
199
247
|
}
|
|
200
248
|
|
|
201
249
|
[data-start-error-viewer-code-view] > .shiki {
|
|
@@ -220,5 +268,5 @@
|
|
|
220
268
|
}
|
|
221
269
|
|
|
222
270
|
[data-start-error-viewer-error-line] {
|
|
223
|
-
background-color:
|
|
271
|
+
background-color: oklch(0.63 0.21 25 / 0.25);
|
|
224
272
|
}
|
|
@@ -1,4 +1,52 @@
|
|
|
1
|
+
[data-start-blob-viewer] {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: 0.625rem;
|
|
5
|
+
|
|
6
|
+
align-self: flex-start;
|
|
7
|
+
|
|
8
|
+
padding: 0.5rem 0.75rem;
|
|
9
|
+
text-align: left;
|
|
10
|
+
}
|
|
11
|
+
|
|
1
12
|
[data-start-blob-viewer] svg {
|
|
2
|
-
width:
|
|
3
|
-
height:
|
|
13
|
+
width: 1.5rem;
|
|
14
|
+
height: 1.5rem;
|
|
15
|
+
flex-shrink: 0;
|
|
16
|
+
|
|
17
|
+
color: var(--start-dt-accent, currentColor);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
[data-start-blob-viewer-info] {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
align-items: flex-start;
|
|
24
|
+
gap: 0.25rem;
|
|
25
|
+
|
|
26
|
+
min-width: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
[data-start-blob-viewer-name] {
|
|
30
|
+
font-family:
|
|
31
|
+
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
|
|
32
|
+
monospace;
|
|
33
|
+
font-size: 0.8125rem;
|
|
34
|
+
line-height: 1rem;
|
|
35
|
+
font-weight: 600;
|
|
36
|
+
|
|
37
|
+
max-width: 100%;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
text-overflow: ellipsis;
|
|
40
|
+
white-space: nowrap;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
[data-start-blob-viewer-meta] {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: 0.5rem;
|
|
47
|
+
|
|
48
|
+
font-size: 0.75rem;
|
|
49
|
+
line-height: 1rem;
|
|
50
|
+
|
|
51
|
+
color: var(--start-dt-text-muted, inherit);
|
|
4
52
|
}
|
|
@@ -8,6 +8,15 @@ function DocumentIcon(props) {
|
|
|
8
8
|
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/>
|
|
9
9
|
</svg>);
|
|
10
10
|
}
|
|
11
|
+
function formatSize(size) {
|
|
12
|
+
if (size < 1024) {
|
|
13
|
+
return `${size} B`;
|
|
14
|
+
}
|
|
15
|
+
if (size < 1024 * 1024) {
|
|
16
|
+
return `${(size / 1024).toFixed(1)} KB`;
|
|
17
|
+
}
|
|
18
|
+
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
19
|
+
}
|
|
11
20
|
function BlobViewerInner(props) {
|
|
12
21
|
const fileURL = createMemo(() => URL.createObjectURL(props.source));
|
|
13
22
|
onCleanup(() => {
|
|
@@ -22,11 +31,21 @@ function BlobViewerInner(props) {
|
|
|
22
31
|
link.click();
|
|
23
32
|
document.body.removeChild(link);
|
|
24
33
|
}
|
|
34
|
+
const name = createMemo(() => {
|
|
35
|
+
if (props.source instanceof File) {
|
|
36
|
+
return props.source.name;
|
|
37
|
+
}
|
|
38
|
+
return props.source.type || "Blob";
|
|
39
|
+
});
|
|
25
40
|
return (<Button data-start-blob-viewer onClick={() => openFileInNewTab()}>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
<DocumentIcon title={name()}/>
|
|
42
|
+
<span data-start-blob-viewer-info>
|
|
43
|
+
<span data-start-blob-viewer-name>{name()}</span>
|
|
44
|
+
<span data-start-blob-viewer-meta>
|
|
45
|
+
<Badge type="info">{props.source.type || "unknown"}</Badge>
|
|
46
|
+
<span>{formatSize(props.source.size)}</span>
|
|
47
|
+
</span>
|
|
48
|
+
</span>
|
|
30
49
|
</Button>);
|
|
31
50
|
}
|
|
32
51
|
export function BlobViewer(props) {
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { createResource, For, Show, Suspense } from "solid-js";
|
|
2
2
|
import { Section } from "../../ui/Section.jsx";
|
|
3
|
+
import { Text } from "../../ui/Text.jsx";
|
|
3
4
|
import { BlobViewer } from "./BlobViewer.jsx";
|
|
4
|
-
import { PropertySeparator, SerovalValue } from "./SerovalValue.jsx";
|
|
5
5
|
function FormDataViewerInner(props) {
|
|
6
6
|
return (<Section title="FormData" options={{ size: "sm" }}>
|
|
7
|
-
<div data-start-form-data-viewer data-start-
|
|
7
|
+
<div data-start-form-data-viewer data-start-kv-table>
|
|
8
8
|
<For each={Array.from(props.source.entries())}>
|
|
9
|
-
{([key, value]) => (<div data-start-property>
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
{([key, value]) => (<div data-start-property data-start-kv-row>
|
|
10
|
+
<Text data-start-kv-key options={{ size: "xs", weight: "semibold", font: "mono", wrap: "nowrap" }}>
|
|
11
|
+
{key}
|
|
12
|
+
</Text>
|
|
13
|
+
{typeof value === "string" ? (<Text data-start-kv-value options={{ size: "xs", font: "mono", wrap: "wrap" }}>
|
|
14
|
+
{JSON.stringify(value)}
|
|
15
|
+
</Text>) : (<BlobViewer source={value}/>)}
|
|
13
16
|
</div>)}
|
|
14
17
|
</For>
|
|
15
18
|
</div>
|