@runwisp/ui 0.6.0 → 0.8.0
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/package.json +4 -12
- package/src/lib/components/Alert.svelte +4 -4
- package/src/lib/components/Avatar.svelte +8 -8
- package/src/lib/components/Badge.svelte +23 -23
- package/src/lib/components/CronInput.svelte +1 -1
- package/src/lib/components/Dropdown.svelte +16 -3
- package/src/lib/components/DurationInput.svelte +4 -2
- package/src/lib/components/EmptyState.svelte +1 -1
- package/src/lib/components/FormField.svelte +4 -2
- package/src/lib/components/Input.svelte +4 -2
- package/src/lib/components/LogConsole.svelte +76 -29
- package/src/lib/components/Progress.svelte +4 -4
- package/src/lib/components/RadioGroup.svelte +3 -1
- package/src/lib/components/Select.svelte +1 -1
- package/src/lib/components/Sparkline.svelte +11 -3
- package/src/lib/components/TaskCard.svelte +4 -4
- package/src/lib/components/Textarea.svelte +2 -2
- package/src/lib/components/ToastContainer.svelte +4 -4
- package/src/lib/components/Tooltip.svelte +11 -2
- package/src/lib/components/dashboard/RunDetailPanel.svelte +27 -17
- package/src/lib/components/dashboard/RunsList.svelte +19 -2
- package/src/lib/components/dashboard/cron-format.test.ts +67 -0
- package/src/lib/components/dashboard/cron-format.ts +71 -0
- package/src/lib/components/dashboard/status-config.test.ts +18 -0
- package/src/lib/components/dashboard/status-config.ts +19 -0
- package/src/lib/index.ts +1 -4
- package/src/lib/log-console/ansi.test.ts +50 -0
- package/src/lib/log-console/ansi.ts +39 -0
- package/src/lib/theme-tokens.css +33 -0
- package/src/lib/theme.css +6 -0
- package/src/lib/utils/format.test.ts +37 -1
- package/src/lib/utils/format.ts +8 -6
- package/src/stories/Avatar.stories.svelte +1 -1
- package/src/stories/Breadcrumb.stories.svelte +3 -3
- package/src/stories/Card.stories.svelte +21 -19
- package/src/stories/Checkbox.stories.svelte +2 -2
- package/src/stories/DataGrid.stories.svelte +1 -1
- package/src/stories/Dropdown.stories.svelte +3 -3
- package/src/stories/EmptyState.stories.svelte +1 -1
- package/src/stories/LogConsole.stories.svelte +75 -0
- package/src/stories/LogConsoleDemo.svelte +60 -0
- package/src/stories/Logo.stories.svelte +9 -9
- package/src/stories/Modal.stories.svelte +12 -10
- package/src/stories/Progress.stories.svelte +6 -6
- package/src/stories/Tabs.stories.svelte +16 -12
- package/src/stories/Toggle.stories.svelte +2 -2
- package/src/stories/Tooltip.stories.svelte +5 -5
- package/src/lib/utils/toast.ts +0 -60
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runwisp/ui",
|
|
3
3
|
"description": "Svelte 5 UI component library for RunWisp dashboards and admin interfaces.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -15,14 +15,6 @@
|
|
|
15
15
|
"publishConfig": {
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
|
-
"scripts": {
|
|
19
|
-
"dev": "storybook dev -p 6006 --no-open",
|
|
20
|
-
"build": "echo '@runwisp/ui: no build step (raw source)'",
|
|
21
|
-
"check": "bun run check:types && bun run check:lint",
|
|
22
|
-
"check:types": "svelte-check --tsconfig ./tsconfig.json",
|
|
23
|
-
"check:lint": "prettier --check . && eslint .",
|
|
24
|
-
"format": "prettier --write ."
|
|
25
|
-
},
|
|
26
18
|
"files": [
|
|
27
19
|
"src"
|
|
28
20
|
],
|
|
@@ -58,8 +50,8 @@
|
|
|
58
50
|
"@chromatic-com/storybook": "^4.1.3",
|
|
59
51
|
"@eslint/compat": "^1.4.0",
|
|
60
52
|
"@eslint/js": "^9.39.1",
|
|
61
|
-
"@runwisp/eslint-config": "0.
|
|
62
|
-
"@runwisp/typescript-config": "0.
|
|
53
|
+
"@runwisp/eslint-config": "0.8.0",
|
|
54
|
+
"@runwisp/typescript-config": "0.8.0",
|
|
63
55
|
"@storybook/addon-a11y": "^10.1.9",
|
|
64
56
|
"@storybook/addon-docs": "^10.1.9",
|
|
65
57
|
"@storybook/addon-svelte-csf": "^5.0.10",
|
|
@@ -107,7 +99,7 @@
|
|
|
107
99
|
"@floating-ui/dom": "^1.7.4",
|
|
108
100
|
"@fontsource/tasa-orbiter": "^5.2.2",
|
|
109
101
|
"@lucide/svelte": "^0.561.0",
|
|
110
|
-
"@runwisp/common": "0.
|
|
102
|
+
"@runwisp/common": "0.8.0",
|
|
111
103
|
"@tanstack/svelte-virtual": "^3.13.25",
|
|
112
104
|
"ansi-to-html": "^0.7.2",
|
|
113
105
|
"cron-parser": "^5.4.0",
|
|
@@ -35,25 +35,25 @@
|
|
|
35
35
|
> = {
|
|
36
36
|
info: {
|
|
37
37
|
bg: "bg-info-soft",
|
|
38
|
-
border: "border-
|
|
38
|
+
border: "border-info-soft-border",
|
|
39
39
|
icon: Info,
|
|
40
40
|
iconColor: "text-info",
|
|
41
41
|
},
|
|
42
42
|
success: {
|
|
43
43
|
bg: "bg-success-soft",
|
|
44
|
-
border: "border-success-
|
|
44
|
+
border: "border-success-soft-border",
|
|
45
45
|
icon: CircleCheckBig,
|
|
46
46
|
iconColor: "text-success-surface",
|
|
47
47
|
},
|
|
48
48
|
warning: {
|
|
49
49
|
bg: "bg-warning-soft",
|
|
50
|
-
border: "border-warning-
|
|
50
|
+
border: "border-warning-soft-border",
|
|
51
51
|
icon: TriangleAlert,
|
|
52
52
|
iconColor: "text-warning-surface",
|
|
53
53
|
},
|
|
54
54
|
danger: {
|
|
55
55
|
bg: "bg-danger-soft",
|
|
56
|
-
border: "border-danger-
|
|
56
|
+
border: "border-danger-soft-border",
|
|
57
57
|
icon: CircleX,
|
|
58
58
|
iconColor: "text-danger-surface",
|
|
59
59
|
},
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
const statusClasses: Record<string, string> = {
|
|
33
|
-
online: "bg-success-
|
|
33
|
+
online: "bg-success-surface",
|
|
34
34
|
offline: "bg-on-surface-faint",
|
|
35
|
-
busy: "bg-danger-
|
|
36
|
-
away: "bg-warning-
|
|
35
|
+
busy: "bg-danger-surface",
|
|
36
|
+
away: "bg-warning-surface",
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
const statusSizes: Record<string, string> = {
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
|
|
47
47
|
// Generate a consistent color based on name
|
|
48
48
|
const colors = [
|
|
49
|
-
"bg-
|
|
50
|
-
"bg-
|
|
51
|
-
"bg-success-
|
|
52
|
-
"bg-warning-
|
|
53
|
-
"bg-danger-
|
|
49
|
+
"bg-primary",
|
|
50
|
+
"bg-info",
|
|
51
|
+
"bg-success-surface",
|
|
52
|
+
"bg-warning-surface",
|
|
53
|
+
"bg-danger-surface",
|
|
54
54
|
];
|
|
55
55
|
|
|
56
56
|
const colorIndex = $derived(
|
|
@@ -35,28 +35,28 @@
|
|
|
35
35
|
|
|
36
36
|
const variantClasses: Record<BadgeVariant, { solid: string; outline: string }> = {
|
|
37
37
|
default: {
|
|
38
|
-
solid: "bg-
|
|
39
|
-
outline: "bg-transparent border border-
|
|
38
|
+
solid: "bg-surface-sunken text-on-surface-muted",
|
|
39
|
+
outline: "bg-transparent border border-outline-hover text-on-surface-muted",
|
|
40
40
|
},
|
|
41
41
|
primary: {
|
|
42
|
-
solid: "bg-
|
|
43
|
-
outline: "bg-transparent border border-wisp-300 text-
|
|
42
|
+
solid: "bg-primary-soft text-primary-soft-text",
|
|
43
|
+
outline: "bg-transparent border border-wisp-300 text-primary-soft-text",
|
|
44
44
|
},
|
|
45
45
|
success: {
|
|
46
|
-
solid: "bg-success-
|
|
47
|
-
outline: "bg-transparent border border-success-300 text-success-
|
|
46
|
+
solid: "bg-success-soft text-success-soft-text",
|
|
47
|
+
outline: "bg-transparent border border-success-300 text-success-soft-text",
|
|
48
48
|
},
|
|
49
49
|
warning: {
|
|
50
|
-
solid: "bg-warning-
|
|
51
|
-
outline: "bg-transparent border border-warning-400 text-warning-
|
|
50
|
+
solid: "bg-warning-soft text-warning-soft-text",
|
|
51
|
+
outline: "bg-transparent border border-warning-400 text-warning-soft-text",
|
|
52
52
|
},
|
|
53
53
|
danger: {
|
|
54
|
-
solid: "bg-danger-
|
|
55
|
-
outline: "bg-transparent border border-danger-300 text-danger-
|
|
54
|
+
solid: "bg-danger-soft text-danger-soft-text",
|
|
55
|
+
outline: "bg-transparent border border-danger-300 text-danger-soft-text",
|
|
56
56
|
},
|
|
57
57
|
info: {
|
|
58
|
-
solid: "bg-
|
|
59
|
-
outline: "bg-transparent border border-aurora-300 text-
|
|
58
|
+
solid: "bg-info-soft text-info-soft-text",
|
|
59
|
+
outline: "bg-transparent border border-aurora-300 text-info-soft-text",
|
|
60
60
|
},
|
|
61
61
|
};
|
|
62
62
|
|
|
@@ -68,20 +68,20 @@
|
|
|
68
68
|
|
|
69
69
|
const dotColors: Record<BadgeVariant, string> = {
|
|
70
70
|
default: "bg-mist-500",
|
|
71
|
-
primary: "bg-
|
|
72
|
-
success: "bg-success-
|
|
73
|
-
warning: "bg-warning-
|
|
74
|
-
danger: "bg-danger-
|
|
75
|
-
info: "bg-
|
|
71
|
+
primary: "bg-primary",
|
|
72
|
+
success: "bg-success-surface",
|
|
73
|
+
warning: "bg-warning-surface",
|
|
74
|
+
danger: "bg-danger-surface",
|
|
75
|
+
info: "bg-info",
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
const indicatorTextColors: Record<BadgeVariant, string> = {
|
|
79
|
-
default: "text-
|
|
80
|
-
primary: "text-
|
|
81
|
-
success: "text-success-
|
|
82
|
-
warning: "text-warning-
|
|
83
|
-
danger: "text-danger-
|
|
84
|
-
info: "text-
|
|
79
|
+
default: "text-on-surface-muted",
|
|
80
|
+
primary: "text-primary",
|
|
81
|
+
success: "text-success-soft-text",
|
|
82
|
+
warning: "text-warning-soft-text",
|
|
83
|
+
danger: "text-danger-soft-text",
|
|
84
|
+
info: "text-info",
|
|
85
85
|
};
|
|
86
86
|
|
|
87
87
|
const spinnerSizeClasses: Record<BadgeSize, string> = {
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
<!-- Live Feedback -->
|
|
89
89
|
<div
|
|
90
90
|
class="flex items-start gap-3 rounded-md border p-3 transition-colors duration-200
|
|
91
|
-
{isError ? 'border-danger-
|
|
91
|
+
{isError ? 'border-danger-soft-border bg-danger-soft' : 'border-outline bg-surface-sunken'}"
|
|
92
92
|
>
|
|
93
93
|
<Info size={18} class="mt-0.5 {isError ? 'text-danger-surface' : 'text-primary'}" />
|
|
94
94
|
<div>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { Snippet } from "svelte";
|
|
6
6
|
import type { Component } from "svelte";
|
|
7
7
|
import { tick, onDestroy } from "svelte";
|
|
8
|
-
import { EllipsisVertical } from "@lucide/svelte";
|
|
8
|
+
import { EllipsisVertical, Check } from "@lucide/svelte";
|
|
9
9
|
import { scale } from "svelte/transition";
|
|
10
10
|
import { quintOut } from "svelte/easing";
|
|
11
11
|
import { computePosition, autoUpdate, flip, shift, offset } from "@floating-ui/dom";
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
danger?: boolean;
|
|
19
19
|
disabled?: boolean;
|
|
20
20
|
divider?: boolean;
|
|
21
|
+
selected?: boolean;
|
|
21
22
|
title?: string;
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -26,10 +27,18 @@
|
|
|
26
27
|
trigger?: Snippet;
|
|
27
28
|
children?: Snippet;
|
|
28
29
|
align?: "left" | "right";
|
|
30
|
+
triggerLabel?: string;
|
|
29
31
|
class?: string;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
let {
|
|
34
|
+
let {
|
|
35
|
+
items = [],
|
|
36
|
+
trigger,
|
|
37
|
+
children,
|
|
38
|
+
align = "right",
|
|
39
|
+
triggerLabel = "Options",
|
|
40
|
+
class: className = "",
|
|
41
|
+
}: Props = $props();
|
|
33
42
|
|
|
34
43
|
let open = $state(false);
|
|
35
44
|
let triggerEl = $state<HTMLElement | null>(null);
|
|
@@ -116,7 +125,8 @@
|
|
|
116
125
|
toggle(!open);
|
|
117
126
|
}}
|
|
118
127
|
bind:this={triggerEl}
|
|
119
|
-
aria-label=
|
|
128
|
+
aria-label={triggerLabel}
|
|
129
|
+
aria-expanded={open}
|
|
120
130
|
class="rounded-lg p-2 text-on-surface-muted transition-colors hover:bg-surface-sunken hover:text-on-surface focus:ring-2 focus:ring-outline focus:outline-none active:bg-surface-sunken"
|
|
121
131
|
>
|
|
122
132
|
{#if trigger}
|
|
@@ -186,6 +196,9 @@
|
|
|
186
196
|
<Icon size={16} class="opacity-70" />
|
|
187
197
|
{/if}
|
|
188
198
|
{item.label}
|
|
199
|
+
{#if item.selected}
|
|
200
|
+
<Check size={16} class="ml-auto text-primary" />
|
|
201
|
+
{/if}
|
|
189
202
|
</button>
|
|
190
203
|
{/if}
|
|
191
204
|
{/each}
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
|
|
148
148
|
{#if error}
|
|
149
149
|
<div
|
|
150
|
-
class="pointer-events-none absolute top-1/2 right-3 -translate-y-1/2 text-danger-
|
|
150
|
+
class="pointer-events-none absolute top-1/2 right-3 -translate-y-1/2 text-danger-soft-text"
|
|
151
151
|
>
|
|
152
152
|
<CircleAlert size={18} />
|
|
153
153
|
</div>
|
|
@@ -161,7 +161,9 @@
|
|
|
161
161
|
{parsedHuman}
|
|
162
162
|
</p>
|
|
163
163
|
{:else if error}
|
|
164
|
-
<p
|
|
164
|
+
<p
|
|
165
|
+
class="animate-in slide-in-from-top-1 fade-in text-sm text-danger-soft-text duration-200"
|
|
166
|
+
>
|
|
165
167
|
{error}
|
|
166
168
|
</p>
|
|
167
169
|
{:else if hint}
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<label for={generatedId} class="block text-sm font-medium text-on-surface-muted">
|
|
33
33
|
{label}
|
|
34
34
|
{#if required}
|
|
35
|
-
<span class="text-danger-
|
|
35
|
+
<span class="text-danger-soft-text">*</span>
|
|
36
36
|
{/if}
|
|
37
37
|
</label>
|
|
38
38
|
{/if}
|
|
@@ -44,7 +44,9 @@
|
|
|
44
44
|
{@render children()}
|
|
45
45
|
|
|
46
46
|
{#if error}
|
|
47
|
-
<p
|
|
47
|
+
<p
|
|
48
|
+
class="animate-in slide-in-from-top-1 fade-in text-sm text-danger-soft-text duration-200"
|
|
49
|
+
>
|
|
48
50
|
{error}
|
|
49
51
|
</p>
|
|
50
52
|
{:else if hint}
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
|
|
92
92
|
{#if error}
|
|
93
93
|
<div
|
|
94
|
-
class="pointer-events-none absolute top-1/2 right-3 -translate-y-1/2 text-danger-
|
|
94
|
+
class="pointer-events-none absolute top-1/2 right-3 -translate-y-1/2 text-danger-soft-text"
|
|
95
95
|
>
|
|
96
96
|
<CircleAlert size={18} />
|
|
97
97
|
</div>
|
|
@@ -105,7 +105,9 @@
|
|
|
105
105
|
</div>
|
|
106
106
|
|
|
107
107
|
{#if error}
|
|
108
|
-
<p
|
|
108
|
+
<p
|
|
109
|
+
class="animate-in slide-in-from-top-1 fade-in text-sm text-danger-soft-text duration-200"
|
|
110
|
+
>
|
|
109
111
|
{error}
|
|
110
112
|
</p>
|
|
111
113
|
{:else if hint}
|
|
@@ -3,27 +3,34 @@
|
|
|
3
3
|
|
|
4
4
|
<script lang="ts">
|
|
5
5
|
import { untrack } from "svelte";
|
|
6
|
-
import
|
|
6
|
+
import { ansiLineToHtml } from "../log-console/ansi.js";
|
|
7
7
|
import { LogCache } from "../log-console/LogCache.svelte.js";
|
|
8
8
|
import { LogFetcher } from "../log-console/LogFetcher.svelte.js";
|
|
9
9
|
import type { FetchLogsFn, LogEvent } from "../log-console/types.js";
|
|
10
10
|
import { formatBytes } from "../utils/format.js";
|
|
11
11
|
|
|
12
|
-
// Convert ANSI escape sequences in a single log line to safe HTML spans.
|
|
13
|
-
// A fresh converter per call ensures no colour state leaks between
|
|
14
|
-
// independently virtualised lines.
|
|
15
|
-
function ansiLineToHtml(text: string): string {
|
|
16
|
-
return new Convert({ escapeXML: true }).toHtml(text);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
12
|
interface Props {
|
|
20
13
|
fetchLogs?: FetchLogsFn;
|
|
21
14
|
chunkSize?: number;
|
|
22
15
|
lineHeight?: number;
|
|
23
16
|
class?: string;
|
|
17
|
+
// highlightLine, when set, scrolls the console so the given absolute
|
|
18
|
+
// line number is visible and pulses a short-lived flash on that row.
|
|
19
|
+
// The pulse is one-shot: changing the prop to a new line restarts the
|
|
20
|
+
// animation; setting it to null clears immediately.
|
|
21
|
+
highlightLine?: number | null;
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
let {
|
|
24
|
+
let {
|
|
25
|
+
fetchLogs,
|
|
26
|
+
chunkSize = 4096,
|
|
27
|
+
lineHeight = 20,
|
|
28
|
+
class: className = "",
|
|
29
|
+
highlightLine = null,
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
let flashLine = $state<number | null>(null);
|
|
33
|
+
let flashTimer: ReturnType<typeof setTimeout> | null = null;
|
|
27
34
|
|
|
28
35
|
const cache = new LogCache();
|
|
29
36
|
let fetcher: LogFetcher | null = $state(null);
|
|
@@ -202,6 +209,30 @@
|
|
|
202
209
|
scrollTop = 0;
|
|
203
210
|
}
|
|
204
211
|
|
|
212
|
+
// Search-hit deep-link: when highlightLine flips to a non-null number,
|
|
213
|
+
// scroll the viewport so that line is centred, and pulse a one-shot
|
|
214
|
+
// flash class for ~1.5s. Cleaning up the timer on prop change keeps a
|
|
215
|
+
// rapid sequence of jumps from leaking timers.
|
|
216
|
+
$effect(() => {
|
|
217
|
+
const target = highlightLine;
|
|
218
|
+
if (target === null || target === undefined) {
|
|
219
|
+
flashLine = null;
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (!containerEl) return;
|
|
223
|
+
const targetY = lineTop(target) - containerHeight / 2 + lineHeight / 2;
|
|
224
|
+
const clamped = Math.max(0, Math.min(targetY, totalHeight - containerHeight));
|
|
225
|
+
containerEl.scrollTo({ top: clamped, behavior: "smooth" });
|
|
226
|
+
isAutoScroll = false;
|
|
227
|
+
userScrolledUp = true;
|
|
228
|
+
flashLine = target;
|
|
229
|
+
if (flashTimer !== null) clearTimeout(flashTimer);
|
|
230
|
+
flashTimer = setTimeout(() => {
|
|
231
|
+
flashLine = null;
|
|
232
|
+
flashTimer = null;
|
|
233
|
+
}, 1500);
|
|
234
|
+
});
|
|
235
|
+
|
|
205
236
|
$effect(() => {
|
|
206
237
|
if (!containerEl) return;
|
|
207
238
|
containerHeight = containerEl.clientHeight;
|
|
@@ -218,13 +249,13 @@
|
|
|
218
249
|
</script>
|
|
219
250
|
|
|
220
251
|
<div
|
|
221
|
-
class="log-console relative flex h-full w-full flex-col overflow-hidden bg-
|
|
252
|
+
class="log-console relative flex h-full w-full flex-col overflow-hidden bg-mist-950 font-mono text-sm {className}"
|
|
222
253
|
>
|
|
223
254
|
<div bind:this={containerEl} class="flex-1 overflow-auto" onscroll={onScroll}>
|
|
224
255
|
<div class="relative" style="height: {totalHeight}px; min-height: 100%;">
|
|
225
256
|
{#if cache.firstAvailableLine > 0}
|
|
226
257
|
<div
|
|
227
|
-
class="absolute right-0 left-0 flex items-center bg-
|
|
258
|
+
class="absolute right-0 left-0 flex items-center bg-warning-700/20 text-warning-400"
|
|
228
259
|
style="top: 0px; height: {lineHeight}px;"
|
|
229
260
|
>
|
|
230
261
|
<div
|
|
@@ -242,21 +273,24 @@
|
|
|
242
273
|
|
|
243
274
|
{#each renderedLines as line (line.num)}
|
|
244
275
|
<div
|
|
245
|
-
class="log-line absolute right-0 left-0 flex items-center hover:bg-
|
|
276
|
+
class="log-line absolute right-0 left-0 flex items-center hover:bg-mist-900/50 {flashLine ===
|
|
277
|
+
line.num
|
|
278
|
+
? 'log-line--flash'
|
|
279
|
+
: ''}"
|
|
246
280
|
style="top: {lineTop(line.num)}px; height: {lineHeight}px;"
|
|
247
281
|
>
|
|
248
282
|
<div
|
|
249
|
-
class="flex-shrink-0 pr-3 text-right text-
|
|
283
|
+
class="flex-shrink-0 pr-3 text-right text-mist-500 select-none"
|
|
250
284
|
style="width: {gutterWidth}px;"
|
|
251
285
|
>
|
|
252
286
|
{line.num + 1}
|
|
253
287
|
</div>
|
|
254
|
-
<div class="flex-1 truncate pr-4 text-
|
|
288
|
+
<div class="flex-1 truncate pr-4 text-mist-200">
|
|
255
289
|
{#if line.text !== undefined}
|
|
256
290
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
257
291
|
<span class="whitespace-pre">{@html ansiLineToHtml(line.text)}</span>
|
|
258
292
|
{:else}
|
|
259
|
-
<span class="text-
|
|
293
|
+
<span class="text-mist-600 italic">Loading...</span>
|
|
260
294
|
{/if}
|
|
261
295
|
</div>
|
|
262
296
|
</div>
|
|
@@ -271,7 +305,7 @@
|
|
|
271
305
|
class="flex-shrink-0 pr-3 text-right select-none"
|
|
272
306
|
style="width: {gutterWidth}px;"
|
|
273
307
|
></div>
|
|
274
|
-
<div class="flex items-center gap-1 text-
|
|
308
|
+
<div class="flex items-center gap-1 text-mist-400">
|
|
275
309
|
<span class="dot" style="animation-delay: 0ms;"></span>
|
|
276
310
|
<span class="dot" style="animation-delay: 150ms;"></span>
|
|
277
311
|
<span class="dot" style="animation-delay: 300ms;"></span>
|
|
@@ -288,7 +322,7 @@
|
|
|
288
322
|
)}px; height: {lineHeight}px;"
|
|
289
323
|
>
|
|
290
324
|
<div
|
|
291
|
-
class="flex-shrink-0 pr-3 text-right text-
|
|
325
|
+
class="flex-shrink-0 pr-3 text-right text-mist-700 opacity-0 select-none"
|
|
292
326
|
style="width: {gutterWidth}px;"
|
|
293
327
|
>
|
|
294
328
|
~
|
|
@@ -298,16 +332,16 @@
|
|
|
298
332
|
{/if}
|
|
299
333
|
|
|
300
334
|
{#if cache.totalLines === 0 && !fetcher?.isFetching}
|
|
301
|
-
<div class="absolute inset-0 flex items-center justify-center text-
|
|
335
|
+
<div class="absolute inset-0 flex items-center justify-center text-mist-500">
|
|
302
336
|
<div class="text-center">
|
|
303
337
|
<div class="mb-1 text-lg">No output yet</div>
|
|
304
|
-
<div class="text-sm text-
|
|
338
|
+
<div class="text-sm text-mist-600">Waiting for logs...</div>
|
|
305
339
|
</div>
|
|
306
340
|
</div>
|
|
307
341
|
{/if}
|
|
308
342
|
|
|
309
343
|
{#if cache.totalLines === 0 && fetcher?.isFetching}
|
|
310
|
-
<div class="absolute inset-0 flex items-center justify-center text-
|
|
344
|
+
<div class="absolute inset-0 flex items-center justify-center text-mist-500">
|
|
311
345
|
<div class="flex items-center gap-2">
|
|
312
346
|
<span class="dot" style="animation-delay: 0ms;"></span>
|
|
313
347
|
<span class="dot" style="animation-delay: 150ms;"></span>
|
|
@@ -322,11 +356,11 @@
|
|
|
322
356
|
{#if userScrolledUp && cache.totalLines > 0}
|
|
323
357
|
<button
|
|
324
358
|
class="absolute right-4 bottom-12 z-10 flex items-center gap-2 rounded-full border
|
|
325
|
-
border-
|
|
359
|
+
border-mist-700 bg-mist-800
|
|
326
360
|
px-3 py-1.5 text-xs
|
|
327
|
-
font-medium text-
|
|
361
|
+
font-medium text-mist-200 shadow-lg
|
|
328
362
|
transition-all duration-200 hover:scale-105
|
|
329
|
-
hover:bg-
|
|
363
|
+
hover:bg-mist-700 active:scale-95"
|
|
330
364
|
onclick={enableAutoScroll}
|
|
331
365
|
>
|
|
332
366
|
<svg
|
|
@@ -343,20 +377,20 @@
|
|
|
343
377
|
{/if}
|
|
344
378
|
|
|
345
379
|
<div
|
|
346
|
-
class="flex flex-shrink-0 items-center justify-between border-t border-
|
|
347
|
-
bg-
|
|
380
|
+
class="flex flex-shrink-0 items-center justify-between border-t border-mist-800
|
|
381
|
+
bg-mist-900/80 px-3 py-1.5 text-xs text-mist-400"
|
|
348
382
|
>
|
|
349
383
|
<div class="flex items-center gap-3">
|
|
350
384
|
{#if isStreaming}
|
|
351
|
-
<div class="flex items-center gap-1.5 text-
|
|
385
|
+
<div class="flex items-center gap-1.5 text-info">
|
|
352
386
|
<div class="h-1.5 w-1.5 animate-pulse rounded-full bg-aurora-400"></div>
|
|
353
387
|
Streaming
|
|
354
388
|
</div>
|
|
355
389
|
{:else if cache.finished}
|
|
356
|
-
<span class="text-
|
|
390
|
+
<span class="text-mist-500">Stream ended</span>
|
|
357
391
|
{/if}
|
|
358
392
|
{#if fetcher?.isFetching}
|
|
359
|
-
<span class="text-
|
|
393
|
+
<span class="text-mist-500">Fetching...</span>
|
|
360
394
|
{/if}
|
|
361
395
|
</div>
|
|
362
396
|
<div class="flex items-center gap-4">
|
|
@@ -365,7 +399,7 @@
|
|
|
365
399
|
<span>{formatBytes(cache.totalBytes)}</span>
|
|
366
400
|
{/if}
|
|
367
401
|
{#if isAutoScroll}
|
|
368
|
-
<span class="text-
|
|
402
|
+
<span class="text-info">Auto-scroll</span>
|
|
369
403
|
{/if}
|
|
370
404
|
</div>
|
|
371
405
|
</div>
|
|
@@ -376,6 +410,19 @@
|
|
|
376
410
|
--log-line-height: 20px;
|
|
377
411
|
}
|
|
378
412
|
|
|
413
|
+
.log-line--flash {
|
|
414
|
+
animation: log-line-flash 1.5s ease-out;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
@keyframes log-line-flash {
|
|
418
|
+
0% {
|
|
419
|
+
background-color: oklch(0.8 0.18 70 / 0.45);
|
|
420
|
+
}
|
|
421
|
+
100% {
|
|
422
|
+
background-color: transparent;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
379
426
|
.dot {
|
|
380
427
|
display: inline-block;
|
|
381
428
|
width: 4px;
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
const percentage = $derived(Math.min(Math.max((value / max) * 100, 0), 100));
|
|
29
29
|
|
|
30
30
|
const variantClasses: Record<ProgressVariant, string> = {
|
|
31
|
-
default: "bg-
|
|
32
|
-
success: "bg-success-
|
|
33
|
-
warning: "bg-warning-
|
|
34
|
-
danger: "bg-danger-
|
|
31
|
+
default: "bg-primary",
|
|
32
|
+
success: "bg-success-surface",
|
|
33
|
+
warning: "bg-warning-surface",
|
|
34
|
+
danger: "bg-danger-surface",
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
const sizeClasses: Record<ProgressSize, string> = {
|
|
@@ -44,7 +44,9 @@
|
|
|
44
44
|
</div>
|
|
45
45
|
|
|
46
46
|
{#if error}
|
|
47
|
-
<p
|
|
47
|
+
<p
|
|
48
|
+
class="animate-in slide-in-from-top-1 fade-in text-sm text-danger-soft-text duration-200"
|
|
49
|
+
>
|
|
48
50
|
{error}
|
|
49
51
|
</p>
|
|
50
52
|
{:else if hint}
|
|
@@ -6,14 +6,19 @@
|
|
|
6
6
|
data: number[];
|
|
7
7
|
color?: string;
|
|
8
8
|
fillColor?: string;
|
|
9
|
+
fillOpacity?: number;
|
|
9
10
|
height?: number;
|
|
10
11
|
class?: string;
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
// `currentColor` by default so the caller can theme us with a Tailwind
|
|
15
|
+
// text-* class — that's the only way to flip on .dark without burning a
|
|
16
|
+
// hardcoded hex in here.
|
|
13
17
|
let {
|
|
14
18
|
data,
|
|
15
|
-
color = "
|
|
19
|
+
color = "currentColor",
|
|
16
20
|
fillColor,
|
|
21
|
+
fillOpacity = 0.12,
|
|
17
22
|
height = 40,
|
|
18
23
|
class: className = "",
|
|
19
24
|
}: Props = $props();
|
|
@@ -42,7 +47,10 @@
|
|
|
42
47
|
return `${pathD} L${lastX.toFixed(1)},${height} L0,${height} Z`;
|
|
43
48
|
});
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
// Use `fill-opacity` rather than appending "18" to a hex — that trick
|
|
51
|
+
// breaks for `currentColor` / `oklch()` / `var(...)` values.
|
|
52
|
+
let resolvedFill = $derived(fillColor ?? color);
|
|
53
|
+
let resolvedFillOpacity = $derived(fillColor === undefined ? fillOpacity : 1);
|
|
46
54
|
</script>
|
|
47
55
|
|
|
48
56
|
<svg
|
|
@@ -52,7 +60,7 @@
|
|
|
52
60
|
style="height: {height}px"
|
|
53
61
|
>
|
|
54
62
|
{#if data.length >= 2}
|
|
55
|
-
<path d={fillD} fill={resolvedFill} />
|
|
63
|
+
<path d={fillD} fill={resolvedFill} fill-opacity={resolvedFillOpacity} />
|
|
56
64
|
<path d={pathD} fill="none" stroke={color} stroke-width="1.5" stroke-linejoin="round" />
|
|
57
65
|
{/if}
|
|
58
66
|
</svg>
|
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
let { accent = "wisp", onclick, children, class: className = "" }: Props = $props();
|
|
17
17
|
|
|
18
18
|
const accentClasses: Record<TaskCardAccent, string> = {
|
|
19
|
-
danger: "hover:border-danger-
|
|
20
|
-
wisp: "hover:border-
|
|
21
|
-
aurora: "hover:border-
|
|
19
|
+
danger: "hover:border-danger-soft-border",
|
|
20
|
+
wisp: "hover:border-primary-soft-border",
|
|
21
|
+
aurora: "hover:border-info-soft-border",
|
|
22
22
|
};
|
|
23
23
|
</script>
|
|
24
24
|
|
|
25
25
|
<button
|
|
26
26
|
type="button"
|
|
27
|
-
class="group w-full rounded-lg border border-
|
|
27
|
+
class="group w-full rounded-lg border border-outline-faint bg-surface-raised p-3 text-left transition-all hover:shadow-sm {accentClasses[
|
|
28
28
|
accent
|
|
29
29
|
]} {className}"
|
|
30
30
|
{onclick}
|