@selvajs/ui 6.0.0-beta.8 → 6.1.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/README.md +5 -7
- package/dist/components/compute/AppLayout.svelte +3 -8
- package/dist/components/compute/AppLayout.svelte.d.ts +0 -2
- package/dist/components/compute/ComputeApp.svelte +30 -43
- package/dist/components/compute/ComputeApp.svelte.d.ts +11 -24
- package/dist/components/compute/ParameterPresetManager.svelte +4 -9
- package/dist/components/compute/ParameterPresetManager.svelte.d.ts +1 -1
- package/dist/components/layout/AppShell.svelte +5 -13
- package/dist/components/layout/AppShell.svelte.d.ts +1 -2
- package/dist/components/layout/EmptyState.svelte +2 -3
- package/dist/components/layout/PageFooter.svelte +0 -4
- package/dist/components/layout/index.js +0 -1
- package/dist/components/preview/Group.svelte +2 -5
- package/dist/components/preview/ImageOutput.svelte +5 -4
- package/dist/components/preview/InputControl.svelte +27 -28
- package/dist/components/preview/InputControl.svelte.d.ts +3 -3
- package/dist/components/preview/OutputDisplay.svelte +3 -6
- package/dist/components/preview/TabLayout.svelte +3 -3
- package/dist/components/preview/inputs/FileInput.svelte +9 -12
- package/dist/components/primitives/field/field-error.svelte +1 -3
- package/dist/components/primitives/index.js +0 -1
- package/dist/components/viewer/SceneManager.svelte +9 -20
- package/dist/components/viewer/SceneManager.svelte.d.ts +3 -3
- package/dist/components/viewer/Viewer.svelte +42 -63
- package/dist/components/viewer/Viewer.svelte.d.ts +8 -19
- package/dist/composables/useFooterItem.svelte.d.ts +3 -4
- package/dist/composables/useFooterItem.svelte.js +3 -3
- package/dist/compute/solving.svelte.d.ts +6 -6
- package/dist/compute/solving.svelte.js +6 -6
- package/dist/compute/useSolveSession.svelte.d.ts +5 -7
- package/dist/compute/useSolveSession.svelte.js +11 -24
- package/dist/constants.js +4 -4
- package/dist/contexts/clientSlotContext.svelte.d.ts +3 -5
- package/dist/contexts/footerContext.svelte.d.ts +6 -8
- package/dist/external/storage.js +2 -3
- package/dist/i18n/localeContext.svelte.d.ts +5 -7
- package/dist/i18n/localeContext.svelte.js +8 -15
- package/dist/i18n/messages.d.ts +0 -2
- package/dist/i18n/messages.js +3 -8
- package/dist/index.js +7 -13
- package/dist/public.js +14 -30
- package/dist/schema/defaults.js +2 -3
- package/dist/schema/dynamic-value-list.js +13 -15
- package/dist/schema/param-exporter.d.ts +7 -14
- package/dist/schema/param-exporter.js +6 -12
- package/dist/schema/traversal.js +3 -4
- package/dist/schema/visibility-rules.d.ts +4 -8
- package/dist/schema/visibility-rules.js +4 -8
- package/dist/types/actionButton.d.ts +1 -25
- package/dist/types/presetLabels.d.ts +2 -3
- package/dist/utils/color.js +6 -9
- package/dist/utils/debounce.d.ts +1 -11
- package/dist/utils/debounce.js +1 -11
- package/dist/utils/loadScript.d.ts +2 -7
- package/dist/utils/loadScript.js +2 -7
- package/package.json +17 -17
- package/src/lib/components/compute/AppLayout.svelte +3 -8
- package/src/lib/components/compute/ComputeApp.svelte +30 -43
- package/src/lib/components/compute/ParameterPresetManager.svelte +4 -9
- package/src/lib/components/layout/AppShell.svelte +5 -13
- package/src/lib/components/layout/EmptyState.svelte +2 -3
- package/src/lib/components/layout/PageFooter.svelte +0 -4
- package/src/lib/components/layout/index.ts +0 -1
- package/src/lib/components/preview/Group.svelte +2 -5
- package/src/lib/components/preview/ImageOutput.svelte +5 -4
- package/src/lib/components/preview/InputControl.svelte +27 -28
- package/src/lib/components/preview/OutputDisplay.svelte +3 -6
- package/src/lib/components/preview/TabLayout.svelte +3 -3
- package/src/lib/components/preview/inputs/FileInput.svelte +9 -12
- package/src/lib/components/primitives/field/field-error.svelte +1 -3
- package/src/lib/components/primitives/index.ts +0 -2
- package/src/lib/components/viewer/SceneManager.svelte +9 -20
- package/src/lib/components/viewer/Viewer.svelte +42 -63
- package/src/lib/composables/useFooterItem.svelte.ts +3 -4
- package/src/lib/compute/solving.svelte.ts +6 -6
- package/src/lib/compute/useSolveSession.svelte.ts +11 -24
- package/src/lib/constants.ts +4 -4
- package/src/lib/contexts/clientSlotContext.svelte.ts +7 -12
- package/src/lib/contexts/footerContext.svelte.ts +6 -8
- package/src/lib/external/storage.ts +2 -3
- package/src/lib/i18n/localeContext.svelte.ts +8 -17
- package/src/lib/i18n/messages.ts +3 -10
- package/src/lib/index.ts +8 -13
- package/src/lib/public.ts +22 -40
- package/src/lib/schema/defaults.ts +2 -3
- package/src/lib/schema/dynamic-value-list.ts +13 -15
- package/src/lib/schema/param-exporter.ts +9 -16
- package/src/lib/schema/traversal.ts +3 -4
- package/src/lib/schema/visibility-rules.ts +4 -8
- package/src/lib/types/actionButton.ts +1 -25
- package/src/lib/types/presetLabels.ts +2 -3
- package/src/lib/utils/color.ts +6 -9
- package/src/lib/utils/debounce.ts +1 -11
- package/src/lib/utils/loadScript.ts +2 -7
|
@@ -46,15 +46,12 @@
|
|
|
46
46
|
showFullscreenButton?: boolean;
|
|
47
47
|
showSceneManager?: boolean;
|
|
48
48
|
showToolsMenu?: boolean;
|
|
49
|
-
/** Expose the grid show/hide toggle in the tools menu. Grid starts hidden. */
|
|
50
49
|
showGridToggle?: boolean;
|
|
51
|
-
/**
|
|
52
|
-
* Expose the "Display" submenu (render style picker + edges toggle) in the tools menu.
|
|
53
|
-
* Defaults on. Starts on the 'technical' style with edges hidden.
|
|
54
|
-
*/
|
|
55
50
|
showDisplayMenu?: boolean;
|
|
56
51
|
enableMeshClick?: boolean;
|
|
57
52
|
backgroundColor?: string;
|
|
53
|
+
/** Initial state of the edge overlay and its display-menu checkmark. Default false. */
|
|
54
|
+
showEdges?: boolean;
|
|
58
55
|
}
|
|
59
56
|
|
|
60
57
|
interface Props {
|
|
@@ -64,26 +61,18 @@
|
|
|
64
61
|
isBlurred?: boolean;
|
|
65
62
|
drawerOpen?: boolean;
|
|
66
63
|
viewerConfig?: ViewerConfig;
|
|
67
|
-
/**
|
|
68
|
-
* Branding logo URL. When set, shown as a small watermark in the viewer's
|
|
69
|
-
* bottom-right corner. Omitted/empty renders nothing.
|
|
70
|
-
*/
|
|
64
|
+
/** Shown as a watermark in the bottom-right corner. */
|
|
71
65
|
logoUrl?: string;
|
|
72
66
|
/**
|
|
73
67
|
* Hands the live three.js viewer to the host once the canvas is up, for apps drawing their
|
|
74
|
-
* own content
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* Return a cleanup function to tear down what you added; it runs before the viewer disposes.
|
|
78
|
-
* Anything added outside a solve needs `viewer.invalidate()` to repaint — the render loop is
|
|
79
|
-
* on-demand.
|
|
68
|
+
* own content. Return a cleanup function to tear down what you added; it runs before the
|
|
69
|
+
* viewer disposes. Anything added outside a solve needs `viewer.invalidate()` to repaint —
|
|
70
|
+
* the render loop is on-demand.
|
|
80
71
|
*/
|
|
81
72
|
onViewerReady?: (viewer: ThreeViewer) => void | (() => void);
|
|
82
73
|
/**
|
|
83
|
-
* UI language for the viewer's own chrome
|
|
84
|
-
*
|
|
85
|
-
* reads the nearest locale context (set by the host app), defaulting to
|
|
86
|
-
* English. Does not translate Grasshopper-sourced names/metadata.
|
|
74
|
+
* UI language for the viewer's own chrome. Omitted, the viewer reads the nearest locale
|
|
75
|
+
* context. Does not translate Grasshopper-sourced names/metadata.
|
|
87
76
|
*/
|
|
88
77
|
lang?: Locale;
|
|
89
78
|
}
|
|
@@ -96,7 +85,8 @@
|
|
|
96
85
|
showGridToggle: true,
|
|
97
86
|
showDisplayMenu: true,
|
|
98
87
|
enableMeshClick: true,
|
|
99
|
-
backgroundColor: '#E6E6E6'
|
|
88
|
+
backgroundColor: '#E6E6E6',
|
|
89
|
+
showEdges: false
|
|
100
90
|
};
|
|
101
91
|
|
|
102
92
|
let {
|
|
@@ -113,13 +103,10 @@
|
|
|
113
103
|
|
|
114
104
|
const config = $derived({ ...defaultViewerConfig, ...viewerConfig });
|
|
115
105
|
|
|
116
|
-
// Read
|
|
106
|
+
// Read the host's locale before overriding it for our subtree.
|
|
117
107
|
const hostLocale = getLocaleContext();
|
|
118
108
|
|
|
119
|
-
//
|
|
120
|
-
// Provide the resolved value to our subtree so the scene manager and metadata
|
|
121
|
-
// dialog read the same locale. The getter is re-read reactively, so switching
|
|
122
|
-
// the `lang` prop (or the host's locale) updates the chrome live.
|
|
109
|
+
// A getter, not a value: it is re-read reactively, so changing `lang` updates the chrome live.
|
|
123
110
|
setLocaleContext(() => lang ?? hostLocale.locale);
|
|
124
111
|
const locale = getLocaleContext();
|
|
125
112
|
const t = $derived(locale.messages);
|
|
@@ -142,10 +129,8 @@
|
|
|
142
129
|
let hideButton = $state(false);
|
|
143
130
|
let sceneManagerOpen = $state(false);
|
|
144
131
|
|
|
145
|
-
// The
|
|
146
|
-
//
|
|
147
|
-
// closed — and must be re-hidden after each solve, which nothing would do if the state
|
|
148
|
-
// unmounted with the panel.
|
|
132
|
+
// The outliner lives here, not in <SceneManager>: that component mounts only while its panel is
|
|
133
|
+
// open, and hidden objects must stay hidden — and be re-hidden after each solve — while it is closed.
|
|
149
134
|
const hiddenObjects = new SvelteSet<string>();
|
|
150
135
|
const selectedObjects = new SvelteSet<string>();
|
|
151
136
|
const collapsedLayers = new SvelteSet<string>();
|
|
@@ -153,15 +138,13 @@
|
|
|
153
138
|
let projection: CameraProjection = $state('perspective');
|
|
154
139
|
let measureActive = $state(false);
|
|
155
140
|
let gridVisible = $state(false);
|
|
156
|
-
// Render style + edge overlays. 'technical' is the default look; edges (crease lines) start off —
|
|
157
|
-
// both are user-switchable via the Display submenu.
|
|
158
141
|
let renderStyle: Look = $state('technical');
|
|
159
|
-
|
|
142
|
+
// Seeded once, then owned by the menu toggle.
|
|
143
|
+
let edgesVisible = $state(untrack(() => viewerConfig.showEdges) ?? defaultViewerConfig.showEdges);
|
|
160
144
|
let selectedMeshMetadata: Record<string, any> | null = $state(null);
|
|
161
145
|
let selectedMeshName: string | null = $state(null);
|
|
162
146
|
|
|
163
|
-
//
|
|
164
|
-
// renaming a look in @selvajs/compute updates this automatically. Label is the value capitalized.
|
|
147
|
+
// Derived from LOOKS so adding a look in @selvajs/visualization shows up here with no edit.
|
|
165
148
|
const STYLE_OPTIONS: { look: Look; label: string }[] = LOOKS.map((look) => ({
|
|
166
149
|
look,
|
|
167
150
|
label: look.charAt(0).toUpperCase() + look.slice(1)
|
|
@@ -192,16 +175,13 @@
|
|
|
192
175
|
onMount(() => {
|
|
193
176
|
if (!canvas) return;
|
|
194
177
|
|
|
195
|
-
// Only
|
|
196
|
-
//
|
|
197
|
-
// switch off the sun/shadows the technical look doesn't need — flat ambient + HDR image-based
|
|
198
|
-
// lighting (baseHDR loads by default) carry it. Grid/measure/click are the tools this viewer uses.
|
|
178
|
+
// Only what differs from the library defaults. The sun and shadows are off because the
|
|
179
|
+
// technical look doesn't need them — flat ambient plus the HDR environment carry it.
|
|
199
180
|
const opts: ThreeInitializerOptions = {
|
|
200
181
|
look: renderStyle,
|
|
201
182
|
lighting: { enableSunlight: false },
|
|
202
183
|
render: { enableShadows: false },
|
|
203
184
|
environment: { backgroundColor: config.backgroundColor },
|
|
204
|
-
// Build the grid so it can be toggled at runtime, but start hidden (off by default).
|
|
205
185
|
grid: { enabled: config.showToolsMenu && config.showGridToggle },
|
|
206
186
|
measure: { enabled: config.showToolsMenu },
|
|
207
187
|
events: {
|
|
@@ -237,8 +217,7 @@
|
|
|
237
217
|
|
|
238
218
|
const renderer = init.renderer;
|
|
239
219
|
|
|
240
|
-
// Untracked
|
|
241
|
-
// inside it must not re-run onMount's teardown.
|
|
220
|
+
// Untracked so the host reading `meshes` or config inside its setup can't re-run onMount's teardown.
|
|
242
221
|
const hostCleanup = untrack(() => onViewerReady?.(init));
|
|
243
222
|
|
|
244
223
|
return () => {
|
|
@@ -280,9 +259,8 @@
|
|
|
280
259
|
setLook(look);
|
|
281
260
|
}
|
|
282
261
|
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
// it also cancels in-flight attaches and stands down the screen-space fallback for capped meshes.
|
|
262
|
+
// `applyEdges` is idempotent per mesh, so the repeated calls after each solve add no duplicate
|
|
263
|
+
// overlays; `clearEdges` is its inverse.
|
|
286
264
|
function applyEdgeState() {
|
|
287
265
|
if (!scene) return;
|
|
288
266
|
if (edgesVisible) applyEdges?.(scene);
|
|
@@ -297,18 +275,18 @@
|
|
|
297
275
|
$effect(() => {
|
|
298
276
|
if (scene && camera && controls) {
|
|
299
277
|
updateScene(scene, meshes, camera, controls, viewerInitialized);
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
// toggling edges is handled directly by toggleEdges(), so it must not re-trigger a full solve.
|
|
278
|
+
// Untracked because toggleEdges() already handles the toggle directly — reading
|
|
279
|
+
// `edgesVisible` tracked here would re-trigger a full solve.
|
|
303
280
|
untrack(() => {
|
|
281
|
+
// updateScene discarded the previous solve's overlays along with its content.
|
|
304
282
|
if (edgesVisible) applyEdges?.(scene!);
|
|
305
|
-
// Rescale the grid
|
|
283
|
+
// Rescale the grid so cells and fade match the new content's extent.
|
|
306
284
|
updateGridScale?.();
|
|
307
|
-
// The rebuild
|
|
308
|
-
//
|
|
285
|
+
// The rebuild un-hid everything; the outliner keys hidden state on Grasshopper
|
|
286
|
+
// identity, not on the instances just discarded, so it can re-hide it.
|
|
309
287
|
outliner?.applyTo();
|
|
310
288
|
sceneVersion++;
|
|
311
|
-
//
|
|
289
|
+
// Repaint now rather than on the render loop's safety interval.
|
|
312
290
|
invalidate?.();
|
|
313
291
|
});
|
|
314
292
|
|
|
@@ -373,11 +351,19 @@
|
|
|
373
351
|
<!-- `defaultSize` must sum to 100 across the live panes. Panes register a frame before the
|
|
374
352
|
group recomputes its layout, so a sum of 115 renders one frame at the raw flex-grow ratio
|
|
375
353
|
and is then renormalized — and if the recompute short-circuits on an equal layout, the
|
|
376
|
-
scene pane keeps a sliver of its intended width. Hence 85 + 15, and the explicit
|
|
377
|
-
|
|
354
|
+
scene pane keeps a sliver of its intended width. Hence 85 + 15, and the explicit id/order
|
|
355
|
+
so a conditionally-rendered pane keeps its slot. -->
|
|
378
356
|
<Resizable.Pane id="viewport" order={1} defaultSize={sceneManagerOpen ? 85 : 100} minSize={40}>
|
|
379
357
|
<div class="relative h-full w-full" style="touch-action: none;">
|
|
380
|
-
|
|
358
|
+
<!-- Mesh count of the geometry currently in the scene. The canvas itself is opaque to
|
|
359
|
+
the DOM, so this is the only observable proof a solve's geometry decoded and
|
|
360
|
+
rendered; e2e asserts on it. -->
|
|
361
|
+
<canvas
|
|
362
|
+
class="block h-full w-full"
|
|
363
|
+
data-testid="viewer-canvas"
|
|
364
|
+
data-mesh-count={meshes.length}
|
|
365
|
+
bind:this={canvas}
|
|
366
|
+
></canvas>
|
|
381
367
|
|
|
382
368
|
<div
|
|
383
369
|
class="inset-0 blur-overlay absolute z-20 {isBlurred
|
|
@@ -392,9 +378,8 @@
|
|
|
392
378
|
{/if}
|
|
393
379
|
|
|
394
380
|
{#if logoUrl}
|
|
395
|
-
<!--
|
|
396
|
-
|
|
397
|
-
non-interactive so it never intercepts canvas drags. -->
|
|
381
|
+
<!-- Bottom offset matches the tools menu so it clears the mobile drawer handle.
|
|
382
|
+
Always pointer-events:none so it never intercepts canvas drags. -->
|
|
398
383
|
<div
|
|
399
384
|
class="right-4 {isFullscreen ? 'bottom-4' : 'bottom-16 sm:bottom-4'} absolute z-20"
|
|
400
385
|
style={hideButton
|
|
@@ -434,7 +419,6 @@
|
|
|
434
419
|
align="start"
|
|
435
420
|
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 min-w-44 p-1 shadow-md z-10001 rounded-md border bg-popover text-popover-foreground"
|
|
436
421
|
>
|
|
437
|
-
<!-- Camera -->
|
|
438
422
|
<DropdownMenu.Item class={itemClass} onSelect={toggleProjection}>
|
|
439
423
|
{#if projection === 'perspective'}
|
|
440
424
|
<Square class="h-4 w-4" />
|
|
@@ -482,7 +466,6 @@
|
|
|
482
466
|
sideOffset={4}
|
|
483
467
|
class="min-w-40 p-1 shadow-md z-10001 rounded-md border bg-popover text-popover-foreground"
|
|
484
468
|
>
|
|
485
|
-
<!-- Render style: single-choice, current one checked. -->
|
|
486
469
|
{#each STYLE_OPTIONS as { look, label } (look)}
|
|
487
470
|
<DropdownMenu.Item
|
|
488
471
|
closeOnSelect={false}
|
|
@@ -498,7 +481,6 @@
|
|
|
498
481
|
|
|
499
482
|
<DropdownMenu.Separator class="my-1 h-px bg-border" />
|
|
500
483
|
|
|
501
|
-
<!-- Edges overlay toggle. -->
|
|
502
484
|
<DropdownMenu.Item
|
|
503
485
|
closeOnSelect={false}
|
|
504
486
|
class="{itemClass} {edgesVisible ? 'text-primary' : ''}"
|
|
@@ -540,7 +522,6 @@
|
|
|
540
522
|
</DropdownMenu.Item>
|
|
541
523
|
{/if}
|
|
542
524
|
|
|
543
|
-
<!-- Scene tools -->
|
|
544
525
|
{#if config.showSceneManager || config.showScreenshotButton || config.showFullscreenButton}
|
|
545
526
|
<DropdownMenu.Separator class="my-1 h-px bg-border" />
|
|
546
527
|
{/if}
|
|
@@ -585,7 +566,6 @@
|
|
|
585
566
|
</div>
|
|
586
567
|
</Resizable.Pane>
|
|
587
568
|
|
|
588
|
-
<!-- Scene Manager Pane -->
|
|
589
569
|
{#if sceneManagerOpen && scene && outliner}
|
|
590
570
|
<Resizable.Handle withHandle />
|
|
591
571
|
<Resizable.Pane id="scene-manager" order={2} defaultSize={15} minSize={8} maxSize={30}>
|
|
@@ -618,7 +598,6 @@
|
|
|
618
598
|
border-radius: 0 !important;
|
|
619
599
|
}
|
|
620
600
|
|
|
621
|
-
/* Blur overlay animation */
|
|
622
601
|
.blur-overlay {
|
|
623
602
|
pointer-events: none;
|
|
624
603
|
}
|
|
@@ -5,15 +5,12 @@ export interface ViewerConfig {
|
|
|
5
5
|
showFullscreenButton?: boolean;
|
|
6
6
|
showSceneManager?: boolean;
|
|
7
7
|
showToolsMenu?: boolean;
|
|
8
|
-
/** Expose the grid show/hide toggle in the tools menu. Grid starts hidden. */
|
|
9
8
|
showGridToggle?: boolean;
|
|
10
|
-
/**
|
|
11
|
-
* Expose the "Display" submenu (render style picker + edges toggle) in the tools menu.
|
|
12
|
-
* Defaults on. Starts on the 'technical' style with edges hidden.
|
|
13
|
-
*/
|
|
14
9
|
showDisplayMenu?: boolean;
|
|
15
10
|
enableMeshClick?: boolean;
|
|
16
11
|
backgroundColor?: string;
|
|
12
|
+
/** Initial state of the edge overlay and its display-menu checkmark. Default false. */
|
|
13
|
+
showEdges?: boolean;
|
|
17
14
|
}
|
|
18
15
|
interface Props {
|
|
19
16
|
meshes: any[];
|
|
@@ -22,26 +19,18 @@ interface Props {
|
|
|
22
19
|
isBlurred?: boolean;
|
|
23
20
|
drawerOpen?: boolean;
|
|
24
21
|
viewerConfig?: ViewerConfig;
|
|
25
|
-
/**
|
|
26
|
-
* Branding logo URL. When set, shown as a small watermark in the viewer's
|
|
27
|
-
* bottom-right corner. Omitted/empty renders nothing.
|
|
28
|
-
*/
|
|
22
|
+
/** Shown as a watermark in the bottom-right corner. */
|
|
29
23
|
logoUrl?: string;
|
|
30
24
|
/**
|
|
31
25
|
* Hands the live three.js viewer to the host once the canvas is up, for apps drawing their
|
|
32
|
-
* own content
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* Return a cleanup function to tear down what you added; it runs before the viewer disposes.
|
|
36
|
-
* Anything added outside a solve needs `viewer.invalidate()` to repaint — the render loop is
|
|
37
|
-
* on-demand.
|
|
26
|
+
* own content. Return a cleanup function to tear down what you added; it runs before the
|
|
27
|
+
* viewer disposes. Anything added outside a solve needs `viewer.invalidate()` to repaint —
|
|
28
|
+
* the render loop is on-demand.
|
|
38
29
|
*/
|
|
39
30
|
onViewerReady?: (viewer: ThreeViewer) => void | (() => void);
|
|
40
31
|
/**
|
|
41
|
-
* UI language for the viewer's own chrome
|
|
42
|
-
*
|
|
43
|
-
* reads the nearest locale context (set by the host app), defaulting to
|
|
44
|
-
* English. Does not translate Grasshopper-sourced names/metadata.
|
|
32
|
+
* UI language for the viewer's own chrome. Omitted, the viewer reads the nearest locale
|
|
33
|
+
* context. Does not translate Grasshopper-sourced names/metadata.
|
|
45
34
|
*/
|
|
46
35
|
lang?: Locale;
|
|
47
36
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { type Component } from 'svelte';
|
|
2
2
|
import { type FooterItem } from '../contexts/footerContext.svelte';
|
|
3
|
-
/** Config for a footer-item registration; generic over the component's props. */
|
|
4
3
|
export type FooterItemConfig<P extends Record<string, unknown> = Record<string, unknown>> = Omit<FooterItem<P>, 'position' | 'priority'> & Partial<Pick<FooterItem<P>, 'position' | 'priority'>>;
|
|
5
4
|
/**
|
|
6
5
|
* Register a footer item for the lifetime of the calling component.
|
|
7
6
|
*
|
|
8
|
-
* Registration
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Registration waits for onMount so it never runs during SSR, where reading the context
|
|
8
|
+
* throws. A no-op when no footer context is present (a component rendered outside the root
|
|
9
|
+
* layout) or no component is supplied.
|
|
11
10
|
*/
|
|
12
11
|
export declare function useFooterItem<P extends Record<string, unknown>>(config: FooterItemConfig<P>): void;
|
|
13
12
|
export type { Component };
|
|
@@ -3,9 +3,9 @@ import { FOOTER_CONTEXT_KEY } from '../contexts/footerContext.svelte';
|
|
|
3
3
|
/**
|
|
4
4
|
* Register a footer item for the lifetime of the calling component.
|
|
5
5
|
*
|
|
6
|
-
* Registration
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Registration waits for onMount so it never runs during SSR, where reading the context
|
|
7
|
+
* throws. A no-op when no footer context is present (a component rendered outside the root
|
|
8
|
+
* layout) or no component is supplied.
|
|
9
9
|
*/
|
|
10
10
|
export function useFooterItem(config) {
|
|
11
11
|
onMount(() => {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Adaptive solving indicator: measures solve durations and adjusts its
|
|
3
|
+
* visibility delay from the running average.
|
|
4
4
|
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* - First solve:
|
|
5
|
+
* - Average under FAST_THRESHOLD_MS: never shown, so quick solves don't flicker
|
|
6
|
+
* - Average over SLOW_THRESHOLD_MS: shown immediately
|
|
7
|
+
* - Between the two: shown after a proportional slice of ANIMATION_DELAY
|
|
8
|
+
* - First solve: shown immediately, there is no history yet
|
|
9
9
|
*/
|
|
10
10
|
export declare function createSolvingIndicator(isSolving: () => boolean): {
|
|
11
11
|
readonly show: boolean;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { untrack } from 'svelte';
|
|
2
2
|
import { APP_DEFAULTS } from '../constants';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Adaptive solving indicator: measures solve durations and adjusts its
|
|
5
|
+
* visibility delay from the running average.
|
|
6
6
|
*
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
* - First solve:
|
|
7
|
+
* - Average under FAST_THRESHOLD_MS: never shown, so quick solves don't flicker
|
|
8
|
+
* - Average over SLOW_THRESHOLD_MS: shown immediately
|
|
9
|
+
* - Between the two: shown after a proportional slice of ANIMATION_DELAY
|
|
10
|
+
* - First solve: shown immediately, there is no history yet
|
|
11
11
|
*/
|
|
12
12
|
export function createSolvingIndicator(isSolving) {
|
|
13
13
|
const { FAST_THRESHOLD_MS, SLOW_THRESHOLD_MS, HISTORY_SIZE, ANIMATION_DELAY } = APP_DEFAULTS.SOLVING_INDICATOR;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { SolveSession, SolveSessionArgs } from '@selvajs/solve/client';
|
|
2
2
|
/**
|
|
3
|
-
* Wraps a Solve Session so its state reads reactively inside Svelte components.
|
|
3
|
+
* Wraps a Solve Session so its state reads reactively inside Svelte components. Use this in
|
|
4
|
+
* a component, never `createSolveSession` directly: the raw session's getters return correct
|
|
5
|
+
* values but never re-render.
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* session it wraps.
|
|
8
|
-
*
|
|
9
|
-
* Must be called during component initialization (it uses `$effect` to manage the
|
|
10
|
-
* subscription, so teardown follows the owning component's lifecycle).
|
|
7
|
+
* Must be called during component initialization — the subscription is managed by `$effect`,
|
|
8
|
+
* so teardown follows the owning component's lifecycle.
|
|
11
9
|
*/
|
|
12
10
|
export declare function useSolveSession(args: SolveSessionArgs): SolveSession;
|
|
@@ -1,35 +1,22 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// exposes plain getters plus a `subscribe()` seam. That makes it usable headless, but a
|
|
5
|
-
// plain getter read inside Svelte markup is NOT reactive — nothing tells the component to
|
|
6
|
-
// re-run. This adapter closes that gap: it subscribes once, bumps a `$state` version
|
|
7
|
-
// counter on every notification, and reads that counter inside each getter so any
|
|
8
|
-
// component touching one re-runs when the session changes.
|
|
9
|
-
//
|
|
10
|
-
// The counter (rather than mirroring each field into its own `$state`) keeps this a pure
|
|
11
|
-
// republish: no field list to keep in sync as the session grows, and no risk of a mirrored
|
|
12
|
-
// copy drifting from the source of truth.
|
|
1
|
+
// A version counter, rather than mirroring each field into its own `$state`, keeps this a
|
|
2
|
+
// pure republish: no field list to keep in sync as the session grows, and no mirrored copy
|
|
3
|
+
// that can drift from the source of truth.
|
|
13
4
|
import { createSolveSession } from '@selvajs/solve/client';
|
|
14
5
|
/**
|
|
15
|
-
* Wraps a Solve Session so its state reads reactively inside Svelte components.
|
|
6
|
+
* Wraps a Solve Session so its state reads reactively inside Svelte components. Use this in
|
|
7
|
+
* a component, never `createSolveSession` directly: the raw session's getters return correct
|
|
8
|
+
* values but never re-render.
|
|
16
9
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* session it wraps.
|
|
20
|
-
*
|
|
21
|
-
* Must be called during component initialization (it uses `$effect` to manage the
|
|
22
|
-
* subscription, so teardown follows the owning component's lifecycle).
|
|
10
|
+
* Must be called during component initialization — the subscription is managed by `$effect`,
|
|
11
|
+
* so teardown follows the owning component's lifecycle.
|
|
23
12
|
*/
|
|
24
13
|
export function useSolveSession(args) {
|
|
25
14
|
const session = createSolveSession(args);
|
|
26
|
-
//
|
|
27
|
-
// the dependency; the value itself is never meaningful.
|
|
15
|
+
// Reading this inside a getter is what registers the dependency; the value never matters.
|
|
28
16
|
let version = $state(0);
|
|
29
17
|
$effect(() => {
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
// whole cleanup.
|
|
18
|
+
// The session is created alongside this adapter and outlives no other component, so
|
|
19
|
+
// dropping the subscription is the whole cleanup.
|
|
33
20
|
return session.subscribe(() => {
|
|
34
21
|
version += 1;
|
|
35
22
|
});
|
package/dist/constants.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export const APP_DEFAULTS = {
|
|
2
2
|
// File upload limits
|
|
3
3
|
// TEMP (dev): raised 150 MB → 300 MB so large dev file-widget inputs aren't
|
|
4
|
-
// blocked client-side. The server request cap (COMPUTE_REQUEST_MAX_BYTES)
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// before release.
|
|
4
|
+
// blocked client-side. The server request cap (COMPUTE_REQUEST_MAX_BYTES)
|
|
5
|
+
// still defaults to 256 MB, and base64 inflates a raw file by ~4/3, so
|
|
6
|
+
// anything over ~192 MB is rejected with a 413 after the client accepts it.
|
|
7
|
+
// Revert to 150 before release.
|
|
8
8
|
FILE_UPLOAD: {
|
|
9
9
|
MAX_SIZE_MB: 300,
|
|
10
10
|
MAX_SIZE_BYTES: 300 * 1024 * 1024
|
|
@@ -4,13 +4,11 @@ export interface ClientSlotArgs {
|
|
|
4
4
|
/** Grasshopper parameter instance GUID (LayoutItem.paramId / SchemaInput.id). */
|
|
5
5
|
inputId: string;
|
|
6
6
|
displayName: string;
|
|
7
|
-
/** The current value held for this input (e.g. the prefilled JSON), if any. */
|
|
8
7
|
value: unknown;
|
|
9
8
|
/**
|
|
10
|
-
* Commit a value for this input
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Lets a slot be an interactive control (a custom picker), not just a display cell.
|
|
9
|
+
* Commit a value for this input — the same channel a built-in widget's change uses, so a
|
|
10
|
+
* slot can be an interactive control rather than just a display cell. `forceSolve`
|
|
11
|
+
* requests a solve even in manual-solve mode.
|
|
14
12
|
*/
|
|
15
13
|
onValueChange: (value: SupportedTypes, forceSolve?: boolean) => void;
|
|
16
14
|
}
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import { type Component } from 'svelte';
|
|
2
2
|
import { SvelteMap } from 'svelte/reactivity';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* reactive state from it (e.g. `() => ({ status: myState.status })`) to keep the footer
|
|
7
|
-
* in sync.
|
|
8
|
-
*
|
|
9
|
-
* Generic over the component's props `P` so registration is type-checked at the call
|
|
10
|
-
* site. The store holds a heterogeneous mix of items, so its map widens `P` to
|
|
4
|
+
* Generic over the component's props `P` so registration is type-checked at the call site.
|
|
5
|
+
* The store holds a heterogeneous mix of items, so its map widens `P` to
|
|
11
6
|
* `Record<string, unknown>` at the boundary.
|
|
12
7
|
*/
|
|
13
8
|
export interface FooterItem<P extends Record<string, unknown> = Record<string, unknown>> {
|
|
14
9
|
id: string;
|
|
15
10
|
component: Component<P>;
|
|
16
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Re-invoked on every render. Return reactive state — `() => ({ status: s.status })` —
|
|
13
|
+
* or the footer never updates.
|
|
14
|
+
*/
|
|
17
15
|
getProps: () => P;
|
|
18
16
|
position: 'left' | 'right';
|
|
19
17
|
priority: number;
|
package/dist/external/storage.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// Moved to
|
|
2
|
-
//
|
|
3
|
-
// the pre-step producer routes that import it — working unchanged.
|
|
1
|
+
// Moved to @selvajs/solve/client, beside the Solve Session that hydrates from it.
|
|
2
|
+
// Re-exported to keep the published `@selvajs/ui/external` sub-path working unchanged.
|
|
4
3
|
export { writeExternalValue, readExternalValue, clearExternalValue, getExternalInputs } from '@selvajs/solve/client';
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import { type Locale, type ViewerMessages } from './messages';
|
|
2
2
|
export interface LocaleContext {
|
|
3
|
-
/** Current locale. Called reactively — return reactive state to enable live switching. */
|
|
4
3
|
readonly locale: Locale;
|
|
5
|
-
/** Resolved message catalog for the current locale. */
|
|
6
4
|
readonly messages: ViewerMessages;
|
|
7
5
|
}
|
|
8
6
|
/**
|
|
9
|
-
* Provide the locale to descendants. Pass a getter
|
|
10
|
-
* `$state`,
|
|
7
|
+
* Provide the locale to descendants. Pass a getter, not a value: it is re-read on every
|
|
8
|
+
* consumer render, so a reactive source (a `$state`, the app's Paraglide locale) switches
|
|
9
|
+
* the language live.
|
|
11
10
|
*/
|
|
12
11
|
export declare function setLocaleContext(getLocale: () => Locale | undefined): void;
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* need a null check.
|
|
13
|
+
* Falls back to an English-only context when no provider exists (a primitive used in
|
|
14
|
+
* isolation), so consumers never need a null check.
|
|
17
15
|
*/
|
|
18
16
|
export declare function getLocaleContext(): LocaleContext;
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { getContext, setContext } from 'svelte';
|
|
2
2
|
import { messagesFor, DEFAULT_LOCALE } from './messages';
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// ============================================================================
|
|
6
|
-
//
|
|
7
|
-
// Carries the current UI locale down to the viewer and its panels without
|
|
8
|
-
// threading a `lang` prop through every layer. The value is a getter so the
|
|
9
|
-
// host can back it with reactive state — flip the language and the viewer
|
|
10
|
-
// re-renders live.
|
|
3
|
+
// Carries the UI locale down to the viewer and its panels without threading a
|
|
4
|
+
// `lang` prop through every layer.
|
|
11
5
|
//
|
|
12
6
|
// Resolution order for any consuming component:
|
|
13
7
|
// explicit `lang` prop → nearest locale context → English default
|
|
@@ -15,12 +9,12 @@ import { messagesFor, DEFAULT_LOCALE } from './messages';
|
|
|
15
9
|
// Two ways to provide it:
|
|
16
10
|
// - Standalone: <Viewer lang="de" /> — Viewer provides the context itself.
|
|
17
11
|
// - In an app: call setLocaleContext(() => app.locale) once at the root; the
|
|
18
|
-
// viewer (and anything else) reads it.
|
|
19
|
-
// locale in here.
|
|
12
|
+
// viewer (and anything else) reads it.
|
|
20
13
|
const LOCALE_CONTEXT_KEY = Symbol('viewer-locale-context');
|
|
21
14
|
/**
|
|
22
|
-
* Provide the locale to descendants. Pass a getter
|
|
23
|
-
* `$state`,
|
|
15
|
+
* Provide the locale to descendants. Pass a getter, not a value: it is re-read on every
|
|
16
|
+
* consumer render, so a reactive source (a `$state`, the app's Paraglide locale) switches
|
|
17
|
+
* the language live.
|
|
24
18
|
*/
|
|
25
19
|
export function setLocaleContext(getLocale) {
|
|
26
20
|
const ctx = {
|
|
@@ -34,9 +28,8 @@ export function setLocaleContext(getLocale) {
|
|
|
34
28
|
setContext(LOCALE_CONTEXT_KEY, ctx);
|
|
35
29
|
}
|
|
36
30
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* need a null check.
|
|
31
|
+
* Falls back to an English-only context when no provider exists (a primitive used in
|
|
32
|
+
* isolation), so consumers never need a null check.
|
|
40
33
|
*/
|
|
41
34
|
export function getLocaleContext() {
|
|
42
35
|
return (getContext(LOCALE_CONTEXT_KEY) ?? {
|
package/dist/i18n/messages.d.ts
CHANGED
|
@@ -7,9 +7,7 @@ export interface ViewerMessages {
|
|
|
7
7
|
views: string;
|
|
8
8
|
measure: string;
|
|
9
9
|
grid: string;
|
|
10
|
-
/** "Display" submenu label — groups render style + edges. */
|
|
11
10
|
display: string;
|
|
12
|
-
/** Edges show/hide toggle label. */
|
|
13
11
|
edges: string;
|
|
14
12
|
sceneManager: string;
|
|
15
13
|
screenshot: string;
|
package/dist/i18n/messages.js
CHANGED
|
@@ -2,14 +2,9 @@
|
|
|
2
2
|
// Viewer message catalog (library-owned strings)
|
|
3
3
|
// ============================================================================
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// names): those live in the user's .gh file and can't be translated here.
|
|
9
|
-
//
|
|
10
|
-
// The library ships English + German. A host app can switch locale at runtime
|
|
11
|
-
// via the locale context (see ./localeContext.svelte.ts) — e.g. selva feeds its
|
|
12
|
-
// own Paraglide locale in. With no provider, components fall back to English.
|
|
5
|
+
// Only the strings @selvajs/ui renders itself. Names coming from a Grasshopper
|
|
6
|
+
// definition — meshes, layers, metadata keys — live in the user's .gh file and
|
|
7
|
+
// cannot be translated here.
|
|
13
8
|
const en = {
|
|
14
9
|
toolsMenu: 'Viewer tools',
|
|
15
10
|
switchTo2D: 'Switch to 2D',
|