@selvajs/ui 6.3.2 → 6.4.1
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/dist/components/compute/ComputeApp.svelte +32 -2
- package/dist/components/compute/ComputeApp.svelte.d.ts +7 -1
- package/dist/components/compute/ComputeMessagesDialog.svelte +118 -95
- package/dist/components/compute/ComputeMessagesDialog.svelte.d.ts +4 -0
- package/dist/components/compute/SolveMessages.svelte +367 -0
- package/dist/components/compute/SolveMessages.svelte.d.ts +28 -0
- package/dist/components/layout/AppShell.svelte +5 -1
- package/dist/components/layout/AppShell.svelte.d.ts +3 -0
- package/dist/components/layout/PageFooter.svelte +34 -14
- package/dist/components/layout/PageFooter.svelte.d.ts +3 -0
- package/dist/compute/useSolveSession.svelte.js +28 -4
- package/dist/i18n/messages.d.ts +9 -0
- package/dist/i18n/messages.js +18 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -1
- package/dist/public.d.ts +2 -2
- package/package.json +16 -16
- package/src/lib/components/compute/ComputeApp.svelte +32 -2
- package/src/lib/components/compute/ComputeMessagesDialog.svelte +118 -95
- package/src/lib/components/compute/SolveMessages.svelte +367 -0
- package/src/lib/components/layout/AppShell.svelte +5 -1
- package/src/lib/components/layout/PageFooter.svelte +34 -14
- package/src/lib/compute/useSolveSession.svelte.ts +28 -4
- package/src/lib/i18n/messages.ts +28 -0
- package/src/lib/index.ts +10 -2
- package/src/lib/public.ts +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './components/layout';
|
|
2
2
|
export { default as AppLayout } from './components/compute/AppLayout.svelte';
|
|
3
3
|
export { default as ComputeApp } from './components/compute/ComputeApp.svelte';
|
|
4
|
+
export { default as SolveMessages } from './components/compute/SolveMessages.svelte';
|
|
4
5
|
export { default as ErrorScreen } from './components/ErrorScreen.svelte';
|
|
5
6
|
export * from './components/primitives';
|
|
6
7
|
export { default as StateDisplay } from './components/primitives/StateDisplay.svelte';
|
|
@@ -10,7 +11,7 @@ export * from './schema/dynamic-value-list';
|
|
|
10
11
|
export * from './schema/traversal';
|
|
11
12
|
export * from './compute/solving.svelte';
|
|
12
13
|
export { useSolveSession } from './compute/useSolveSession.svelte';
|
|
13
|
-
export { createSolveSession, createRequestResponseDriver, type SolveSession, type SolveSessionArgs, type SolveDriver, type SolveReporter } from '@selvajs/solve/client';
|
|
14
|
+
export { createSolveSession, createRequestResponseDriver, type SolveSession, type SolveSessionArgs, type SolveDriver, type SolveReporter, type LiveSolveState, type SolvePhase, EMPTY_LIVE_SOLVE, decodeOutcome, finalizeResult, type SolveOutcome } from '@selvajs/solve/client';
|
|
14
15
|
export { writeExternalValue, readExternalValue, clearExternalValue, getExternalInputs, type ExternalValueRef, type ExternalInput } from '@selvajs/solve/client';
|
|
15
16
|
export * from './contexts/footerContext.svelte';
|
|
16
17
|
export * from './contexts/clientSlotContext.svelte';
|
|
@@ -18,5 +19,5 @@ export * from './composables/useFooterItem.svelte';
|
|
|
18
19
|
export * from './utils';
|
|
19
20
|
export { randomId } from './utils/randomId';
|
|
20
21
|
export type { ActionButton } from './types/actionButton';
|
|
21
|
-
export type { SolveFn, SolveResult } from '@selvajs/solve/shared';
|
|
22
|
+
export type { SolveFn, SolveResult, SolveDiagnostic } from '@selvajs/solve/shared';
|
|
22
23
|
export { DEFAULT_PRESET_LABELS, type PresetLabels } from './types/presetLabels';
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
export * from './components/layout';
|
|
5
5
|
export { default as AppLayout } from './components/compute/AppLayout.svelte';
|
|
6
6
|
export { default as ComputeApp } from './components/compute/ComputeApp.svelte';
|
|
7
|
+
export { default as SolveMessages } from './components/compute/SolveMessages.svelte';
|
|
7
8
|
export { default as ErrorScreen } from './components/ErrorScreen.svelte';
|
|
8
9
|
// Design-system primitives (shadcn-svelte + custom)
|
|
9
10
|
export * from './components/primitives';
|
|
@@ -17,7 +18,9 @@ export * from './compute/solving.svelte';
|
|
|
17
18
|
// plugin-ui's WebSocket driver — can satisfy SolveDriver and drive a session.
|
|
18
19
|
// See CONTEXT.md.
|
|
19
20
|
export { useSolveSession } from './compute/useSolveSession.svelte';
|
|
20
|
-
export { createSolveSession, createRequestResponseDriver
|
|
21
|
+
export { createSolveSession, createRequestResponseDriver,
|
|
22
|
+
// A push driver (plugin-ui's WebSocket) decodes the plugin's verdict with these.
|
|
23
|
+
EMPTY_LIVE_SOLVE, decodeOutcome, finalizeResult } from '@selvajs/solve/client';
|
|
21
24
|
// Pre-step producer transit storage
|
|
22
25
|
export { writeExternalValue, readExternalValue, clearExternalValue, getExternalInputs } from '@selvajs/solve/client';
|
|
23
26
|
export * from './contexts/footerContext.svelte';
|
package/dist/public.d.ts
CHANGED
|
@@ -8,12 +8,12 @@ export { VIEWER_MESSAGES, DEFAULT_LOCALE, messagesFor } from './i18n/messages';
|
|
|
8
8
|
export { setLocaleContext, getLocaleContext, type LocaleContext } from './i18n/localeContext.svelte';
|
|
9
9
|
export { default as ErrorScreen } from './components/ErrorScreen.svelte';
|
|
10
10
|
export { useSolveSession } from './compute/useSolveSession.svelte';
|
|
11
|
-
export { createSolveSession, createRequestResponseDriver, type SolveSession, type SolveSessionArgs, type SolveDriver, type SolveReporter } from '@selvajs/solve/client';
|
|
11
|
+
export { createSolveSession, createRequestResponseDriver, type SolveSession, type SolveSessionArgs, type SolveDriver, type SolveReporter, type LiveSolveState, type SolvePhase } from '@selvajs/solve/client';
|
|
12
12
|
export type { ClientSlotArgs, ClientSlot } from './contexts/clientSlotContext.svelte';
|
|
13
13
|
export { writeExternalValue, readExternalValue, clearExternalValue, getExternalInputs, type ExternalValueRef, type ExternalInput } from '@selvajs/solve/client';
|
|
14
14
|
export * from './schema/defaults';
|
|
15
15
|
export * from './schema/traversal';
|
|
16
16
|
export * from './schema/dynamic-value-list';
|
|
17
17
|
export type { ActionButton } from './types/actionButton';
|
|
18
|
-
export type { SolveFn, SolveResult } from '@selvajs/solve/shared';
|
|
18
|
+
export type { SolveFn, SolveResult, SolveDiagnostic } from '@selvajs/solve/shared';
|
|
19
19
|
export { DEFAULT_PRESET_LABELS, type PresetLabels } from './types/presetLabels';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selvajs/ui",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.1",
|
|
4
4
|
"description": "Shared UI components and utilities for Selva applications",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "VektorNode",
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
"bits-ui": "^2.18.0",
|
|
59
59
|
"svelte": "^5",
|
|
60
60
|
"tailwind-variants": "^3.3.1",
|
|
61
|
-
"three": "^0.186.
|
|
62
|
-
"@selvajs/
|
|
63
|
-
"@selvajs/
|
|
64
|
-
"@selvajs/
|
|
65
|
-
"@selvajs/
|
|
61
|
+
"three": "^0.186.1",
|
|
62
|
+
"@selvajs/schemas": "^5.1.1",
|
|
63
|
+
"@selvajs/visualization": "^1.4.1",
|
|
64
|
+
"@selvajs/solve": "^1.2.1",
|
|
65
|
+
"@selvajs/compute": "^4.2.1"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
|
68
68
|
"three": {
|
|
@@ -72,29 +72,29 @@
|
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@floating-ui/dom": "^1.8.0",
|
|
74
74
|
"@iconify/svelte": "^5.2.1",
|
|
75
|
-
"@lucide/svelte": "^1.
|
|
75
|
+
"@lucide/svelte": "^1.48.0",
|
|
76
76
|
"clsx": "^2.1.1",
|
|
77
77
|
"mode-watcher": "^1.1.0",
|
|
78
78
|
"paneforge": "^1.0.2",
|
|
79
79
|
"svelte-sonner": "^1.2.1",
|
|
80
|
-
"tailwind-merge": "^3.
|
|
80
|
+
"tailwind-merge": "^3.7.0",
|
|
81
81
|
"tw-animate-css": "^1.4.0"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
84
|
"@internationalized/date": "^3.12.4",
|
|
85
85
|
"@sveltejs/kit": "2.70.3",
|
|
86
|
-
"@sveltejs/vite-plugin-svelte": "^7.3.
|
|
86
|
+
"@sveltejs/vite-plugin-svelte": "^7.3.1",
|
|
87
87
|
"@types/three": "^0.186.0",
|
|
88
|
-
"bits-ui": "^2.19.
|
|
88
|
+
"bits-ui": "^2.19.3",
|
|
89
89
|
"rimraf": "^6.1.3",
|
|
90
|
-
"svelte": "5.57.
|
|
90
|
+
"svelte": "5.57.1",
|
|
91
91
|
"tailwind-variants": "^3.3.1",
|
|
92
|
-
"vitest": "^
|
|
92
|
+
"vitest": "^5.0.2",
|
|
93
|
+
"@selvajs/compute": "4.2.1",
|
|
94
|
+
"@selvajs/visualization": "1.4.1",
|
|
93
95
|
"@selvajs/config": "0.0.4",
|
|
94
|
-
"@selvajs/
|
|
95
|
-
"@selvajs/schemas": "5.
|
|
96
|
-
"@selvajs/compute": "4.1.3",
|
|
97
|
-
"@selvajs/solve": "1.1.1"
|
|
96
|
+
"@selvajs/solve": "1.2.1",
|
|
97
|
+
"@selvajs/schemas": "5.1.1"
|
|
98
98
|
},
|
|
99
99
|
"scripts": {
|
|
100
100
|
"predev": "node ../../scripts/sync-shared-assets.js",
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
import type { PresetLabels } from '../../types/presetLabels';
|
|
8
8
|
import { createSolvingIndicator } from '../../compute/solving.svelte';
|
|
9
9
|
import { createRequestResponseDriver } from '@selvajs/solve/client';
|
|
10
|
-
import type { RetainedSolveResult, SolveSession } from '@selvajs/solve/client';
|
|
10
|
+
import type { RetainedSolveResult, SolveEventSource, SolveSession } from '@selvajs/solve/client';
|
|
11
|
+
import SolveMessages from './SolveMessages.svelte';
|
|
11
12
|
import { meshPolicy } from '@selvajs/visualization/parse';
|
|
12
13
|
import type { ThreeViewer } from '@selvajs/visualization/render';
|
|
13
14
|
import { useSolveSession } from '../../compute/useSolveSession.svelte';
|
|
@@ -52,6 +53,12 @@
|
|
|
52
53
|
* have finished.
|
|
53
54
|
*/
|
|
54
55
|
solveDeadlineMs: number;
|
|
56
|
+
/**
|
|
57
|
+
* Live events for the solves this app runs (`createSolveEventStream`). Pair it with
|
|
58
|
+
* `streamId` on the `createComputeFetchSolveFn` that backs `onSolve`, or the server has
|
|
59
|
+
* no stream to route to. Without it the app works as before, just with no mid-solve news.
|
|
60
|
+
*/
|
|
61
|
+
events?: SolveEventSource;
|
|
55
62
|
footerComponent?: any;
|
|
56
63
|
footerComponentProps?: () => Record<string, unknown>;
|
|
57
64
|
footerItemId?: string;
|
|
@@ -105,6 +112,7 @@
|
|
|
105
112
|
copyrightName,
|
|
106
113
|
footerText,
|
|
107
114
|
solveDeadlineMs,
|
|
115
|
+
events,
|
|
108
116
|
footerComponent,
|
|
109
117
|
footerComponentProps,
|
|
110
118
|
footerItemId = 'footer-item',
|
|
@@ -146,7 +154,8 @@
|
|
|
146
154
|
meshPolicy,
|
|
147
155
|
// `isSolving` lives on the driver, which the session can't observe — republish so the
|
|
148
156
|
// spinner and disabled states track it.
|
|
149
|
-
onChange: () => session.notify()
|
|
157
|
+
onChange: () => session.notify(),
|
|
158
|
+
events
|
|
150
159
|
});
|
|
151
160
|
// svelte-ignore state_referenced_locally
|
|
152
161
|
const session = useSolveSession({
|
|
@@ -231,6 +240,7 @@
|
|
|
231
240
|
rightContent={headerRight}
|
|
232
241
|
errors={session.computeErrors}
|
|
233
242
|
warnings={session.computeWarnings}
|
|
243
|
+
diagnostics={session.diagnostics}
|
|
234
244
|
>
|
|
235
245
|
{#if session.error}
|
|
236
246
|
<div class="min-h-100 p-8 flex items-center justify-center">
|
|
@@ -268,3 +278,23 @@
|
|
|
268
278
|
{/if}
|
|
269
279
|
</AppShell>
|
|
270
280
|
</div>
|
|
281
|
+
|
|
282
|
+
<SolveMessages
|
|
283
|
+
live={session.live}
|
|
284
|
+
phase={session.phase}
|
|
285
|
+
diagnostics={session.diagnostics}
|
|
286
|
+
onabort={() => session.abort()}
|
|
287
|
+
onconfirm={() => session.acknowledge()}
|
|
288
|
+
ondiscard={() => session.discard()}
|
|
289
|
+
labels={{
|
|
290
|
+
solving: t.solveSolving,
|
|
291
|
+
blockedTitle: t.solveBlockedTitle,
|
|
292
|
+
messagesTitle: t.solveMessagesTitle,
|
|
293
|
+
blockedDescription: t.solveBlocked,
|
|
294
|
+
messagesDescription: t.solveMessagesDescription,
|
|
295
|
+
confirm: t.solveMessagesConfirm,
|
|
296
|
+
discard: t.solveMessagesDiscard,
|
|
297
|
+
abort: t.solveMessagesAbort,
|
|
298
|
+
dismiss: t.solveMessagesDismiss
|
|
299
|
+
}}
|
|
300
|
+
/>
|
|
@@ -1,26 +1,100 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
// The message centre: every message the last solve raised, reached from the footer badge.
|
|
3
|
+
//
|
|
4
|
+
// The counterpart to `SolveMessages`, which interrupts only for what the definition's author
|
|
5
|
+
// wrote. Everything else — Grasshopper complaining about its own components — lands here,
|
|
6
|
+
// where it is available without being in the way. One section per level, most severe first, with the
|
|
7
|
+
// component that raised each message shown as a label rather than spliced into its text.
|
|
8
|
+
|
|
2
9
|
import type { Snippet } from 'svelte';
|
|
3
|
-
import { CircleAlert, TriangleAlert,
|
|
4
|
-
import
|
|
10
|
+
import { CircleAlert, TriangleAlert, Info } from '@lucide/svelte';
|
|
11
|
+
import type { SolveDiagnostic } from '@selvajs/solve/shared';
|
|
5
12
|
import * as Dialog from '$lib/components/primitives/dialog';
|
|
6
|
-
import {
|
|
13
|
+
import { Badge } from '$lib/components/primitives/badge';
|
|
7
14
|
|
|
8
15
|
interface Props {
|
|
16
|
+
/** The last solve's messages. Preferred over `errors`/`warnings` when both are given. */
|
|
17
|
+
diagnostics?: SolveDiagnostic[];
|
|
18
|
+
/** Fallback for hosts that only have the flat text. */
|
|
9
19
|
errors?: string[];
|
|
10
20
|
warnings?: string[];
|
|
11
21
|
/** Renders inside Dialog.Trigger. */
|
|
12
22
|
trigger: Snippet;
|
|
13
23
|
}
|
|
14
24
|
|
|
15
|
-
let { errors = [], warnings = [], trigger }: Props = $props();
|
|
25
|
+
let { diagnostics, errors = [], warnings = [], trigger }: Props = $props();
|
|
16
26
|
|
|
17
27
|
let open = $state(false);
|
|
18
|
-
let showErrors = $state(true);
|
|
19
|
-
let showWarnings = $state(false);
|
|
20
28
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
const rows = $derived<SolveDiagnostic[]>(
|
|
30
|
+
diagnostics?.length
|
|
31
|
+
? diagnostics
|
|
32
|
+
: [
|
|
33
|
+
...errors.map((message) => ({ level: 'error' as const, message })),
|
|
34
|
+
...warnings.map((message) => ({ level: 'warning' as const, message }))
|
|
35
|
+
]
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
interface Item {
|
|
39
|
+
level: SolveDiagnostic['level'];
|
|
40
|
+
message: string;
|
|
41
|
+
source?: string;
|
|
42
|
+
count: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Tone classes are written out, never interpolated: Tailwind scans source text, so a
|
|
46
|
+
// constructed class name is not emitted and the icon would render colourless.
|
|
47
|
+
const LEVELS = [
|
|
48
|
+
{
|
|
49
|
+
level: 'error',
|
|
50
|
+
heading: 'Errors',
|
|
51
|
+
one: 'error',
|
|
52
|
+
many: 'errors',
|
|
53
|
+
icon: CircleAlert,
|
|
54
|
+
tone: 'text-destructive'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
level: 'warning',
|
|
58
|
+
heading: 'Warnings',
|
|
59
|
+
one: 'warning',
|
|
60
|
+
many: 'warnings',
|
|
61
|
+
icon: TriangleAlert,
|
|
62
|
+
tone: 'text-amber-500'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
level: 'remark',
|
|
66
|
+
heading: 'Notes',
|
|
67
|
+
one: 'note',
|
|
68
|
+
many: 'notes',
|
|
69
|
+
icon: Info,
|
|
70
|
+
tone: 'text-muted-foreground'
|
|
71
|
+
}
|
|
72
|
+
] as const;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* One section per level that has messages, most severe first; within a level, the order the
|
|
76
|
+
* solve raised them. Repeats collapse into one row with a count: a definition fanning over 200
|
|
77
|
+
* branches raises the same warning 200 times, and an unstacked list buries every other message.
|
|
78
|
+
*/
|
|
79
|
+
const sections = $derived(
|
|
80
|
+
LEVELS.map((meta) => {
|
|
81
|
+
// A null-prototype object rather than a Map: a local built fresh on each derive, never
|
|
82
|
+
// reactive state, and the lint rule that steers Map → SvelteMap cannot tell them apart.
|
|
83
|
+
const byKey: Record<string, Item> = Object.create(null);
|
|
84
|
+
const items: Item[] = [];
|
|
85
|
+
for (const d of rows) {
|
|
86
|
+
if (d.level !== meta.level) continue;
|
|
87
|
+
const key = `${d.message} ${d.source ?? ''}`;
|
|
88
|
+
if (byKey[key]) {
|
|
89
|
+
byKey[key].count++;
|
|
90
|
+
} else {
|
|
91
|
+
byKey[key] = { level: d.level, message: d.message, source: d.source, count: 1 };
|
|
92
|
+
items.push(byKey[key]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { ...meta, items };
|
|
96
|
+
}).filter((section) => section.items.length > 0)
|
|
97
|
+
);
|
|
24
98
|
</script>
|
|
25
99
|
|
|
26
100
|
<Dialog.Root bind:open>
|
|
@@ -28,98 +102,47 @@
|
|
|
28
102
|
{@render trigger()}
|
|
29
103
|
</Dialog.Trigger>
|
|
30
104
|
|
|
31
|
-
<Dialog.Content class="max-w-
|
|
105
|
+
<Dialog.Content class="max-w-lg gap-4">
|
|
32
106
|
<Dialog.Header>
|
|
33
|
-
<Dialog.Title
|
|
34
|
-
<CircleAlert class="h-5 w-5" />
|
|
35
|
-
Compute Messages
|
|
36
|
-
</Dialog.Title>
|
|
107
|
+
<Dialog.Title>Messages</Dialog.Title>
|
|
37
108
|
<Dialog.Description>
|
|
38
|
-
{
|
|
39
|
-
|
|
109
|
+
{#each sections as section, i (section.level)}
|
|
110
|
+
{#if i > 0}<span class="mx-1.5">·</span>{/if}
|
|
111
|
+
<span
|
|
112
|
+
>{section.items.length} {section.items.length === 1 ? section.one : section.many}</span
|
|
113
|
+
>
|
|
114
|
+
{/each}
|
|
40
115
|
</Dialog.Description>
|
|
41
116
|
</Dialog.Header>
|
|
42
117
|
|
|
43
|
-
<div class="space-y-
|
|
44
|
-
{#
|
|
45
|
-
<
|
|
46
|
-
<
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
118
|
+
<div class="space-y-4 max-h-[60vh] overflow-y-auto">
|
|
119
|
+
{#each sections as section (section.level)}
|
|
120
|
+
<section>
|
|
121
|
+
<h3 class="mb-1.5 gap-1.5 text-xs font-medium flex items-center text-muted-foreground">
|
|
122
|
+
<section.icon class="h-3.5 w-3.5 {section.tone}" />
|
|
123
|
+
{section.heading}
|
|
124
|
+
</h3>
|
|
125
|
+
<ul class="divide-y rounded-lg border">
|
|
126
|
+
{#each section.items as item (item.message + (item.source ?? ''))}
|
|
127
|
+
<li class="gap-3 px-3 py-2 text-sm flex items-start justify-between">
|
|
128
|
+
<p class="min-w-0 wrap-anywhere">
|
|
129
|
+
{item.message}
|
|
130
|
+
{#if item.count > 1}
|
|
131
|
+
<span class="ml-1 text-xs text-muted-foreground tabular-nums"
|
|
132
|
+
>×{item.count}</span
|
|
133
|
+
>
|
|
134
|
+
{/if}
|
|
135
|
+
</p>
|
|
136
|
+
{#if item.source}
|
|
137
|
+
<Badge variant="secondary" class="font-normal max-w-[40%] shrink-0 truncate">
|
|
138
|
+
{item.source}
|
|
139
|
+
</Badge>
|
|
55
140
|
{/if}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
</div>
|
|
62
|
-
|
|
63
|
-
<Collapsible.Content class="space-y-0">
|
|
64
|
-
<div class="max-h-60 px-4 py-3 overflow-y-auto border-t border-destructive bg-card">
|
|
65
|
-
<ul class="space-y-2">
|
|
66
|
-
{#each groupedErrors as { message, count } (message)}
|
|
67
|
-
<li class="gap-2 text-sm flex text-destructive/90">
|
|
68
|
-
<span class="shrink-0">•</span>
|
|
69
|
-
<span class="flex-1">
|
|
70
|
-
{message}
|
|
71
|
-
{#if count > 1}
|
|
72
|
-
<span class="ml-1 font-medium text-destructive/70">×{count} </span>
|
|
73
|
-
{/if}
|
|
74
|
-
</span>
|
|
75
|
-
</li>
|
|
76
|
-
{/each}
|
|
77
|
-
</ul>
|
|
78
|
-
</div>
|
|
79
|
-
</Collapsible.Content>
|
|
80
|
-
</div>
|
|
81
|
-
</Collapsible.Root>
|
|
82
|
-
{/if}
|
|
83
|
-
|
|
84
|
-
{#if warnings.length > 0}
|
|
85
|
-
<Collapsible.Root bind:open={showWarnings}>
|
|
86
|
-
<div class="overflow-hidden rounded-lg border border-warning/50 bg-card">
|
|
87
|
-
<div class="px-4 py-3 flex items-center">
|
|
88
|
-
<Collapsible.Trigger
|
|
89
|
-
class="-mx-4 -my-3 gap-3 px-4 py-3 flex flex-1 items-center text-left transition-colors hover:bg-warning/5"
|
|
90
|
-
>
|
|
91
|
-
{#if showWarnings}
|
|
92
|
-
<ChevronDown class="h-4 w-4 shrink-0 text-warning" />
|
|
93
|
-
{:else}
|
|
94
|
-
<ChevronRight class="h-4 w-4 shrink-0 text-warning" />
|
|
95
|
-
{/if}
|
|
96
|
-
<TriangleAlert class="h-4 w-4 shrink-0 text-warning" />
|
|
97
|
-
<span class="text-sm font-medium text-warning">
|
|
98
|
-
{warnings.length === 1 ? '1 Warning' : `${warnings.length} Warnings`}
|
|
99
|
-
</span>
|
|
100
|
-
</Collapsible.Trigger>
|
|
101
|
-
</div>
|
|
102
|
-
|
|
103
|
-
<Collapsible.Content class="space-y-0">
|
|
104
|
-
<div class="max-h-60 px-4 py-3 overflow-y-auto border-t border-warning/50 bg-card">
|
|
105
|
-
<ul class="space-y-2">
|
|
106
|
-
{#each groupedWarnings as { message, count } (message)}
|
|
107
|
-
<li class="gap-2 text-sm flex text-muted-foreground">
|
|
108
|
-
<span class="shrink-0">•</span>
|
|
109
|
-
<span class="flex-1">
|
|
110
|
-
{message}
|
|
111
|
-
{#if count > 1}
|
|
112
|
-
<span class="ml-1 font-medium text-warning/70">×{count}</span>
|
|
113
|
-
{/if}
|
|
114
|
-
</span>
|
|
115
|
-
</li>
|
|
116
|
-
{/each}
|
|
117
|
-
</ul>
|
|
118
|
-
</div>
|
|
119
|
-
</Collapsible.Content>
|
|
120
|
-
</div>
|
|
121
|
-
</Collapsible.Root>
|
|
122
|
-
{/if}
|
|
141
|
+
</li>
|
|
142
|
+
{/each}
|
|
143
|
+
</ul>
|
|
144
|
+
</section>
|
|
145
|
+
{/each}
|
|
123
146
|
</div>
|
|
124
147
|
</Dialog.Content>
|
|
125
148
|
</Dialog.Root>
|