@selvajs/ui 4.3.0 → 4.5.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/dist/components/compute/AppLayout.svelte +3 -3
- package/dist/components/compute/AppLayout.svelte.d.ts +2 -2
- package/dist/components/compute/ComputeApp.svelte +47 -140
- package/dist/components/compute/ComputeApp.svelte.d.ts +0 -16
- package/dist/components/compute/ComputeMessagesDialog.svelte +125 -0
- package/dist/components/compute/ComputeMessagesDialog.svelte.d.ts +10 -0
- package/dist/components/compute/ParameterPresetManager.svelte +25 -33
- package/dist/components/layout/PageFooter.svelte +22 -144
- package/dist/components/preview/Group.svelte +6 -3
- package/dist/components/preview/ImageOutput.svelte +1 -13
- package/dist/components/preview/OutputDisplay.svelte +6 -6
- package/dist/components/preview/TabLayout.svelte +6 -5
- package/dist/components/preview/inputs/FileInput.svelte +10 -19
- package/dist/composables/useFooterItem.svelte.d.ts +10 -12
- package/dist/composables/useFooterItem.svelte.js +9 -16
- package/dist/compute/computeThrottle.svelte.js +5 -2
- package/dist/compute/createSolveSession.svelte.d.ts +56 -0
- package/dist/compute/createSolveSession.svelte.js +122 -0
- package/dist/compute/solve-session-core.d.ts +45 -0
- package/dist/compute/solve-session-core.js +69 -0
- package/dist/constants.d.ts +0 -2
- package/dist/constants.js +0 -2
- package/dist/contexts/footerContext.svelte.d.ts +17 -5
- package/dist/contexts/footerContext.svelte.js +10 -2
- package/dist/external/storage.js +7 -23
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -0
- package/dist/schema/param-exporter.d.ts +25 -1
- package/dist/schema/param-exporter.js +30 -27
- package/dist/schema/traversal.d.ts +1 -0
- package/dist/schema/traversal.js +5 -0
- package/dist/schema/visibility-rules.d.ts +12 -0
- package/dist/schema/visibility-rules.js +20 -0
- package/dist/types/solveFn.d.ts +1 -2
- package/dist/utils/file-download.d.ts +5 -17
- package/dist/utils/file-download.js +12 -19
- package/dist/utils/randomId.js +3 -19
- package/package.json +10 -26
- package/src/lib/components/compute/AppLayout.svelte +3 -3
- package/src/lib/components/compute/ComputeApp.svelte +47 -140
- package/src/lib/components/compute/ComputeMessagesDialog.svelte +125 -0
- package/src/lib/components/compute/ParameterPresetManager.svelte +25 -33
- package/src/lib/components/layout/PageFooter.svelte +22 -144
- package/src/lib/components/preview/Group.svelte +6 -3
- package/src/lib/components/preview/ImageOutput.svelte +1 -13
- package/src/lib/components/preview/OutputDisplay.svelte +6 -6
- package/src/lib/components/preview/TabLayout.svelte +6 -5
- package/src/lib/components/preview/inputs/FileInput.svelte +10 -19
- package/src/lib/composables/useFooterItem.svelte.ts +25 -25
- package/src/lib/compute/computeThrottle.svelte.ts +5 -2
- package/src/lib/compute/computeThrottle.test.ts +106 -0
- package/src/lib/compute/createSolveSession.svelte.ts +188 -0
- package/src/lib/compute/solve-session-core.test.ts +153 -0
- package/src/lib/compute/solve-session-core.ts +102 -0
- package/src/lib/constants.ts +0 -2
- package/src/lib/contexts/footerContext.svelte.ts +29 -14
- package/src/lib/external/storage.test.ts +99 -0
- package/src/lib/external/storage.ts +6 -22
- package/src/lib/index.ts +14 -1
- package/src/lib/schema/param-exporter.test.ts +136 -0
- package/src/lib/schema/param-exporter.ts +44 -30
- package/src/lib/schema/traversal.test.ts +92 -0
- package/src/lib/schema/traversal.ts +5 -0
- package/src/lib/schema/visibility-rules.test.ts +173 -0
- package/src/lib/schema/visibility-rules.ts +25 -0
- package/src/lib/types/solveFn.ts +1 -1
- package/src/lib/utils/file-download.ts +13 -20
- package/src/lib/utils/randomId.ts +3 -19
- package/dist/components/compute/ComputeMessages.svelte +0 -169
- package/dist/components/compute/ComputeMessages.svelte.d.ts +0 -7
- package/dist/components/compute/index.d.ts +0 -5
- package/dist/components/compute/index.js +0 -5
- package/dist/components/preview/index.d.ts +0 -3
- package/dist/components/preview/index.js +0 -4
- package/dist/components/viewer/index.d.ts +0 -3
- package/dist/components/viewer/index.js +0 -3
- package/src/lib/components/compute/ComputeMessages.svelte +0 -169
- package/src/lib/components/compute/index.ts +0 -5
- package/src/lib/components/preview/index.ts +0 -4
- package/src/lib/components/viewer/index.ts +0 -3
|
@@ -84,6 +84,31 @@ export function evaluateVisibility(
|
|
|
84
84
|
return actionFn(met, defaultValue);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Stable key for a layout item: `paramId` for inputs/outputs, `id` for linebreaks.
|
|
89
|
+
* Mirrors the key the renderers use in their `{#each}` blocks.
|
|
90
|
+
*/
|
|
91
|
+
export function itemKey(item: LayoutItem): string {
|
|
92
|
+
return item.type === 'linebreak' ? item.id : item.paramId;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Evaluates every item's visibility once against the current values, keyed by itemKey.
|
|
97
|
+
* Callers that touch the same items more than once per render (column layout + cell
|
|
98
|
+
* render, plus the default-value sweep) read from this single map instead of
|
|
99
|
+
* re-evaluating per access — one source of truth for "what's visible right now".
|
|
100
|
+
*/
|
|
101
|
+
export function buildVisibilityMap(
|
|
102
|
+
items: LayoutItem[],
|
|
103
|
+
values: Record<string, unknown>
|
|
104
|
+
): Record<string, VisibilityResult> {
|
|
105
|
+
const map: Record<string, VisibilityResult> = {};
|
|
106
|
+
for (const item of items) {
|
|
107
|
+
map[itemKey(item)] = evaluateVisibility(item, values);
|
|
108
|
+
}
|
|
109
|
+
return map;
|
|
110
|
+
}
|
|
111
|
+
|
|
87
112
|
export function evaluateGroupVisibility(
|
|
88
113
|
group: { visibilityCondition?: GroupVisibilityCondition },
|
|
89
114
|
values: Record<string, unknown>
|
package/src/lib/types/solveFn.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @property errors - Optional array of error messages that occurred
|
|
7
7
|
* @property warnings - Optional array of warning messages from the computation
|
|
8
8
|
*/
|
|
9
|
-
interface SolveResult {
|
|
9
|
+
export interface SolveResult {
|
|
10
10
|
outputs: Record<string, unknown>;
|
|
11
11
|
meshes?: any[];
|
|
12
12
|
errors?: string[];
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wrapper utilities for file downloads from Grasshopper outputs
|
|
3
|
-
* Uses the core package implementation for file handling
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import { downloadFileData, type FileData } from '@selvajs/compute';
|
|
2
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
7
3
|
import { APP_DEFAULTS } from '../constants';
|
|
8
4
|
|
|
9
|
-
const MIME_BY_EXT: Record<string, string> = {
|
|
5
|
+
export const MIME_BY_EXT: Record<string, string> = {
|
|
10
6
|
'.png': 'image/png',
|
|
11
7
|
'.jpg': 'image/jpeg',
|
|
12
8
|
'.jpeg': 'image/jpeg',
|
|
@@ -45,10 +41,6 @@ function saveSingleFile(file: FileData): void {
|
|
|
45
41
|
URL.revokeObjectURL(a.href);
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
/**
|
|
49
|
-
* Download file(s) from Grasshopper outputs
|
|
50
|
-
* Single files are downloaded directly, multiple files are packaged as ZIP
|
|
51
|
-
*/
|
|
52
44
|
export async function downloadFiles(
|
|
53
45
|
fileData: FileData | FileData[],
|
|
54
46
|
fileName: string = 'grasshopper-output'
|
|
@@ -73,9 +65,6 @@ export async function downloadFiles(
|
|
|
73
65
|
}
|
|
74
66
|
}
|
|
75
67
|
|
|
76
|
-
/**
|
|
77
|
-
* Check if data is FileData
|
|
78
|
-
*/
|
|
79
68
|
export function isFileData(data: unknown): data is FileData {
|
|
80
69
|
return (
|
|
81
70
|
typeof data === 'object' &&
|
|
@@ -86,9 +75,17 @@ export function isFileData(data: unknown): data is FileData {
|
|
|
86
75
|
);
|
|
87
76
|
}
|
|
88
77
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
78
|
+
export function groupMessages(messages: string[]): Array<{ message: string; count: number }> {
|
|
79
|
+
const grouped = new SvelteMap<string, number>();
|
|
80
|
+
for (const msg of messages) {
|
|
81
|
+
const baseMsg = msg.replace(/\([a-f0-9-]{36}\)$/i, '(...)').trim();
|
|
82
|
+
grouped.set(baseMsg, (grouped.get(baseMsg) ?? 0) + 1);
|
|
83
|
+
}
|
|
84
|
+
return Array.from(grouped.entries())
|
|
85
|
+
.map(([message, count]) => ({ message, count }))
|
|
86
|
+
.sort((a, b) => b.count - a.count);
|
|
87
|
+
}
|
|
88
|
+
|
|
92
89
|
export function formatFileSize(bytes: number): string {
|
|
93
90
|
if (bytes === 0) return '0 Bytes';
|
|
94
91
|
|
|
@@ -99,10 +96,6 @@ export function formatFileSize(bytes: number): string {
|
|
|
99
96
|
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
100
97
|
}
|
|
101
98
|
|
|
102
|
-
/**
|
|
103
|
-
* Get approximate file size from base64 string
|
|
104
|
-
*/
|
|
105
99
|
export function getBase64FileSize(base64: string): number {
|
|
106
|
-
// Base64 encoded string is approximately 33% larger than original
|
|
107
100
|
return Math.ceil((base64.length * 3) / 4);
|
|
108
101
|
}
|
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// Plain-HTTP deployments — common for first-time self-hosters trying things
|
|
5
|
-
// out — get `crypto.randomUUID is not a function`, which is a confusing
|
|
6
|
-
// browser-level error nobody can diagnose without devtools open.
|
|
7
|
-
//
|
|
8
|
-
// We try in order:
|
|
9
|
-
// 1. crypto.randomUUID() — fastest path, available in secure contexts
|
|
10
|
-
// 2. crypto.getRandomValues() — Web Crypto's lower-level entropy, works
|
|
11
|
-
// in any context that ships Web Crypto
|
|
12
|
-
// 3. Math.random() fallback — last resort; non-cryptographic, but the
|
|
13
|
-
// IDs we generate here are only used as
|
|
14
|
-
// collision-resistant keys for client-side
|
|
15
|
-
// form rows, not as security tokens.
|
|
16
|
-
//
|
|
17
|
-
// Callers that need cryptographic randomness (tokens, secrets) must NOT use
|
|
18
|
-
// this — they should fail loudly in insecure contexts instead of silently
|
|
19
|
-
// downgrading.
|
|
1
|
+
// Falls back through crypto.randomUUID → crypto.getRandomValues → Math.random so
|
|
2
|
+
// plain-HTTP deployments don't crash. Math.random IDs are non-cryptographic —
|
|
3
|
+
// fine for collision-resistant UI keys, not for tokens or secrets.
|
|
20
4
|
export function randomId(): string {
|
|
21
5
|
const c = typeof crypto !== 'undefined' ? crypto : undefined;
|
|
22
6
|
if (c?.randomUUID) return c.randomUUID();
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { CircleAlert, TriangleAlert, ChevronDown, ChevronRight } from '@lucide/svelte';
|
|
3
|
-
import { SvelteMap } from 'svelte/reactivity';
|
|
4
|
-
import * as Collapsible from '../primitives/collapsible';
|
|
5
|
-
import * as Dialog from '../primitives/dialog';
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
errors?: string[];
|
|
9
|
-
warnings?: string[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
let { errors = [], warnings = [] }: Props = $props();
|
|
13
|
-
|
|
14
|
-
let open = $state(false);
|
|
15
|
-
|
|
16
|
-
let showErrors = $state(true);
|
|
17
|
-
let showWarnings = $state(false);
|
|
18
|
-
|
|
19
|
-
function groupMessages(messages: string[]): Array<{ message: string; count: number }> {
|
|
20
|
-
const grouped = new SvelteMap<string, number>();
|
|
21
|
-
|
|
22
|
-
for (const msg of messages) {
|
|
23
|
-
// Extract the base message without the component GUID
|
|
24
|
-
const baseMsg = msg.replace(/\([a-f0-9-]{36}\)$/i, '(...)').trim();
|
|
25
|
-
const currentCount = grouped.get(baseMsg) || 0;
|
|
26
|
-
grouped.set(baseMsg, currentCount + 1);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return Array.from(grouped.entries())
|
|
30
|
-
.map(([message, count]) => ({ message, count }))
|
|
31
|
-
.sort((a, b) => b.count - a.count);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const groupedErrors = $derived(groupMessages(errors));
|
|
35
|
-
const groupedWarnings = $derived(groupMessages(warnings));
|
|
36
|
-
const hasMessages = $derived(errors.length > 0 || warnings.length > 0);
|
|
37
|
-
const totalCount = $derived(errors.length + warnings.length);
|
|
38
|
-
</script>
|
|
39
|
-
|
|
40
|
-
{#if hasMessages}
|
|
41
|
-
<Dialog.Root bind:open>
|
|
42
|
-
<Dialog.Trigger
|
|
43
|
-
class="bottom-12 right-2 sm:bottom-4 sm:right-4 sm:block fixed z-100 hidden focus:outline-none"
|
|
44
|
-
>
|
|
45
|
-
<div
|
|
46
|
-
class="shadow-lg text-xs font-medium hover:shadow-xl hover:-translate-y-0.5 active:translate-y-0 flex cursor-pointer items-stretch overflow-hidden rounded-full border transition-all duration-150
|
|
47
|
-
{errors.length > 0
|
|
48
|
-
? 'text-destructive-foreground border-destructive/30 bg-destructive'
|
|
49
|
-
: 'border-warning/30 bg-warning/5 text-warning-foreground'}"
|
|
50
|
-
>
|
|
51
|
-
{#if errors.length > 0}
|
|
52
|
-
<span class="gap-1.5 px-3 py-1.5 flex items-center">
|
|
53
|
-
<CircleAlert class="h-3.5 w-3.5 shrink-0" />
|
|
54
|
-
{errors.length}
|
|
55
|
-
</span>
|
|
56
|
-
{/if}
|
|
57
|
-
{#if warnings.length > 0}
|
|
58
|
-
{#if errors.length > 0}
|
|
59
|
-
<span class="bg-destructive-foreground/20 w-px self-stretch"></span>
|
|
60
|
-
<span class="gap-1.5 px-3 py-1.5 flex items-center bg-destructive/80">
|
|
61
|
-
<TriangleAlert class="h-3.5 w-3.5 shrink-0" />
|
|
62
|
-
{warnings.length}
|
|
63
|
-
</span>
|
|
64
|
-
{:else}
|
|
65
|
-
<span class="gap-1.5 px-3 py-1.5 flex items-center">
|
|
66
|
-
<TriangleAlert class="h-3.5 w-3.5 shrink-0" />
|
|
67
|
-
{warnings.length}
|
|
68
|
-
</span>
|
|
69
|
-
{/if}
|
|
70
|
-
{/if}
|
|
71
|
-
</div>
|
|
72
|
-
</Dialog.Trigger>
|
|
73
|
-
|
|
74
|
-
<Dialog.Content class="max-w-2xl max-h-[80vh]">
|
|
75
|
-
<Dialog.Header>
|
|
76
|
-
<Dialog.Title class="gap-2 flex items-center">
|
|
77
|
-
<CircleAlert class="h-5 w-5" />
|
|
78
|
-
Compute Messages
|
|
79
|
-
</Dialog.Title>
|
|
80
|
-
<Dialog.Description>
|
|
81
|
-
{totalCount}
|
|
82
|
-
{totalCount === 1 ? 'issue' : 'issues'} detected during solve
|
|
83
|
-
</Dialog.Description>
|
|
84
|
-
</Dialog.Header>
|
|
85
|
-
|
|
86
|
-
<div class="space-y-3 pr-2 overflow-y-auto" style="max-height: calc(80vh - 180px);">
|
|
87
|
-
{#if errors.length > 0}
|
|
88
|
-
<Collapsible.Root bind:open={showErrors}>
|
|
89
|
-
<div class="overflow-hidden rounded-lg border border-destructive bg-card">
|
|
90
|
-
<div class="px-4 py-3 flex items-center">
|
|
91
|
-
<Collapsible.Trigger
|
|
92
|
-
class="-mx-4 -my-3 gap-3 px-4 py-3 flex flex-1 items-center text-left transition-colors hover:bg-destructive/5"
|
|
93
|
-
>
|
|
94
|
-
{#if showErrors}
|
|
95
|
-
<ChevronDown class="h-4 w-4 shrink-0 text-destructive" />
|
|
96
|
-
{:else}
|
|
97
|
-
<ChevronRight class="h-4 w-4 shrink-0 text-destructive" />
|
|
98
|
-
{/if}
|
|
99
|
-
<CircleAlert class="h-4 w-4 shrink-0 text-destructive" />
|
|
100
|
-
<span class="text-sm font-medium text-destructive">
|
|
101
|
-
{errors.length === 1 ? '1 Error' : `${errors.length} Errors`}
|
|
102
|
-
</span>
|
|
103
|
-
</Collapsible.Trigger>
|
|
104
|
-
</div>
|
|
105
|
-
|
|
106
|
-
<Collapsible.Content class="space-y-0">
|
|
107
|
-
<div class="max-h-60 px-4 py-3 overflow-y-auto border-t border-destructive bg-card">
|
|
108
|
-
<ul class="space-y-2">
|
|
109
|
-
{#each groupedErrors as { message, count } (message)}
|
|
110
|
-
<li class="gap-2 text-sm flex text-destructive/90">
|
|
111
|
-
<span class="shrink-0">•</span>
|
|
112
|
-
<span class="flex-1">
|
|
113
|
-
{message}
|
|
114
|
-
{#if count > 1}
|
|
115
|
-
<span class="ml-1 font-medium text-destructive/70">×{count} </span>
|
|
116
|
-
{/if}
|
|
117
|
-
</span>
|
|
118
|
-
</li>
|
|
119
|
-
{/each}
|
|
120
|
-
</ul>
|
|
121
|
-
</div>
|
|
122
|
-
</Collapsible.Content>
|
|
123
|
-
</div>
|
|
124
|
-
</Collapsible.Root>
|
|
125
|
-
{/if}
|
|
126
|
-
|
|
127
|
-
{#if warnings.length > 0}
|
|
128
|
-
<Collapsible.Root bind:open={showWarnings}>
|
|
129
|
-
<div class="overflow-hidden rounded-lg border border-warning/50 bg-card">
|
|
130
|
-
<div class="px-4 py-3 flex items-center">
|
|
131
|
-
<Collapsible.Trigger
|
|
132
|
-
class="-mx-4 -my-3 gap-3 px-4 py-3 flex flex-1 items-center text-left transition-colors hover:bg-warning/5"
|
|
133
|
-
>
|
|
134
|
-
{#if showWarnings}
|
|
135
|
-
<ChevronDown class="h-4 w-4 shrink-0 text-warning" />
|
|
136
|
-
{:else}
|
|
137
|
-
<ChevronRight class="h-4 w-4 shrink-0 text-warning" />
|
|
138
|
-
{/if}
|
|
139
|
-
<TriangleAlert class="h-4 w-4 shrink-0 text-warning" />
|
|
140
|
-
<span class="text-sm font-medium text-warning">
|
|
141
|
-
{warnings.length === 1 ? '1 Warning' : `${warnings.length} Warnings`}
|
|
142
|
-
</span>
|
|
143
|
-
</Collapsible.Trigger>
|
|
144
|
-
</div>
|
|
145
|
-
|
|
146
|
-
<Collapsible.Content class="space-y-0">
|
|
147
|
-
<div class="max-h-60 px-4 py-3 overflow-y-auto border-t border-warning/50 bg-card">
|
|
148
|
-
<ul class="space-y-2">
|
|
149
|
-
{#each groupedWarnings as { message, count } (message)}
|
|
150
|
-
<li class="gap-2 text-sm flex text-muted-foreground">
|
|
151
|
-
<span class="shrink-0">•</span>
|
|
152
|
-
<span class="flex-1">
|
|
153
|
-
{message}
|
|
154
|
-
{#if count > 1}
|
|
155
|
-
<span class="ml-1 font-medium text-warning/70">×{count}</span>
|
|
156
|
-
{/if}
|
|
157
|
-
</span>
|
|
158
|
-
</li>
|
|
159
|
-
{/each}
|
|
160
|
-
</ul>
|
|
161
|
-
</div>
|
|
162
|
-
</Collapsible.Content>
|
|
163
|
-
</div>
|
|
164
|
-
</Collapsible.Root>
|
|
165
|
-
{/if}
|
|
166
|
-
</div>
|
|
167
|
-
</Dialog.Content>
|
|
168
|
-
</Dialog.Root>
|
|
169
|
-
{/if}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { default as AppLayout } from './AppLayout.svelte';
|
|
2
|
-
export { default as ComputeApp } from './ComputeApp.svelte';
|
|
3
|
-
export { default as ComputeMessages } from './ComputeMessages.svelte';
|
|
4
|
-
export { default as ParameterPresetManager } from './ParameterPresetManager.svelte';
|
|
5
|
-
export { default as CollapsedPanelStrip } from './CollapsedPanelStrip.svelte';
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { default as AppLayout } from './AppLayout.svelte';
|
|
2
|
-
export { default as ComputeApp } from './ComputeApp.svelte';
|
|
3
|
-
export { default as ComputeMessages } from './ComputeMessages.svelte';
|
|
4
|
-
export { default as ParameterPresetManager } from './ParameterPresetManager.svelte';
|
|
5
|
-
export { default as CollapsedPanelStrip } from './CollapsedPanelStrip.svelte';
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { CircleAlert, TriangleAlert, ChevronDown, ChevronRight } from '@lucide/svelte';
|
|
3
|
-
import { SvelteMap } from 'svelte/reactivity';
|
|
4
|
-
import * as Collapsible from '$lib/components/primitives/collapsible';
|
|
5
|
-
import * as Dialog from '$lib/components/primitives/dialog';
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
errors?: string[];
|
|
9
|
-
warnings?: string[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
let { errors = [], warnings = [] }: Props = $props();
|
|
13
|
-
|
|
14
|
-
let open = $state(false);
|
|
15
|
-
|
|
16
|
-
let showErrors = $state(true);
|
|
17
|
-
let showWarnings = $state(false);
|
|
18
|
-
|
|
19
|
-
function groupMessages(messages: string[]): Array<{ message: string; count: number }> {
|
|
20
|
-
const grouped = new SvelteMap<string, number>();
|
|
21
|
-
|
|
22
|
-
for (const msg of messages) {
|
|
23
|
-
// Extract the base message without the component GUID
|
|
24
|
-
const baseMsg = msg.replace(/\([a-f0-9-]{36}\)$/i, '(...)').trim();
|
|
25
|
-
const currentCount = grouped.get(baseMsg) || 0;
|
|
26
|
-
grouped.set(baseMsg, currentCount + 1);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return Array.from(grouped.entries())
|
|
30
|
-
.map(([message, count]) => ({ message, count }))
|
|
31
|
-
.sort((a, b) => b.count - a.count);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const groupedErrors = $derived(groupMessages(errors));
|
|
35
|
-
const groupedWarnings = $derived(groupMessages(warnings));
|
|
36
|
-
const hasMessages = $derived(errors.length > 0 || warnings.length > 0);
|
|
37
|
-
const totalCount = $derived(errors.length + warnings.length);
|
|
38
|
-
</script>
|
|
39
|
-
|
|
40
|
-
{#if hasMessages}
|
|
41
|
-
<Dialog.Root bind:open>
|
|
42
|
-
<Dialog.Trigger
|
|
43
|
-
class="bottom-12 right-2 sm:bottom-4 sm:right-4 sm:block fixed z-100 hidden focus:outline-none"
|
|
44
|
-
>
|
|
45
|
-
<div
|
|
46
|
-
class="shadow-lg text-xs font-medium hover:shadow-xl hover:-translate-y-0.5 active:translate-y-0 flex cursor-pointer items-stretch overflow-hidden rounded-full border transition-all duration-150
|
|
47
|
-
{errors.length > 0
|
|
48
|
-
? 'text-destructive-foreground border-destructive/30 bg-destructive'
|
|
49
|
-
: 'border-warning/30 bg-warning/5 text-warning-foreground'}"
|
|
50
|
-
>
|
|
51
|
-
{#if errors.length > 0}
|
|
52
|
-
<span class="gap-1.5 px-3 py-1.5 flex items-center">
|
|
53
|
-
<CircleAlert class="h-3.5 w-3.5 shrink-0" />
|
|
54
|
-
{errors.length}
|
|
55
|
-
</span>
|
|
56
|
-
{/if}
|
|
57
|
-
{#if warnings.length > 0}
|
|
58
|
-
{#if errors.length > 0}
|
|
59
|
-
<span class="bg-destructive-foreground/20 w-px self-stretch"></span>
|
|
60
|
-
<span class="gap-1.5 px-3 py-1.5 flex items-center bg-destructive/80">
|
|
61
|
-
<TriangleAlert class="h-3.5 w-3.5 shrink-0" />
|
|
62
|
-
{warnings.length}
|
|
63
|
-
</span>
|
|
64
|
-
{:else}
|
|
65
|
-
<span class="gap-1.5 px-3 py-1.5 flex items-center">
|
|
66
|
-
<TriangleAlert class="h-3.5 w-3.5 shrink-0" />
|
|
67
|
-
{warnings.length}
|
|
68
|
-
</span>
|
|
69
|
-
{/if}
|
|
70
|
-
{/if}
|
|
71
|
-
</div>
|
|
72
|
-
</Dialog.Trigger>
|
|
73
|
-
|
|
74
|
-
<Dialog.Content class="max-w-2xl max-h-[80vh]">
|
|
75
|
-
<Dialog.Header>
|
|
76
|
-
<Dialog.Title class="gap-2 flex items-center">
|
|
77
|
-
<CircleAlert class="h-5 w-5" />
|
|
78
|
-
Compute Messages
|
|
79
|
-
</Dialog.Title>
|
|
80
|
-
<Dialog.Description>
|
|
81
|
-
{totalCount}
|
|
82
|
-
{totalCount === 1 ? 'issue' : 'issues'} detected during solve
|
|
83
|
-
</Dialog.Description>
|
|
84
|
-
</Dialog.Header>
|
|
85
|
-
|
|
86
|
-
<div class="space-y-3 pr-2 overflow-y-auto" style="max-height: calc(80vh - 180px);">
|
|
87
|
-
{#if errors.length > 0}
|
|
88
|
-
<Collapsible.Root bind:open={showErrors}>
|
|
89
|
-
<div class="overflow-hidden rounded-lg border border-destructive bg-card">
|
|
90
|
-
<div class="px-4 py-3 flex items-center">
|
|
91
|
-
<Collapsible.Trigger
|
|
92
|
-
class="-mx-4 -my-3 gap-3 px-4 py-3 flex flex-1 items-center text-left transition-colors hover:bg-destructive/5"
|
|
93
|
-
>
|
|
94
|
-
{#if showErrors}
|
|
95
|
-
<ChevronDown class="h-4 w-4 shrink-0 text-destructive" />
|
|
96
|
-
{:else}
|
|
97
|
-
<ChevronRight class="h-4 w-4 shrink-0 text-destructive" />
|
|
98
|
-
{/if}
|
|
99
|
-
<CircleAlert class="h-4 w-4 shrink-0 text-destructive" />
|
|
100
|
-
<span class="text-sm font-medium text-destructive">
|
|
101
|
-
{errors.length === 1 ? '1 Error' : `${errors.length} Errors`}
|
|
102
|
-
</span>
|
|
103
|
-
</Collapsible.Trigger>
|
|
104
|
-
</div>
|
|
105
|
-
|
|
106
|
-
<Collapsible.Content class="space-y-0">
|
|
107
|
-
<div class="max-h-60 px-4 py-3 overflow-y-auto border-t border-destructive bg-card">
|
|
108
|
-
<ul class="space-y-2">
|
|
109
|
-
{#each groupedErrors as { message, count } (message)}
|
|
110
|
-
<li class="gap-2 text-sm flex text-destructive/90">
|
|
111
|
-
<span class="shrink-0">•</span>
|
|
112
|
-
<span class="flex-1">
|
|
113
|
-
{message}
|
|
114
|
-
{#if count > 1}
|
|
115
|
-
<span class="ml-1 font-medium text-destructive/70">×{count} </span>
|
|
116
|
-
{/if}
|
|
117
|
-
</span>
|
|
118
|
-
</li>
|
|
119
|
-
{/each}
|
|
120
|
-
</ul>
|
|
121
|
-
</div>
|
|
122
|
-
</Collapsible.Content>
|
|
123
|
-
</div>
|
|
124
|
-
</Collapsible.Root>
|
|
125
|
-
{/if}
|
|
126
|
-
|
|
127
|
-
{#if warnings.length > 0}
|
|
128
|
-
<Collapsible.Root bind:open={showWarnings}>
|
|
129
|
-
<div class="overflow-hidden rounded-lg border border-warning/50 bg-card">
|
|
130
|
-
<div class="px-4 py-3 flex items-center">
|
|
131
|
-
<Collapsible.Trigger
|
|
132
|
-
class="-mx-4 -my-3 gap-3 px-4 py-3 flex flex-1 items-center text-left transition-colors hover:bg-warning/5"
|
|
133
|
-
>
|
|
134
|
-
{#if showWarnings}
|
|
135
|
-
<ChevronDown class="h-4 w-4 shrink-0 text-warning" />
|
|
136
|
-
{:else}
|
|
137
|
-
<ChevronRight class="h-4 w-4 shrink-0 text-warning" />
|
|
138
|
-
{/if}
|
|
139
|
-
<TriangleAlert class="h-4 w-4 shrink-0 text-warning" />
|
|
140
|
-
<span class="text-sm font-medium text-warning">
|
|
141
|
-
{warnings.length === 1 ? '1 Warning' : `${warnings.length} Warnings`}
|
|
142
|
-
</span>
|
|
143
|
-
</Collapsible.Trigger>
|
|
144
|
-
</div>
|
|
145
|
-
|
|
146
|
-
<Collapsible.Content class="space-y-0">
|
|
147
|
-
<div class="max-h-60 px-4 py-3 overflow-y-auto border-t border-warning/50 bg-card">
|
|
148
|
-
<ul class="space-y-2">
|
|
149
|
-
{#each groupedWarnings as { message, count } (message)}
|
|
150
|
-
<li class="gap-2 text-sm flex text-muted-foreground">
|
|
151
|
-
<span class="shrink-0">•</span>
|
|
152
|
-
<span class="flex-1">
|
|
153
|
-
{message}
|
|
154
|
-
{#if count > 1}
|
|
155
|
-
<span class="ml-1 font-medium text-warning/70">×{count}</span>
|
|
156
|
-
{/if}
|
|
157
|
-
</span>
|
|
158
|
-
</li>
|
|
159
|
-
{/each}
|
|
160
|
-
</ul>
|
|
161
|
-
</div>
|
|
162
|
-
</Collapsible.Content>
|
|
163
|
-
</div>
|
|
164
|
-
</Collapsible.Root>
|
|
165
|
-
{/if}
|
|
166
|
-
</div>
|
|
167
|
-
</Dialog.Content>
|
|
168
|
-
</Dialog.Root>
|
|
169
|
-
{/if}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { default as AppLayout } from './AppLayout.svelte';
|
|
2
|
-
export { default as ComputeApp } from './ComputeApp.svelte';
|
|
3
|
-
export { default as ComputeMessages } from './ComputeMessages.svelte';
|
|
4
|
-
export { default as ParameterPresetManager } from './ParameterPresetManager.svelte';
|
|
5
|
-
export { default as CollapsedPanelStrip } from './CollapsedPanelStrip.svelte';
|