@selvajs/ui 6.6.0-beta.4 → 6.6.0-beta.5
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/ErrorScreen.svelte +6 -2
- package/dist/components/compute/AppLayout.svelte +157 -71
- package/dist/components/compute/AppLayout.svelte.d.ts +6 -0
- package/dist/components/compute/ComputeApp.svelte +39 -20
- package/dist/components/compute/ComputeApp.svelte.d.ts +19 -2
- package/dist/components/compute/ComputeMessagesDialog.svelte +30 -40
- package/dist/components/compute/ParameterPresetManager.svelte +7 -44
- package/dist/components/compute/ParameterPresetManager.svelte.d.ts +4 -5
- package/dist/components/compute/SolveMessages.svelte +30 -37
- package/dist/components/compute/SolveMessages.svelte.d.ts +0 -12
- package/dist/components/layout/PageFooter.svelte +21 -11
- package/dist/components/preview/ChartOutput.svelte +12 -7
- package/dist/components/preview/Group.svelte +33 -18
- package/dist/components/preview/HelpButton.svelte +4 -2
- package/dist/components/preview/ImageOutput.svelte +427 -68
- package/dist/components/preview/InputControl.svelte +11 -2
- package/dist/components/preview/OutputDisplay.svelte +17 -12
- package/dist/components/preview/TabContent.svelte +3 -0
- package/dist/components/preview/inputs/ColorInput.svelte +3 -1
- package/dist/components/preview/inputs/ColorInput.svelte.d.ts +1 -0
- package/dist/components/preview/inputs/DropdownInput.svelte +4 -1
- package/dist/components/preview/inputs/FileInput.svelte +50 -34
- package/dist/components/preview/inputs/NumberInput.svelte +92 -10
- package/dist/components/preview/inputs/NumberInput.svelte.d.ts +2 -0
- package/dist/components/preview/inputs/TextInput.svelte +8 -3
- package/dist/components/primitives/CalculateButton.svelte +49 -23
- package/dist/components/primitives/CalculateButton.svelte.d.ts +3 -0
- package/dist/components/primitives/SolvingIndicator.svelte +21 -17
- package/dist/components/primitives/badge/badge.svelte +2 -2
- package/dist/components/primitives/badge/badge.svelte.d.ts +4 -4
- package/dist/components/primitives/button/button.svelte +2 -2
- package/dist/components/primitives/button/button.svelte.d.ts +4 -4
- package/dist/components/primitives/checkbox/checkbox.svelte +1 -1
- package/dist/components/primitives/dialog/dialog-content.svelte +6 -2
- package/dist/components/primitives/input/input.svelte +2 -2
- package/dist/components/primitives/input-group/input-group.svelte +1 -1
- package/dist/components/primitives/mode-toggle/mode-toggle.svelte +4 -1
- package/dist/components/primitives/scroll-area/scroll-area.svelte +1 -1
- package/dist/components/primitives/search/search.svelte +1 -1
- package/dist/components/primitives/select/select-trigger.svelte +1 -1
- package/dist/components/primitives/slider/slider.svelte +7 -2
- package/dist/components/primitives/slider/slider.svelte.d.ts +5 -1
- package/dist/components/primitives/switch/switch.svelte +1 -1
- package/dist/components/primitives/tabs/tabs-trigger.svelte +1 -1
- package/dist/components/primitives/tabs/tabs-trigger.svelte.d.ts +1 -1
- package/dist/components/primitives/textarea/textarea.svelte +1 -1
- package/dist/components/primitives/toggle/toggle.svelte +1 -1
- package/dist/components/primitives/toggle/toggle.svelte.d.ts +1 -1
- package/dist/components/viewer/Viewer.svelte +20 -17
- package/dist/compute/run-panel-action.d.ts +8 -0
- package/dist/compute/run-panel-action.js +12 -0
- package/dist/compute/useSolveSession.svelte.js +3 -0
- package/dist/i18n/localeContext.svelte.d.ts +10 -3
- package/dist/i18n/localeContext.svelte.js +27 -5
- package/dist/i18n/messages.d.ts +115 -2
- package/dist/i18n/messages.js +199 -11
- package/dist/index.d.ts +1 -1
- package/dist/public.d.ts +1 -1
- package/dist/schema/visibility-rules.js +21 -0
- package/dist/styles/base.css +9 -6
- package/dist/types/actionButton.d.ts +17 -3
- package/dist/types/presetLabels.d.ts +3 -2
- package/dist/types/presetLabels.js +9 -8
- package/package.json +4 -4
- package/src/lib/components/ErrorScreen.svelte +6 -2
- package/src/lib/components/compute/AppLayout.svelte +157 -71
- package/src/lib/components/compute/ComputeApp.svelte +39 -20
- package/src/lib/components/compute/ComputeMessagesDialog.svelte +30 -40
- package/src/lib/components/compute/ParameterPresetManager.svelte +7 -44
- package/src/lib/components/compute/SolveMessages.svelte +30 -37
- package/src/lib/components/layout/PageFooter.svelte +21 -11
- package/src/lib/components/preview/ChartOutput.svelte +12 -7
- package/src/lib/components/preview/Group.svelte +33 -18
- package/src/lib/components/preview/HelpButton.svelte +4 -2
- package/src/lib/components/preview/ImageOutput.svelte +427 -68
- package/src/lib/components/preview/InputControl.svelte +11 -2
- package/src/lib/components/preview/OutputDisplay.svelte +17 -12
- package/src/lib/components/preview/TabContent.svelte +3 -0
- package/src/lib/components/preview/inputs/ColorInput.svelte +3 -1
- package/src/lib/components/preview/inputs/DropdownInput.svelte +4 -1
- package/src/lib/components/preview/inputs/FileInput.svelte +50 -34
- package/src/lib/components/preview/inputs/NumberInput.svelte +92 -10
- package/src/lib/components/preview/inputs/TextInput.svelte +8 -3
- package/src/lib/components/primitives/CalculateButton.svelte +49 -23
- package/src/lib/components/primitives/SolvingIndicator.svelte +21 -17
- package/src/lib/components/primitives/badge/badge.svelte +2 -2
- package/src/lib/components/primitives/button/button.svelte +2 -2
- package/src/lib/components/primitives/checkbox/checkbox.svelte +1 -1
- package/src/lib/components/primitives/dialog/dialog-content.svelte +6 -2
- package/src/lib/components/primitives/input/input.svelte +2 -2
- package/src/lib/components/primitives/input-group/input-group.svelte +1 -1
- package/src/lib/components/primitives/mode-toggle/mode-toggle.svelte +4 -1
- package/src/lib/components/primitives/scroll-area/scroll-area.svelte +1 -1
- package/src/lib/components/primitives/search/search.svelte +1 -1
- package/src/lib/components/primitives/select/select-trigger.svelte +1 -1
- package/src/lib/components/primitives/slider/slider.svelte +7 -2
- package/src/lib/components/primitives/switch/switch.svelte +1 -1
- package/src/lib/components/primitives/tabs/tabs-trigger.svelte +1 -1
- package/src/lib/components/primitives/textarea/textarea.svelte +1 -1
- package/src/lib/components/primitives/toggle/toggle.svelte +1 -1
- package/src/lib/components/viewer/Viewer.svelte +20 -17
- package/src/lib/compute/run-panel-action.ts +16 -0
- package/src/lib/compute/useSolveSession.svelte.ts +3 -0
- package/src/lib/i18n/localeContext.svelte.ts +40 -5
- package/src/lib/i18n/messages.ts +351 -13
- package/src/lib/index.ts +1 -0
- package/src/lib/public.ts +1 -0
- package/src/lib/schema/visibility-rules.ts +23 -0
- package/src/lib/styles/base.css +9 -6
- package/src/lib/types/actionButton.ts +16 -3
- package/src/lib/types/presetLabels.ts +13 -11
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as Card from './primitives/card/index.js';
|
|
4
4
|
import { Button } from './primitives/button/index.js';
|
|
5
5
|
import Callout from './primitives/Callout.svelte';
|
|
6
|
+
import { getLocaleContext } from '../i18n/localeContext.svelte';
|
|
6
7
|
|
|
7
8
|
interface Props {
|
|
8
9
|
status?: number;
|
|
@@ -12,6 +13,9 @@
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
let { status = 500, message, details, homeLink = '/' }: Props = $props();
|
|
16
|
+
|
|
17
|
+
const locale = getLocaleContext();
|
|
18
|
+
const t = $derived(locale.messages);
|
|
15
19
|
</script>
|
|
16
20
|
|
|
17
21
|
<div class="p-4 flex min-h-screen items-center justify-center bg-background">
|
|
@@ -26,7 +30,7 @@
|
|
|
26
30
|
</Card.Header>
|
|
27
31
|
<Card.Content>
|
|
28
32
|
<div class="gap-4 flex flex-col">
|
|
29
|
-
<Callout tone="danger" title=
|
|
33
|
+
<Callout tone="danger" title={t.errorTitle}>
|
|
30
34
|
{#if message}{message}{/if}
|
|
31
35
|
</Callout>
|
|
32
36
|
{#if details}
|
|
@@ -37,7 +41,7 @@
|
|
|
37
41
|
</div>
|
|
38
42
|
</Card.Content>
|
|
39
43
|
<Card.Footer>
|
|
40
|
-
<Button variant="outline" href={homeLink} rel="external" class="w-full">
|
|
44
|
+
<Button variant="outline" href={homeLink} rel="external" class="w-full">{t.goHome}</Button>
|
|
41
45
|
</Card.Footer>
|
|
42
46
|
</Card.Root>
|
|
43
47
|
</div>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { UISchema, SupportedTypes, ParameterPreset } from '@selvajs/schemas';
|
|
3
|
+
import type { SolveSettlement } from '@selvajs/solve/client';
|
|
3
4
|
import type { ActionButton } from '../../types/actionButton';
|
|
4
5
|
import type { PresetLabels } from '../../types/presetLabels';
|
|
5
|
-
import { ChevronUp } from '@lucide/svelte';
|
|
6
|
+
import { ChevronUp, Download, Ellipsis, Upload } from '@lucide/svelte';
|
|
6
7
|
import type { ThreeViewer } from '@selvajs/visualization/render';
|
|
7
8
|
import Viewer, { type ViewerConfig } from '../viewer/Viewer.svelte';
|
|
8
9
|
import CalculateButton from '../primitives/CalculateButton.svelte';
|
|
@@ -10,10 +11,13 @@
|
|
|
10
11
|
import TabLayout from '../preview/TabLayout.svelte';
|
|
11
12
|
import CollapsedPanelStrip from './CollapsedPanelStrip.svelte';
|
|
12
13
|
import * as Resizable from '../primitives/resizable';
|
|
14
|
+
import * as DropdownMenu from '../primitives/dropdown-menu/index.js';
|
|
13
15
|
import { Button } from '../..';
|
|
14
16
|
import ParameterPresetManager from './ParameterPresetManager.svelte';
|
|
17
|
+
import { runPanelAction } from '../../compute/run-panel-action';
|
|
15
18
|
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
16
19
|
import { buildVisibilityMap, isTabVisible, itemKey } from '../../schema/visibility-rules';
|
|
20
|
+
import { DEFAULT_PRESET_LABELS } from '../../types/presetLabels';
|
|
17
21
|
|
|
18
22
|
const locale = getLocaleContext();
|
|
19
23
|
const t = $derived(locale.messages);
|
|
@@ -29,6 +33,11 @@
|
|
|
29
33
|
hasNeverSolved?: boolean;
|
|
30
34
|
isViewerFullscreen?: boolean;
|
|
31
35
|
oncalculate?: () => void;
|
|
36
|
+
/**
|
|
37
|
+
* The result for the current inputs, solving if it must (`session.ensureSolved`). Needed
|
|
38
|
+
* by `solvesFirst` actions; without it they run straight away.
|
|
39
|
+
*/
|
|
40
|
+
ensureSolved?: () => Promise<SolveSettlement>;
|
|
32
41
|
values: Record<string, unknown>;
|
|
33
42
|
onValueChange: (id: string, val: SupportedTypes, forceSolve?: boolean) => void | Promise<void>;
|
|
34
43
|
onLoadValues?: (values: Record<string, unknown>) => void | Promise<void>;
|
|
@@ -52,6 +61,7 @@
|
|
|
52
61
|
hasNeverSolved = false,
|
|
53
62
|
isViewerFullscreen = $bindable(false),
|
|
54
63
|
oncalculate = () => {},
|
|
64
|
+
ensureSolved,
|
|
55
65
|
values,
|
|
56
66
|
onValueChange,
|
|
57
67
|
onLoadValues,
|
|
@@ -66,6 +76,11 @@
|
|
|
66
76
|
onViewerReady
|
|
67
77
|
}: Props = $props();
|
|
68
78
|
|
|
79
|
+
const presetText = $derived({ ...DEFAULT_PRESET_LABELS, ...presetLabels });
|
|
80
|
+
let presets = $state<ParameterPresetManager | null>(null);
|
|
81
|
+
const hasMenu = $derived(showSaveButton || showLoadButton || panelActions.length > 0);
|
|
82
|
+
const resultIsStale = $derived(hasPendingChanges || hasNeverSolved);
|
|
83
|
+
|
|
69
84
|
const hasViewer = $derived(
|
|
70
85
|
!!(schema?.viewerOptions?.enableLocal || schema?.viewerOptions?.enableRemote)
|
|
71
86
|
);
|
|
@@ -151,54 +166,110 @@
|
|
|
151
166
|
}
|
|
152
167
|
</script>
|
|
153
168
|
|
|
169
|
+
<!-- Everything but Calculate: saving and loading inputs, then the host's own actions. -->
|
|
170
|
+
{#snippet actionsMenu()}
|
|
171
|
+
<DropdownMenu.Root>
|
|
172
|
+
<DropdownMenu.Trigger>
|
|
173
|
+
{#snippet child({ props })}
|
|
174
|
+
<Button
|
|
175
|
+
{...props}
|
|
176
|
+
variant="outline"
|
|
177
|
+
size="icon-lg"
|
|
178
|
+
class="shrink-0"
|
|
179
|
+
title={t.moreActions}
|
|
180
|
+
aria-label={t.moreActions}
|
|
181
|
+
>
|
|
182
|
+
<Ellipsis />
|
|
183
|
+
</Button>
|
|
184
|
+
{/snippet}
|
|
185
|
+
</DropdownMenu.Trigger>
|
|
186
|
+
<!-- Above the fullscreen viewer, which sits at z-index 10000. -->
|
|
187
|
+
<DropdownMenu.Content side="top" align="end" sideOffset={6} class="min-w-56 z-10001 w-auto">
|
|
188
|
+
{#if showSaveButton}
|
|
189
|
+
<DropdownMenu.Item onSelect={() => presets?.openSave()}>
|
|
190
|
+
<Download />
|
|
191
|
+
{presetText.saveMenuItem}
|
|
192
|
+
</DropdownMenu.Item>
|
|
193
|
+
{/if}
|
|
194
|
+
{#if showLoadButton}
|
|
195
|
+
<DropdownMenu.Item onSelect={() => presets?.openLoad()}>
|
|
196
|
+
<Upload />
|
|
197
|
+
{presetText.loadMenuItem}
|
|
198
|
+
</DropdownMenu.Item>
|
|
199
|
+
{/if}
|
|
200
|
+
{#if (showSaveButton || showLoadButton) && panelActions.length > 0}
|
|
201
|
+
<DropdownMenu.Separator />
|
|
202
|
+
{/if}
|
|
203
|
+
{#each panelActions as action (action.id)}
|
|
204
|
+
{@const hint =
|
|
205
|
+
action.hint ?? (action.solvesFirst && resultIsStale ? t.calculatesFirst : undefined)}
|
|
206
|
+
<DropdownMenu.Item
|
|
207
|
+
disabled={action.disabled}
|
|
208
|
+
variant={action.variant ?? 'default'}
|
|
209
|
+
onSelect={() => runPanelAction(action, ensureSolved)}
|
|
210
|
+
class="items-start"
|
|
211
|
+
>
|
|
212
|
+
{#if action.icon}
|
|
213
|
+
{@const IconComponent = action.icon}
|
|
214
|
+
<IconComponent class="mt-0.5" />
|
|
215
|
+
{/if}
|
|
216
|
+
<span class="flex flex-col">
|
|
217
|
+
{action.label}
|
|
218
|
+
{#if hint}
|
|
219
|
+
<span class="text-xs leading-snug text-muted-foreground">{hint}</span>
|
|
220
|
+
{/if}
|
|
221
|
+
</span>
|
|
222
|
+
</DropdownMenu.Item>
|
|
223
|
+
{/each}
|
|
224
|
+
</DropdownMenu.Content>
|
|
225
|
+
</DropdownMenu.Root>
|
|
226
|
+
{/snippet}
|
|
227
|
+
|
|
228
|
+
<!-- One primary action and one menu. A definition that solves on every change has no Calculate,
|
|
229
|
+
so the menu sits alone at the end of the row. -->
|
|
230
|
+
{#snippet footer()}
|
|
231
|
+
{#if schema.instanceSolve === false}
|
|
232
|
+
<CalculateButton
|
|
233
|
+
{hasPendingChanges}
|
|
234
|
+
{hasNeverSolved}
|
|
235
|
+
{isSolving}
|
|
236
|
+
{oncalculate}
|
|
237
|
+
trailing={hasMenu ? actionsMenu : undefined}
|
|
238
|
+
/>
|
|
239
|
+
{:else if hasMenu}
|
|
240
|
+
<div class="mt-3 pb-3 pt-2 px-2 flex justify-end border-t border-border/50">
|
|
241
|
+
{@render actionsMenu()}
|
|
242
|
+
</div>
|
|
243
|
+
{/if}
|
|
244
|
+
{/snippet}
|
|
245
|
+
|
|
246
|
+
<!-- `hostsFooter` marks the one panel that carries the footer. On a phone the footer moves to the
|
|
247
|
+
drawer's pinned bottom instead. -->
|
|
154
248
|
{#snippet panelContent(
|
|
155
249
|
panelFilter: 'left' | 'right' | undefined,
|
|
156
250
|
requestedTabId: string | null,
|
|
157
|
-
|
|
158
|
-
showCalculateButton = true
|
|
251
|
+
hostsFooter = true
|
|
159
252
|
)}
|
|
160
253
|
<div class="panel-content-wrapper">
|
|
161
254
|
{#if schema.layout.type === 'tabbed'}
|
|
162
255
|
<TabLayout {schema} {values} {onValueChange} {panelFilter} {requestedTabId} />
|
|
163
256
|
{/if}
|
|
164
|
-
{#if
|
|
257
|
+
{#if hostsFooter && !isMobile}
|
|
165
258
|
<div class="panel-footer px-1">
|
|
166
|
-
|
|
167
|
-
{#if showParameterStateManager}
|
|
168
|
-
<ParameterPresetManager
|
|
169
|
-
{schema}
|
|
170
|
-
currentValues={values}
|
|
171
|
-
onLoadValues={handleLoadValues}
|
|
172
|
-
{showSaveButton}
|
|
173
|
-
{showLoadButton}
|
|
174
|
-
{onSaveState}
|
|
175
|
-
{onListStates}
|
|
176
|
-
labels={presetLabels}
|
|
177
|
-
/>
|
|
178
|
-
{/if}
|
|
179
|
-
{#each panelActions as action (action.id)}
|
|
180
|
-
<Button
|
|
181
|
-
variant={action.variant ?? 'outline'}
|
|
182
|
-
size={action.size ?? 'sm'}
|
|
183
|
-
onclick={action.onclick}
|
|
184
|
-
>
|
|
185
|
-
{#if action.icon}
|
|
186
|
-
{@const IconComponent = action.icon}
|
|
187
|
-
<IconComponent class="mr-2 h-4 w-4" />
|
|
188
|
-
{/if}
|
|
189
|
-
{action.label}
|
|
190
|
-
</Button>
|
|
191
|
-
{/each}
|
|
192
|
-
</div>
|
|
193
|
-
{#if !isMobile && showCalculateButton && schema.instanceSolve === false}
|
|
194
|
-
<CalculateButton {hasPendingChanges} {hasNeverSolved} {isSolving} {oncalculate} />
|
|
195
|
-
{/if}
|
|
259
|
+
{@render footer()}
|
|
196
260
|
</div>
|
|
197
261
|
{/if}
|
|
198
262
|
</div>
|
|
199
263
|
{/snippet}
|
|
200
264
|
|
|
201
|
-
|
|
265
|
+
<!-- Pinned under the scrolling fields, so the primary action never sits at the end of a long list. -->
|
|
266
|
+
{#snippet drawerFooter(isInert: boolean)}
|
|
267
|
+
<div class="drawer-footer" inert={isInert}>
|
|
268
|
+
{@render footer()}
|
|
269
|
+
</div>
|
|
270
|
+
{/snippet}
|
|
271
|
+
|
|
272
|
+
<main
|
|
202
273
|
data-layout-root
|
|
203
274
|
class="min-h-0 sm:flex-row sm:py-(--page-py) flex flex-1 flex-col overflow-hidden {leftCollapsed
|
|
204
275
|
? 'sm:pl-0'
|
|
@@ -230,12 +301,12 @@
|
|
|
230
301
|
class:drawer-open={drawerOpen}
|
|
231
302
|
class:drawer-closed={!drawerOpen}
|
|
232
303
|
>
|
|
304
|
+
<!-- Named by its visible label; aria-expanded carries the open/closed state. -->
|
|
233
305
|
<button
|
|
234
306
|
class="drawer-handle-bar"
|
|
235
307
|
onclick={() => (drawerOpen = !drawerOpen)}
|
|
236
308
|
ontouchstart={onDrawerTouchStart}
|
|
237
309
|
ontouchend={onDrawerTouchEnd}
|
|
238
|
-
aria-label={drawerOpen ? t.collapsePanel : t.expandPanel}
|
|
239
310
|
aria-expanded={drawerOpen}
|
|
240
311
|
>
|
|
241
312
|
<div class="drawer-pill"></div>
|
|
@@ -248,62 +319,47 @@
|
|
|
248
319
|
/>
|
|
249
320
|
</button>
|
|
250
321
|
|
|
251
|
-
|
|
322
|
+
<!-- Inert while closed: the fields are clipped out of view, and tabbing into them
|
|
323
|
+
would scroll the drawer's insides around behind the handle. -->
|
|
324
|
+
<div class="drawer-content" inert={!drawerOpen}>
|
|
252
325
|
<div class="px-3 pb-6">
|
|
253
326
|
{#if hasLeftPanel}
|
|
254
|
-
{@render panelContent(
|
|
255
|
-
hasRightPanel ? 'left' : undefined,
|
|
256
|
-
requestedLeftTabId,
|
|
257
|
-
!hasRightPanel,
|
|
258
|
-
false
|
|
259
|
-
)}
|
|
327
|
+
{@render panelContent(hasRightPanel ? 'left' : undefined, requestedLeftTabId, false)}
|
|
260
328
|
{/if}
|
|
261
329
|
{#if hasRightPanel}
|
|
262
|
-
|
|
330
|
+
<!-- Decorative: the section below names itself with a heading. -->
|
|
331
|
+
<div class="my-4 gap-2 flex items-center" aria-hidden="true">
|
|
263
332
|
<div class="h-px flex-1 bg-border"></div>
|
|
264
333
|
<span class="text-xs font-medium px-1 text-muted-foreground">
|
|
265
334
|
{rightTabs[0]?.label ?? t.moreTab}
|
|
266
335
|
</span>
|
|
267
336
|
<div class="h-px flex-1 bg-border"></div>
|
|
268
337
|
</div>
|
|
269
|
-
{@render panelContent('right', requestedRightTabId,
|
|
338
|
+
{@render panelContent('right', requestedRightTabId, false)}
|
|
270
339
|
{/if}
|
|
271
340
|
</div>
|
|
272
341
|
</div>
|
|
273
342
|
|
|
274
|
-
{
|
|
275
|
-
<div class="drawer-footer">
|
|
276
|
-
<CalculateButton {hasPendingChanges} {hasNeverSolved} {isSolving} {oncalculate} />
|
|
277
|
-
</div>
|
|
278
|
-
{/if}
|
|
343
|
+
{@render drawerFooter(!drawerOpen)}
|
|
279
344
|
</div>
|
|
280
345
|
{:else}
|
|
281
346
|
<div class="flex flex-1 flex-col overflow-hidden">
|
|
282
347
|
<div class="px-3 pt-3 flex-1 overflow-y-auto">
|
|
283
348
|
{#if hasLeftPanel}
|
|
284
|
-
{@render panelContent(
|
|
285
|
-
hasRightPanel ? 'left' : undefined,
|
|
286
|
-
requestedLeftTabId,
|
|
287
|
-
!hasRightPanel,
|
|
288
|
-
false
|
|
289
|
-
)}
|
|
349
|
+
{@render panelContent(hasRightPanel ? 'left' : undefined, requestedLeftTabId, false)}
|
|
290
350
|
{/if}
|
|
291
351
|
{#if hasRightPanel}
|
|
292
|
-
<div class="my-4 gap-2 flex items-center">
|
|
352
|
+
<div class="my-4 gap-2 flex items-center" aria-hidden="true">
|
|
293
353
|
<div class="h-px flex-1 bg-border"></div>
|
|
294
354
|
<span class="text-xs font-medium px-1 text-muted-foreground">
|
|
295
355
|
{rightTabs[0]?.label ?? t.moreTab}
|
|
296
356
|
</span>
|
|
297
357
|
<div class="h-px flex-1 bg-border"></div>
|
|
298
358
|
</div>
|
|
299
|
-
{@render panelContent('right', requestedRightTabId,
|
|
359
|
+
{@render panelContent('right', requestedRightTabId, false)}
|
|
300
360
|
{/if}
|
|
301
361
|
</div>
|
|
302
|
-
{
|
|
303
|
-
<div class="drawer-footer">
|
|
304
|
-
<CalculateButton {hasPendingChanges} {hasNeverSolved} {isSolving} {oncalculate} />
|
|
305
|
-
</div>
|
|
306
|
-
{/if}
|
|
362
|
+
{@render drawerFooter(false)}
|
|
307
363
|
</div>
|
|
308
364
|
{/if}
|
|
309
365
|
{:else if !hasViewer && !isTwoPanelMode && hasLeftPanel !== hasRightPanel}
|
|
@@ -312,9 +368,7 @@
|
|
|
312
368
|
<div class="min-h-0 flex h-full w-4/5 flex-col">
|
|
313
369
|
{@render panelContent(
|
|
314
370
|
hasRightPanel ? 'right' : undefined,
|
|
315
|
-
hasRightPanel ? requestedRightTabId : requestedLeftTabId
|
|
316
|
-
!hasRightPanel,
|
|
317
|
-
!hasRightPanel
|
|
371
|
+
hasRightPanel ? requestedRightTabId : requestedLeftTabId
|
|
318
372
|
)}
|
|
319
373
|
</div>
|
|
320
374
|
</div>
|
|
@@ -412,7 +466,7 @@
|
|
|
412
466
|
? 'hidden'
|
|
413
467
|
: ''}"
|
|
414
468
|
>
|
|
415
|
-
{@render panelContent('right', requestedRightTabId, !hasLeftPanel
|
|
469
|
+
{@render panelContent('right', requestedRightTabId, !hasLeftPanel)}
|
|
416
470
|
</div>
|
|
417
471
|
</Resizable.Pane>
|
|
418
472
|
{/if}
|
|
@@ -432,9 +486,20 @@
|
|
|
432
486
|
{/if}
|
|
433
487
|
</div>
|
|
434
488
|
{/if}
|
|
435
|
-
</div>
|
|
436
489
|
|
|
437
|
-
<SolvingIndicator show={showSolvingIndicator} />
|
|
490
|
+
<SolvingIndicator show={showSolvingIndicator} />
|
|
491
|
+
</main>
|
|
492
|
+
|
|
493
|
+
<!-- The save, load and validation dialogs; the footer's menu opens them. -->
|
|
494
|
+
<ParameterPresetManager
|
|
495
|
+
bind:this={presets}
|
|
496
|
+
{schema}
|
|
497
|
+
currentValues={values}
|
|
498
|
+
onLoadValues={handleLoadValues}
|
|
499
|
+
{onSaveState}
|
|
500
|
+
{onListStates}
|
|
501
|
+
labels={presetLabels}
|
|
502
|
+
/>
|
|
438
503
|
|
|
439
504
|
<style>
|
|
440
505
|
.fullscreen-layout {
|
|
@@ -480,8 +545,10 @@
|
|
|
480
545
|
.drawer-closed {
|
|
481
546
|
height: 60px;
|
|
482
547
|
}
|
|
548
|
+
/* The pinned footer takes a fixed slice, so a short phone opens the drawer further to leave
|
|
549
|
+
room for the fields; a tall one keeps it at 60% of the screen. */
|
|
483
550
|
.drawer-open {
|
|
484
|
-
height: 60svh;
|
|
551
|
+
height: max(60svh, min(30rem, 100svh - 10rem));
|
|
485
552
|
}
|
|
486
553
|
|
|
487
554
|
.drawer-handle-bar {
|
|
@@ -524,6 +591,25 @@
|
|
|
524
591
|
.drawer-footer {
|
|
525
592
|
flex-shrink: 0;
|
|
526
593
|
padding: 0rem 1rem;
|
|
527
|
-
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/* Touch gets 44px targets on the panel's controls; a fine pointer keeps the denser sizes. */
|
|
597
|
+
@media (pointer: coarse) {
|
|
598
|
+
.panel-content-wrapper
|
|
599
|
+
:global(
|
|
600
|
+
:is(
|
|
601
|
+
[data-slot='button'],
|
|
602
|
+
[data-slot='input'],
|
|
603
|
+
[data-slot='select-trigger'],
|
|
604
|
+
[data-slot='input-group']
|
|
605
|
+
)
|
|
606
|
+
),
|
|
607
|
+
.drawer-footer :global([data-slot='button']) {
|
|
608
|
+
min-height: 2.75rem;
|
|
609
|
+
}
|
|
610
|
+
.panel-content-wrapper :global([data-slot='input-group'] > [data-slot='input-group-control']) {
|
|
611
|
+
align-self: stretch;
|
|
612
|
+
height: auto;
|
|
613
|
+
}
|
|
528
614
|
}
|
|
529
615
|
</style>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { UISchema, SupportedTypes, ParameterPreset } from '@selvajs/schemas';
|
|
2
|
+
import type { SolveSettlement } from '@selvajs/solve/client';
|
|
2
3
|
import type { ActionButton } from '../../types/actionButton';
|
|
3
4
|
import type { PresetLabels } from '../../types/presetLabels';
|
|
4
5
|
import type { ThreeViewer } from '@selvajs/visualization/render';
|
|
@@ -12,6 +13,11 @@ interface Props {
|
|
|
12
13
|
hasNeverSolved?: boolean;
|
|
13
14
|
isViewerFullscreen?: boolean;
|
|
14
15
|
oncalculate?: () => void;
|
|
16
|
+
/**
|
|
17
|
+
* The result for the current inputs, solving if it must (`session.ensureSolved`). Needed
|
|
18
|
+
* by `solvesFirst` actions; without it they run straight away.
|
|
19
|
+
*/
|
|
20
|
+
ensureSolved?: () => Promise<SolveSettlement>;
|
|
15
21
|
values: Record<string, unknown>;
|
|
16
22
|
onValueChange: (id: string, val: SupportedTypes, forceSolve?: boolean) => void | Promise<void>;
|
|
17
23
|
onLoadValues?: (values: Record<string, unknown>) => void | Promise<void>;
|
|
@@ -7,7 +7,12 @@
|
|
|
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 {
|
|
10
|
+
import type {
|
|
11
|
+
RetainedSolveResult,
|
|
12
|
+
SolveEventSource,
|
|
13
|
+
SolveSession,
|
|
14
|
+
SolveSettlement
|
|
15
|
+
} from '@selvajs/solve/client';
|
|
11
16
|
import SolveMessages from './SolveMessages.svelte';
|
|
12
17
|
import { meshPolicy } from '@selvajs/visualization/parse';
|
|
13
18
|
import type { ThreeViewer } from '@selvajs/visualization/render';
|
|
@@ -19,8 +24,8 @@
|
|
|
19
24
|
import { type ViewerConfig } from '../viewer/Viewer.svelte';
|
|
20
25
|
import StateDisplay from '../primitives/StateDisplay.svelte';
|
|
21
26
|
import { setClientSlot, type ClientSlot } from '../../contexts/clientSlotContext.svelte';
|
|
22
|
-
import type { Locale } from '../../i18n/messages';
|
|
23
|
-
import {
|
|
27
|
+
import type { Locale, ViewerMessages } from '../../i18n/messages';
|
|
28
|
+
import { layerLocaleContext } from '../../i18n/localeContext.svelte';
|
|
24
29
|
|
|
25
30
|
import type { Snippet } from 'svelte';
|
|
26
31
|
|
|
@@ -34,8 +39,11 @@
|
|
|
34
39
|
isEmbedded?: boolean;
|
|
35
40
|
primaryColor?: string;
|
|
36
41
|
showModeToggle?: boolean;
|
|
42
|
+
/** Host actions, listed in the panel footer's ⋯ menu after saving and loading inputs. */
|
|
37
43
|
panelActions?: ActionButton[];
|
|
44
|
+
/** Lists "Save inputs…" in the footer menu. */
|
|
38
45
|
showSaveButton?: boolean;
|
|
46
|
+
/** Lists "Load inputs…" in the footer menu. */
|
|
39
47
|
showLoadButton?: boolean;
|
|
40
48
|
/** When set, persist saved states via this callback instead of downloading a .slvp file. */
|
|
41
49
|
onSaveState?: (state: ParameterPreset) => void | Promise<void>;
|
|
@@ -74,6 +82,12 @@
|
|
|
74
82
|
*/
|
|
75
83
|
getSession: () => SolveSession;
|
|
76
84
|
}) => void;
|
|
85
|
+
/**
|
|
86
|
+
* Once per settled solve: applied, discarded, blocked, aborted or failed. A result held
|
|
87
|
+
* for review counts when the user accepts or rejects it, and one a queued solve replaces
|
|
88
|
+
* never does. Auto-solves from input changes count too.
|
|
89
|
+
*/
|
|
90
|
+
onSolved?: (settlement: SolveSettlement) => void;
|
|
77
91
|
/** Hands the live three.js viewer to the host once it mounts. See `Viewer.svelte` for the contract. */
|
|
78
92
|
onViewerReady?: (viewer: ThreeViewer) => void | (() => void);
|
|
79
93
|
/** Viewer chrome and defaults. `backgroundColor` and `showSceneManager` are set by the layout. */
|
|
@@ -92,6 +106,14 @@
|
|
|
92
106
|
// Language for the app's own chrome, English when unset. Does not translate
|
|
93
107
|
// schema-authored labels or Grasshopper-sourced names/metadata.
|
|
94
108
|
lang?: Locale;
|
|
109
|
+
/**
|
|
110
|
+
* Replaces individual strings of the app's own chrome, layered over the `lang` catalog
|
|
111
|
+
* (`VIEWER_MESSAGES`): reword a label, or supply every string for a language with no
|
|
112
|
+
* built-in catalog. Reaches every component inside, the viewer included. A `{name}`
|
|
113
|
+
* placeholder in the default is filled the same way in an override. The preset manager's
|
|
114
|
+
* strings come from `presetLabels` instead.
|
|
115
|
+
*/
|
|
116
|
+
messages?: Partial<ViewerMessages>;
|
|
95
117
|
}
|
|
96
118
|
|
|
97
119
|
let {
|
|
@@ -123,22 +145,26 @@
|
|
|
123
145
|
brandName,
|
|
124
146
|
header,
|
|
125
147
|
onReady,
|
|
148
|
+
onSolved,
|
|
126
149
|
onViewerReady,
|
|
127
150
|
viewerConfig = {},
|
|
128
151
|
externalScopeKey,
|
|
129
152
|
clientSlot,
|
|
130
|
-
lang
|
|
153
|
+
lang,
|
|
154
|
+
messages
|
|
131
155
|
}: Props = $props();
|
|
132
156
|
|
|
133
157
|
// Reaches InputControl deep in the tree.
|
|
134
158
|
// svelte-ignore state_referenced_locally
|
|
135
159
|
setClientSlot(clientSlot);
|
|
136
160
|
|
|
137
|
-
// Resolution: explicit `lang` → host-provided locale → English
|
|
138
|
-
// switching
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
161
|
+
// Resolution: explicit `lang` → host-provided locale → English, with `messages` on top.
|
|
162
|
+
// Getters, so switching either updates the chrome live.
|
|
163
|
+
const locale = layerLocaleContext(
|
|
164
|
+
() => lang,
|
|
165
|
+
() => messages
|
|
166
|
+
);
|
|
167
|
+
const t = $derived(locale.messages);
|
|
142
168
|
|
|
143
169
|
const resolvedScopeKey = $derived(externalScopeKey || definitionKey || schema?.id || '');
|
|
144
170
|
|
|
@@ -168,6 +194,9 @@
|
|
|
168
194
|
|
|
169
195
|
const solvingIndicator = createSolvingIndicator(() => session.isSolving);
|
|
170
196
|
|
|
197
|
+
// Read at call time, so a host swapping the callback needs no resubscribe.
|
|
198
|
+
$effect(() => session.onSettled((settlement) => onSolved?.(settlement)));
|
|
199
|
+
|
|
171
200
|
$effect(() => {
|
|
172
201
|
onReady?.({
|
|
173
202
|
loadValues: (incoming) => session.loadValues(incoming),
|
|
@@ -272,6 +301,7 @@
|
|
|
272
301
|
{presetLabels}
|
|
273
302
|
onValueChange={handleValueChange}
|
|
274
303
|
oncalculate={handleCalculate}
|
|
304
|
+
ensureSolved={() => session.ensureSolved()}
|
|
275
305
|
onLoadValues={() => session.loadValues({})}
|
|
276
306
|
/>
|
|
277
307
|
{/key}
|
|
@@ -286,15 +316,4 @@
|
|
|
286
316
|
onabort={() => session.abort()}
|
|
287
317
|
onconfirm={() => session.acknowledge()}
|
|
288
318
|
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
319
|
/>
|
|
@@ -2,11 +2,11 @@ import type { UISchema, ParameterPreset } from '@selvajs/schemas';
|
|
|
2
2
|
import type { ActionButton } from '../../types/actionButton';
|
|
3
3
|
import type { SolveFn } from '@selvajs/solve/shared';
|
|
4
4
|
import type { PresetLabels } from '../../types/presetLabels';
|
|
5
|
-
import type { RetainedSolveResult, SolveEventSource, SolveSession } from '@selvajs/solve/client';
|
|
5
|
+
import type { RetainedSolveResult, SolveEventSource, SolveSession, SolveSettlement } from '@selvajs/solve/client';
|
|
6
6
|
import type { ThreeViewer } from '@selvajs/visualization/render';
|
|
7
7
|
import { type ViewerConfig } from '../viewer/Viewer.svelte';
|
|
8
8
|
import { type ClientSlot } from '../../contexts/clientSlotContext.svelte';
|
|
9
|
-
import type { Locale } from '../../i18n/messages';
|
|
9
|
+
import type { Locale, ViewerMessages } from '../../i18n/messages';
|
|
10
10
|
import type { Snippet } from 'svelte';
|
|
11
11
|
interface Props {
|
|
12
12
|
schema: UISchema;
|
|
@@ -18,8 +18,11 @@ interface Props {
|
|
|
18
18
|
isEmbedded?: boolean;
|
|
19
19
|
primaryColor?: string;
|
|
20
20
|
showModeToggle?: boolean;
|
|
21
|
+
/** Host actions, listed in the panel footer's ⋯ menu after saving and loading inputs. */
|
|
21
22
|
panelActions?: ActionButton[];
|
|
23
|
+
/** Lists "Save inputs…" in the footer menu. */
|
|
22
24
|
showSaveButton?: boolean;
|
|
25
|
+
/** Lists "Load inputs…" in the footer menu. */
|
|
23
26
|
showLoadButton?: boolean;
|
|
24
27
|
/** When set, persist saved states via this callback instead of downloading a .slvp file. */
|
|
25
28
|
onSaveState?: (state: ParameterPreset) => void | Promise<void>;
|
|
@@ -57,6 +60,12 @@ interface Props {
|
|
|
57
60
|
*/
|
|
58
61
|
getSession: () => SolveSession;
|
|
59
62
|
}) => void;
|
|
63
|
+
/**
|
|
64
|
+
* Once per settled solve: applied, discarded, blocked, aborted or failed. A result held
|
|
65
|
+
* for review counts when the user accepts or rejects it, and one a queued solve replaces
|
|
66
|
+
* never does. Auto-solves from input changes count too.
|
|
67
|
+
*/
|
|
68
|
+
onSolved?: (settlement: SolveSettlement) => void;
|
|
60
69
|
/** Hands the live three.js viewer to the host once it mounts. See `Viewer.svelte` for the contract. */
|
|
61
70
|
onViewerReady?: (viewer: ThreeViewer) => void | (() => void);
|
|
62
71
|
/** Viewer chrome and defaults. `backgroundColor` and `showSceneManager` are set by the layout. */
|
|
@@ -69,6 +78,14 @@ interface Props {
|
|
|
69
78
|
externalScopeKey?: string;
|
|
70
79
|
clientSlot?: ClientSlot;
|
|
71
80
|
lang?: Locale;
|
|
81
|
+
/**
|
|
82
|
+
* Replaces individual strings of the app's own chrome, layered over the `lang` catalog
|
|
83
|
+
* (`VIEWER_MESSAGES`): reword a label, or supply every string for a language with no
|
|
84
|
+
* built-in catalog. Reaches every component inside, the viewer included. A `{name}`
|
|
85
|
+
* placeholder in the default is filled the same way in an override. The preset manager's
|
|
86
|
+
* strings come from `presetLabels` instead.
|
|
87
|
+
*/
|
|
88
|
+
messages?: Partial<ViewerMessages>;
|
|
72
89
|
}
|
|
73
90
|
declare const ComputeApp: import("svelte").Component<Props, {}, "">;
|
|
74
91
|
type ComputeApp = ReturnType<typeof ComputeApp>;
|