@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.
Files changed (80) hide show
  1. package/dist/components/compute/AppLayout.svelte +3 -3
  2. package/dist/components/compute/AppLayout.svelte.d.ts +2 -2
  3. package/dist/components/compute/ComputeApp.svelte +47 -140
  4. package/dist/components/compute/ComputeApp.svelte.d.ts +0 -16
  5. package/dist/components/compute/ComputeMessagesDialog.svelte +125 -0
  6. package/dist/components/compute/ComputeMessagesDialog.svelte.d.ts +10 -0
  7. package/dist/components/compute/ParameterPresetManager.svelte +25 -33
  8. package/dist/components/layout/PageFooter.svelte +22 -144
  9. package/dist/components/preview/Group.svelte +6 -3
  10. package/dist/components/preview/ImageOutput.svelte +1 -13
  11. package/dist/components/preview/OutputDisplay.svelte +6 -6
  12. package/dist/components/preview/TabLayout.svelte +6 -5
  13. package/dist/components/preview/inputs/FileInput.svelte +10 -19
  14. package/dist/composables/useFooterItem.svelte.d.ts +10 -12
  15. package/dist/composables/useFooterItem.svelte.js +9 -16
  16. package/dist/compute/computeThrottle.svelte.js +5 -2
  17. package/dist/compute/createSolveSession.svelte.d.ts +56 -0
  18. package/dist/compute/createSolveSession.svelte.js +122 -0
  19. package/dist/compute/solve-session-core.d.ts +45 -0
  20. package/dist/compute/solve-session-core.js +69 -0
  21. package/dist/constants.d.ts +0 -2
  22. package/dist/constants.js +0 -2
  23. package/dist/contexts/footerContext.svelte.d.ts +17 -5
  24. package/dist/contexts/footerContext.svelte.js +10 -2
  25. package/dist/external/storage.js +7 -23
  26. package/dist/index.d.ts +3 -1
  27. package/dist/index.js +5 -0
  28. package/dist/schema/param-exporter.d.ts +25 -1
  29. package/dist/schema/param-exporter.js +30 -27
  30. package/dist/schema/traversal.d.ts +1 -0
  31. package/dist/schema/traversal.js +5 -0
  32. package/dist/schema/visibility-rules.d.ts +12 -0
  33. package/dist/schema/visibility-rules.js +20 -0
  34. package/dist/types/solveFn.d.ts +1 -2
  35. package/dist/utils/file-download.d.ts +5 -17
  36. package/dist/utils/file-download.js +12 -19
  37. package/dist/utils/randomId.js +3 -19
  38. package/package.json +10 -26
  39. package/src/lib/components/compute/AppLayout.svelte +3 -3
  40. package/src/lib/components/compute/ComputeApp.svelte +47 -140
  41. package/src/lib/components/compute/ComputeMessagesDialog.svelte +125 -0
  42. package/src/lib/components/compute/ParameterPresetManager.svelte +25 -33
  43. package/src/lib/components/layout/PageFooter.svelte +22 -144
  44. package/src/lib/components/preview/Group.svelte +6 -3
  45. package/src/lib/components/preview/ImageOutput.svelte +1 -13
  46. package/src/lib/components/preview/OutputDisplay.svelte +6 -6
  47. package/src/lib/components/preview/TabLayout.svelte +6 -5
  48. package/src/lib/components/preview/inputs/FileInput.svelte +10 -19
  49. package/src/lib/composables/useFooterItem.svelte.ts +25 -25
  50. package/src/lib/compute/computeThrottle.svelte.ts +5 -2
  51. package/src/lib/compute/computeThrottle.test.ts +106 -0
  52. package/src/lib/compute/createSolveSession.svelte.ts +188 -0
  53. package/src/lib/compute/solve-session-core.test.ts +153 -0
  54. package/src/lib/compute/solve-session-core.ts +102 -0
  55. package/src/lib/constants.ts +0 -2
  56. package/src/lib/contexts/footerContext.svelte.ts +29 -14
  57. package/src/lib/external/storage.test.ts +99 -0
  58. package/src/lib/external/storage.ts +6 -22
  59. package/src/lib/index.ts +14 -1
  60. package/src/lib/schema/param-exporter.test.ts +136 -0
  61. package/src/lib/schema/param-exporter.ts +44 -30
  62. package/src/lib/schema/traversal.test.ts +92 -0
  63. package/src/lib/schema/traversal.ts +5 -0
  64. package/src/lib/schema/visibility-rules.test.ts +173 -0
  65. package/src/lib/schema/visibility-rules.ts +25 -0
  66. package/src/lib/types/solveFn.ts +1 -1
  67. package/src/lib/utils/file-download.ts +13 -20
  68. package/src/lib/utils/randomId.ts +3 -19
  69. package/dist/components/compute/ComputeMessages.svelte +0 -169
  70. package/dist/components/compute/ComputeMessages.svelte.d.ts +0 -7
  71. package/dist/components/compute/index.d.ts +0 -5
  72. package/dist/components/compute/index.js +0 -5
  73. package/dist/components/preview/index.d.ts +0 -3
  74. package/dist/components/preview/index.js +0 -4
  75. package/dist/components/viewer/index.d.ts +0 -3
  76. package/dist/components/viewer/index.js +0 -3
  77. package/src/lib/components/compute/ComputeMessages.svelte +0 -169
  78. package/src/lib/components/compute/index.ts +0 -5
  79. package/src/lib/components/preview/index.ts +0 -4
  80. 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>
@@ -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
- * Format file size for display
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
- // Generate a UUIDv4-shaped string in any browser context.
2
- //
3
- // `crypto.randomUUID()` requires a secure context (HTTPS or localhost).
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,7 +0,0 @@
1
- interface Props {
2
- errors?: string[];
3
- warnings?: string[];
4
- }
5
- declare const ComputeMessages: import("svelte").Component<Props, {}, "">;
6
- type ComputeMessages = ReturnType<typeof ComputeMessages>;
7
- export default ComputeMessages;
@@ -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,3 +0,0 @@
1
- export { default as InputControl } from './InputControl.svelte';
2
- export { default as OutputDisplay } from './OutputDisplay.svelte';
3
- export { default as TabLayout } from './TabLayout.svelte';
@@ -1,4 +0,0 @@
1
- // Preview/Runtime Components - Interactive UI rendering
2
- export { default as InputControl } from './InputControl.svelte';
3
- export { default as OutputDisplay } from './OutputDisplay.svelte';
4
- export { default as TabLayout } from './TabLayout.svelte';
@@ -1,3 +0,0 @@
1
- export { default as Viewer, type ViewerConfig } from './Viewer.svelte';
2
- export { default as MeshMetadataDialog } from './MeshMetadataDialog.svelte';
3
- export { default as SceneManager } from './SceneManager.svelte';
@@ -1,3 +0,0 @@
1
- export { default as Viewer } from './Viewer.svelte';
2
- export { default as MeshMetadataDialog } from './MeshMetadataDialog.svelte';
3
- export { default as SceneManager } from './SceneManager.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';
@@ -1,4 +0,0 @@
1
- // Preview/Runtime Components - Interactive UI rendering
2
- export { default as InputControl } from './InputControl.svelte';
3
- export { default as OutputDisplay } from './OutputDisplay.svelte';
4
- export { default as TabLayout } from './TabLayout.svelte';
@@ -1,3 +0,0 @@
1
- export { default as Viewer, type ViewerConfig } from './Viewer.svelte';
2
- export { default as MeshMetadataDialog } from './MeshMetadataDialog.svelte';
3
- export { default as SceneManager } from './SceneManager.svelte';