@selvajs/ui 4.8.0 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/compute/AppLayout.svelte +8 -4
- package/dist/components/compute/CollapsedPanelStrip.svelte +8 -1
- package/dist/components/compute/ComputeApp.svelte +19 -2
- package/dist/components/compute/ComputeApp.svelte.d.ts +8 -0
- package/dist/components/preview/InputControl.svelte +2 -1
- package/dist/components/preview/TabContent.svelte +5 -1
- package/dist/components/primitives/CalculateButton.svelte +8 -4
- package/dist/components/primitives/SolvingIndicator.svelte +7 -2
- package/dist/contexts/clientSlotContext.svelte.d.ts +8 -0
- package/dist/i18n/messages.d.ts +16 -0
- package/dist/i18n/messages.js +32 -2
- package/dist/public.js +2 -2
- package/package.json +3 -3
- package/src/lib/components/compute/AppLayout.svelte +8 -4
- package/src/lib/components/compute/CollapsedPanelStrip.svelte +8 -1
- package/src/lib/components/compute/ComputeApp.svelte +19 -2
- package/src/lib/components/preview/InputControl.svelte +2 -1
- package/src/lib/components/preview/TabContent.svelte +5 -1
- package/src/lib/components/primitives/CalculateButton.svelte +8 -4
- package/src/lib/components/primitives/SolvingIndicator.svelte +7 -2
- package/src/lib/contexts/clientSlotContext.svelte.ts +12 -2
- package/src/lib/i18n/messages.ts +56 -2
- package/src/lib/public.ts +3 -3
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
import * as Resizable from '../primitives/resizable';
|
|
12
12
|
import { Button } from '../..';
|
|
13
13
|
import ParameterPresetManager from './ParameterPresetManager.svelte';
|
|
14
|
+
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
15
|
+
|
|
16
|
+
const locale = getLocaleContext();
|
|
17
|
+
const t = $derived(locale.messages);
|
|
14
18
|
|
|
15
19
|
const COLLAPSED_WIDTH = 48;
|
|
16
20
|
|
|
@@ -74,7 +78,7 @@
|
|
|
74
78
|
let isMobile = $state(false);
|
|
75
79
|
let drawerOpen = $state(false);
|
|
76
80
|
|
|
77
|
-
const activeLeftTabLabel = $derived(leftTabs[0]?.label ??
|
|
81
|
+
const activeLeftTabLabel = $derived(leftTabs[0]?.label ?? t.parametersTab);
|
|
78
82
|
|
|
79
83
|
$effect(() => {
|
|
80
84
|
const mqMobile = window.matchMedia('(max-width: 639px)');
|
|
@@ -200,7 +204,7 @@
|
|
|
200
204
|
onclick={() => (drawerOpen = !drawerOpen)}
|
|
201
205
|
ontouchstart={onDrawerTouchStart}
|
|
202
206
|
ontouchend={onDrawerTouchEnd}
|
|
203
|
-
aria-label={drawerOpen ?
|
|
207
|
+
aria-label={drawerOpen ? t.collapsePanel : t.expandPanel}
|
|
204
208
|
aria-expanded={drawerOpen}
|
|
205
209
|
>
|
|
206
210
|
<div class="drawer-pill"></div>
|
|
@@ -227,7 +231,7 @@
|
|
|
227
231
|
<div class="my-4 gap-2 flex items-center">
|
|
228
232
|
<div class="h-px flex-1 bg-border"></div>
|
|
229
233
|
<span class="text-xs font-medium px-1 text-muted-foreground">
|
|
230
|
-
{rightTabs[0]?.label ??
|
|
234
|
+
{rightTabs[0]?.label ?? t.moreTab}
|
|
231
235
|
</span>
|
|
232
236
|
<div class="h-px flex-1 bg-border"></div>
|
|
233
237
|
</div>
|
|
@@ -257,7 +261,7 @@
|
|
|
257
261
|
<div class="my-4 gap-2 flex items-center">
|
|
258
262
|
<div class="h-px flex-1 bg-border"></div>
|
|
259
263
|
<span class="text-xs font-medium px-1 text-muted-foreground">
|
|
260
|
-
{rightTabs[0]?.label ??
|
|
264
|
+
{rightTabs[0]?.label ?? t.moreTab}
|
|
261
265
|
</span>
|
|
262
266
|
<div class="h-px flex-1 bg-border"></div>
|
|
263
267
|
</div>
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { TabConfig } from '@selvajs/schemas';
|
|
3
3
|
import Icon from '@iconify/svelte';
|
|
4
|
+
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
5
|
+
|
|
6
|
+
const locale = getLocaleContext();
|
|
7
|
+
const t = $derived(locale.messages);
|
|
4
8
|
|
|
5
9
|
interface Props {
|
|
6
10
|
side: 'left' | 'right';
|
|
@@ -29,7 +33,10 @@
|
|
|
29
33
|
style="--collapsed-w: {collapsedWidth}px;"
|
|
30
34
|
role="button"
|
|
31
35
|
tabindex="0"
|
|
32
|
-
aria-label=
|
|
36
|
+
aria-label={t.expandSidePanel.replace(
|
|
37
|
+
'{side}',
|
|
38
|
+
side === 'left' ? t.panelSideLeft : t.panelSideRight
|
|
39
|
+
)}
|
|
33
40
|
onclick={onExpand}
|
|
34
41
|
onkeydown={(e) => (e.key === 'Enter' || e.key === ' ') && onExpand()}
|
|
35
42
|
>
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
import AppLayout from './AppLayout.svelte';
|
|
17
17
|
import StateDisplay from '../primitives/StateDisplay.svelte';
|
|
18
18
|
import { setClientSlot, type ClientSlot } from '../../contexts/clientSlotContext.svelte';
|
|
19
|
+
import type { Locale } from '../../i18n/messages';
|
|
20
|
+
import { setLocaleContext, getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
19
21
|
|
|
20
22
|
import type { Snippet } from 'svelte';
|
|
21
23
|
|
|
@@ -54,6 +56,13 @@
|
|
|
54
56
|
externalScopeKey?: string;
|
|
55
57
|
// Renders client-sourced inputs with presentation === 'slot'; receives { inputId, displayName, slotLabel, value }.
|
|
56
58
|
clientSlot?: ClientSlot;
|
|
59
|
+
/**
|
|
60
|
+
* UI language for the app's own chrome (viewer, panels, status text).
|
|
61
|
+
* Provided once here and read by every descendant via locale context.
|
|
62
|
+
* Defaults to English when unset. Does not translate schema-authored labels
|
|
63
|
+
* or Grasshopper-sourced names/metadata.
|
|
64
|
+
*/
|
|
65
|
+
lang?: Locale;
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
let {
|
|
@@ -81,13 +90,21 @@
|
|
|
81
90
|
header,
|
|
82
91
|
onReady,
|
|
83
92
|
externalScopeKey,
|
|
84
|
-
clientSlot
|
|
93
|
+
clientSlot,
|
|
94
|
+
lang
|
|
85
95
|
}: Props = $props();
|
|
86
96
|
|
|
87
97
|
// Make the host's client-input slot available to InputControl deep in the tree.
|
|
88
98
|
// svelte-ignore state_referenced_locally
|
|
89
99
|
setClientSlot(clientSlot);
|
|
90
100
|
|
|
101
|
+
// Provide the UI locale once for the whole app subtree (viewer, panels, status
|
|
102
|
+
// text). Resolution: explicit `lang` → any host-provided locale → English. The
|
|
103
|
+
// getter is re-read reactively, so switching `lang` updates the chrome live.
|
|
104
|
+
const hostLocale = getLocaleContext();
|
|
105
|
+
setLocaleContext(() => lang ?? hostLocale.locale);
|
|
106
|
+
const t = $derived(getLocaleContext().messages);
|
|
107
|
+
|
|
91
108
|
const resolvedScopeKey = $derived(externalScopeKey || definitionKey || schema?.id || '');
|
|
92
109
|
|
|
93
110
|
// Solve Session owns the value/lifecycle state machine; the request/response driver
|
|
@@ -182,7 +199,7 @@
|
|
|
182
199
|
</div>
|
|
183
200
|
{:else if !schema}
|
|
184
201
|
<div class="min-h-100 flex items-center justify-center">
|
|
185
|
-
<StateDisplay type="loading" size="large" message=
|
|
202
|
+
<StateDisplay type="loading" size="large" message={t.loadingSchema} />
|
|
186
203
|
</div>
|
|
187
204
|
{:else}
|
|
188
205
|
{#key definitionKey}
|
|
@@ -3,6 +3,7 @@ import type { ActionButton } from '../../types/actionButton';
|
|
|
3
3
|
import type { SolveFn } from '../../types/solveFn';
|
|
4
4
|
import type { PresetLabels } from '../../types/presetLabels';
|
|
5
5
|
import { type ClientSlot } from '../../contexts/clientSlotContext.svelte';
|
|
6
|
+
import type { Locale } from '../../i18n/messages';
|
|
6
7
|
import type { Snippet } from 'svelte';
|
|
7
8
|
interface Props {
|
|
8
9
|
schema: UISchema;
|
|
@@ -38,6 +39,13 @@ interface Props {
|
|
|
38
39
|
header?: Snippet;
|
|
39
40
|
externalScopeKey?: string;
|
|
40
41
|
clientSlot?: ClientSlot;
|
|
42
|
+
/**
|
|
43
|
+
* UI language for the app's own chrome (viewer, panels, status text).
|
|
44
|
+
* Provided once here and read by every descendant via locale context.
|
|
45
|
+
* Defaults to English when unset. Does not translate schema-authored labels
|
|
46
|
+
* or Grasshopper-sourced names/metadata.
|
|
47
|
+
*/
|
|
48
|
+
lang?: Locale;
|
|
41
49
|
}
|
|
42
50
|
declare const ComputeApp: import("svelte").Component<Props, {}, "">;
|
|
43
51
|
type ComputeApp = ReturnType<typeof ComputeApp>;
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
import InputControl from './InputControl.svelte';
|
|
15
15
|
import OutputDisplay from './OutputDisplay.svelte';
|
|
16
16
|
import { evaluateGroupVisibility } from '../../schema/visibility-rules';
|
|
17
|
+
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
18
|
+
|
|
19
|
+
const locale = getLocaleContext();
|
|
20
|
+
const t = $derived(locale.messages);
|
|
17
21
|
|
|
18
22
|
interface Props {
|
|
19
23
|
tab: TabConfig;
|
|
@@ -79,7 +83,7 @@
|
|
|
79
83
|
<ScrollArea class="h-full" orientation="vertical">
|
|
80
84
|
<div class="p-4 tab-content-container">
|
|
81
85
|
{#if tab.groups.length === 0}
|
|
82
|
-
<StateDisplay type="empty" size="medium" message=
|
|
86
|
+
<StateDisplay type="empty" size="medium" message={t.tabNoGroups} />
|
|
83
87
|
{:else}
|
|
84
88
|
<div class="gap-8 flex flex-col">
|
|
85
89
|
{#each tab.groups as group (group.id)}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Button } from './button';
|
|
3
3
|
import { Zap, Check, Loader, Play } from '@lucide/svelte';
|
|
4
|
+
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
5
|
+
|
|
6
|
+
const locale = getLocaleContext();
|
|
7
|
+
const t = $derived(locale.messages);
|
|
4
8
|
|
|
5
9
|
interface Props {
|
|
6
10
|
hasPendingChanges: boolean;
|
|
@@ -25,16 +29,16 @@
|
|
|
25
29
|
>
|
|
26
30
|
{#if isSolving}
|
|
27
31
|
<Loader class="h-4 w-4 animate-spin" />
|
|
28
|
-
|
|
32
|
+
{t.solvingEllipsis}
|
|
29
33
|
{:else if hasNeverSolved}
|
|
30
34
|
<Play class="h-4 w-4" />
|
|
31
|
-
|
|
35
|
+
{t.pressToCalculate}
|
|
32
36
|
{:else if hasPendingChanges}
|
|
33
37
|
<Zap class="h-4 w-4" />
|
|
34
|
-
|
|
38
|
+
{t.calculate}
|
|
35
39
|
{:else}
|
|
36
40
|
<Check class="h-4 w-4" />
|
|
37
|
-
|
|
41
|
+
{t.upToDate}
|
|
38
42
|
{/if}
|
|
39
43
|
</Button>
|
|
40
44
|
</div>
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
3
|
+
|
|
2
4
|
interface Props {
|
|
3
5
|
show: boolean;
|
|
4
6
|
}
|
|
5
7
|
|
|
6
8
|
let { show }: Props = $props();
|
|
9
|
+
|
|
10
|
+
const locale = getLocaleContext();
|
|
11
|
+
const t = $derived(locale.messages);
|
|
7
12
|
</script>
|
|
8
13
|
|
|
9
14
|
{#if show}
|
|
@@ -19,8 +24,8 @@
|
|
|
19
24
|
></div>
|
|
20
25
|
</div>
|
|
21
26
|
<div class="leading-tight flex flex-col">
|
|
22
|
-
<span class="text-sm font-semibold">
|
|
23
|
-
<span class="text-xs opacity-70">
|
|
27
|
+
<span class="text-sm font-semibold">{t.solving}</span>
|
|
28
|
+
<span class="text-xs opacity-70">{t.solvingSubtitle}</span>
|
|
24
29
|
</div>
|
|
25
30
|
</div>
|
|
26
31
|
{/if}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { SupportedTypes } from '@selvajs/schemas';
|
|
2
3
|
export interface ClientSlotArgs {
|
|
3
4
|
/** Grasshopper parameter instance GUID (LayoutItem.paramId / SchemaInput.id). */
|
|
4
5
|
inputId: string;
|
|
@@ -7,6 +8,13 @@ export interface ClientSlotArgs {
|
|
|
7
8
|
slotLabel?: string;
|
|
8
9
|
/** The current value held for this input (e.g. the prefilled JSON), if any. */
|
|
9
10
|
value: unknown;
|
|
11
|
+
/**
|
|
12
|
+
* Commit a value for this input. Identical channel to a built-in widget's change
|
|
13
|
+
* — the value lands in the solve session and is sent to Compute on the next solve.
|
|
14
|
+
* `forceSolve` requests a solve even in manual-solve mode (system reconciliation).
|
|
15
|
+
* Lets a slot be an interactive control (a custom picker), not just a display cell.
|
|
16
|
+
*/
|
|
17
|
+
onValueChange: (value: SupportedTypes, forceSolve?: boolean) => void;
|
|
10
18
|
}
|
|
11
19
|
export type ClientSlot = Snippet<[ClientSlotArgs]>;
|
|
12
20
|
export declare function setClientSlot(slot: ClientSlot | undefined): void;
|
package/dist/i18n/messages.d.ts
CHANGED
|
@@ -31,6 +31,22 @@ export interface ViewerMessages {
|
|
|
31
31
|
noResultsFor: string;
|
|
32
32
|
objectFallbackName: string;
|
|
33
33
|
noMetadata: string;
|
|
34
|
+
loadingSchema: string;
|
|
35
|
+
parametersTab: string;
|
|
36
|
+
moreTab: string;
|
|
37
|
+
expandPanel: string;
|
|
38
|
+
collapsePanel: string;
|
|
39
|
+
/** `{side}` is replaced with `panelSideLeft` / `panelSideRight`. */
|
|
40
|
+
expandSidePanel: string;
|
|
41
|
+
panelSideLeft: string;
|
|
42
|
+
panelSideRight: string;
|
|
43
|
+
tabNoGroups: string;
|
|
44
|
+
solving: string;
|
|
45
|
+
solvingEllipsis: string;
|
|
46
|
+
solvingSubtitle: string;
|
|
47
|
+
pressToCalculate: string;
|
|
48
|
+
calculate: string;
|
|
49
|
+
upToDate: string;
|
|
34
50
|
}
|
|
35
51
|
export declare const VIEWER_MESSAGES: Record<Locale, ViewerMessages>;
|
|
36
52
|
export declare const DEFAULT_LOCALE: Locale;
|
package/dist/i18n/messages.js
CHANGED
|
@@ -40,7 +40,22 @@ const en = {
|
|
|
40
40
|
noObjects: 'No objects',
|
|
41
41
|
noResultsFor: 'No results for "{query}"',
|
|
42
42
|
objectFallbackName: 'Object',
|
|
43
|
-
noMetadata: 'No metadata'
|
|
43
|
+
noMetadata: 'No metadata',
|
|
44
|
+
loadingSchema: 'Loading schema...',
|
|
45
|
+
parametersTab: 'Parameters',
|
|
46
|
+
moreTab: 'More',
|
|
47
|
+
expandPanel: 'Expand panel',
|
|
48
|
+
collapsePanel: 'Collapse panel',
|
|
49
|
+
expandSidePanel: 'Expand {side} panel',
|
|
50
|
+
panelSideLeft: 'left',
|
|
51
|
+
panelSideRight: 'right',
|
|
52
|
+
tabNoGroups: 'This tab has no groups configured.',
|
|
53
|
+
solving: 'Solving',
|
|
54
|
+
solvingEllipsis: 'Solving...',
|
|
55
|
+
solvingSubtitle: 'Grasshopper is calculating...',
|
|
56
|
+
pressToCalculate: 'Press to calculate',
|
|
57
|
+
calculate: 'Calculate',
|
|
58
|
+
upToDate: 'Up to date'
|
|
44
59
|
};
|
|
45
60
|
const de = {
|
|
46
61
|
toolsMenu: 'Viewer-Werkzeuge',
|
|
@@ -72,7 +87,22 @@ const de = {
|
|
|
72
87
|
noObjects: 'Keine Objekte',
|
|
73
88
|
noResultsFor: 'Keine Ergebnisse für „{query}“',
|
|
74
89
|
objectFallbackName: 'Objekt',
|
|
75
|
-
noMetadata: 'Keine Metadaten'
|
|
90
|
+
noMetadata: 'Keine Metadaten',
|
|
91
|
+
loadingSchema: 'Schema wird geladen...',
|
|
92
|
+
parametersTab: 'Parameter',
|
|
93
|
+
moreTab: 'Mehr',
|
|
94
|
+
expandPanel: 'Bereich aufklappen',
|
|
95
|
+
collapsePanel: 'Bereich zuklappen',
|
|
96
|
+
expandSidePanel: '{side} Bereich aufklappen',
|
|
97
|
+
panelSideLeft: 'Linken',
|
|
98
|
+
panelSideRight: 'Rechten',
|
|
99
|
+
tabNoGroups: 'Für diesen Tab sind keine Gruppen konfiguriert.',
|
|
100
|
+
solving: 'Berechnung läuft',
|
|
101
|
+
solvingEllipsis: 'Berechnung läuft...',
|
|
102
|
+
solvingSubtitle: 'Grasshopper berechnet...',
|
|
103
|
+
pressToCalculate: 'Zum Berechnen klicken',
|
|
104
|
+
calculate: 'Berechnen',
|
|
105
|
+
upToDate: 'Aktuell'
|
|
76
106
|
};
|
|
77
107
|
export const VIEWER_MESSAGES = { en, de };
|
|
78
108
|
export const DEFAULT_LOCALE = 'en';
|
package/dist/public.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
//
|
|
8
8
|
// Scope: the compute-app SDK — everything an external host app needs to embed a
|
|
9
9
|
// Grasshopper-driven app (ComputeApp), drive solves, and wire pre-step
|
|
10
|
-
// producers. Verified against real
|
|
10
|
+
// producers. Verified against real external host apps: they import
|
|
11
11
|
// ComputeApp + its types, the solve seam, and external/storage. Nothing else.
|
|
12
12
|
//
|
|
13
13
|
// Deliberately NOT public: design-system primitives (Button, Card, Dialog, …),
|
|
@@ -33,7 +33,7 @@ export { default as ErrorScreen } from './components/ErrorScreen.svelte';
|
|
|
33
33
|
// driver interface). Exported so transports outside this package can satisfy
|
|
34
34
|
// SolveDriver and drive a session. See CONTEXT.md.
|
|
35
35
|
export { createSolveSession, createRequestResponseDriver } from './compute/createSolveSession.svelte';
|
|
36
|
-
// Pre-step producer transit storage (
|
|
36
|
+
// Pre-step producer transit storage (host apps wire producers via these).
|
|
37
37
|
export * from './external/storage';
|
|
38
38
|
// Schema utilities a ComputeApp host reasonably needs to read/shape values.
|
|
39
39
|
export * from './schema/defaults';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selvajs/ui",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.10.0",
|
|
4
4
|
"description": "Shared UI components and utilities for Selva applications",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"svelte": "5.55.5",
|
|
73
73
|
"tailwind-variants": "^3.2.2",
|
|
74
74
|
"vitest": "^3.2.6",
|
|
75
|
-
"@selvajs/
|
|
76
|
-
"@selvajs/
|
|
75
|
+
"@selvajs/config": "0.0.1",
|
|
76
|
+
"@selvajs/schemas": "4.5.0"
|
|
77
77
|
},
|
|
78
78
|
"scripts": {
|
|
79
79
|
"dev": "vite dev",
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
import * as Resizable from '$lib/components/primitives/resizable';
|
|
12
12
|
import { Button } from '$lib';
|
|
13
13
|
import ParameterPresetManager from './ParameterPresetManager.svelte';
|
|
14
|
+
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
15
|
+
|
|
16
|
+
const locale = getLocaleContext();
|
|
17
|
+
const t = $derived(locale.messages);
|
|
14
18
|
|
|
15
19
|
const COLLAPSED_WIDTH = 48;
|
|
16
20
|
|
|
@@ -74,7 +78,7 @@
|
|
|
74
78
|
let isMobile = $state(false);
|
|
75
79
|
let drawerOpen = $state(false);
|
|
76
80
|
|
|
77
|
-
const activeLeftTabLabel = $derived(leftTabs[0]?.label ??
|
|
81
|
+
const activeLeftTabLabel = $derived(leftTabs[0]?.label ?? t.parametersTab);
|
|
78
82
|
|
|
79
83
|
$effect(() => {
|
|
80
84
|
const mqMobile = window.matchMedia('(max-width: 639px)');
|
|
@@ -200,7 +204,7 @@
|
|
|
200
204
|
onclick={() => (drawerOpen = !drawerOpen)}
|
|
201
205
|
ontouchstart={onDrawerTouchStart}
|
|
202
206
|
ontouchend={onDrawerTouchEnd}
|
|
203
|
-
aria-label={drawerOpen ?
|
|
207
|
+
aria-label={drawerOpen ? t.collapsePanel : t.expandPanel}
|
|
204
208
|
aria-expanded={drawerOpen}
|
|
205
209
|
>
|
|
206
210
|
<div class="drawer-pill"></div>
|
|
@@ -227,7 +231,7 @@
|
|
|
227
231
|
<div class="my-4 gap-2 flex items-center">
|
|
228
232
|
<div class="h-px flex-1 bg-border"></div>
|
|
229
233
|
<span class="text-xs font-medium px-1 text-muted-foreground">
|
|
230
|
-
{rightTabs[0]?.label ??
|
|
234
|
+
{rightTabs[0]?.label ?? t.moreTab}
|
|
231
235
|
</span>
|
|
232
236
|
<div class="h-px flex-1 bg-border"></div>
|
|
233
237
|
</div>
|
|
@@ -257,7 +261,7 @@
|
|
|
257
261
|
<div class="my-4 gap-2 flex items-center">
|
|
258
262
|
<div class="h-px flex-1 bg-border"></div>
|
|
259
263
|
<span class="text-xs font-medium px-1 text-muted-foreground">
|
|
260
|
-
{rightTabs[0]?.label ??
|
|
264
|
+
{rightTabs[0]?.label ?? t.moreTab}
|
|
261
265
|
</span>
|
|
262
266
|
<div class="h-px flex-1 bg-border"></div>
|
|
263
267
|
</div>
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { TabConfig } from '@selvajs/schemas';
|
|
3
3
|
import Icon from '@iconify/svelte';
|
|
4
|
+
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
5
|
+
|
|
6
|
+
const locale = getLocaleContext();
|
|
7
|
+
const t = $derived(locale.messages);
|
|
4
8
|
|
|
5
9
|
interface Props {
|
|
6
10
|
side: 'left' | 'right';
|
|
@@ -29,7 +33,10 @@
|
|
|
29
33
|
style="--collapsed-w: {collapsedWidth}px;"
|
|
30
34
|
role="button"
|
|
31
35
|
tabindex="0"
|
|
32
|
-
aria-label=
|
|
36
|
+
aria-label={t.expandSidePanel.replace(
|
|
37
|
+
'{side}',
|
|
38
|
+
side === 'left' ? t.panelSideLeft : t.panelSideRight
|
|
39
|
+
)}
|
|
33
40
|
onclick={onExpand}
|
|
34
41
|
onkeydown={(e) => (e.key === 'Enter' || e.key === ' ') && onExpand()}
|
|
35
42
|
>
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
import AppLayout from './AppLayout.svelte';
|
|
17
17
|
import StateDisplay from '../primitives/StateDisplay.svelte';
|
|
18
18
|
import { setClientSlot, type ClientSlot } from '../../contexts/clientSlotContext.svelte';
|
|
19
|
+
import type { Locale } from '../../i18n/messages';
|
|
20
|
+
import { setLocaleContext, getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
19
21
|
|
|
20
22
|
import type { Snippet } from 'svelte';
|
|
21
23
|
|
|
@@ -54,6 +56,13 @@
|
|
|
54
56
|
externalScopeKey?: string;
|
|
55
57
|
// Renders client-sourced inputs with presentation === 'slot'; receives { inputId, displayName, slotLabel, value }.
|
|
56
58
|
clientSlot?: ClientSlot;
|
|
59
|
+
/**
|
|
60
|
+
* UI language for the app's own chrome (viewer, panels, status text).
|
|
61
|
+
* Provided once here and read by every descendant via locale context.
|
|
62
|
+
* Defaults to English when unset. Does not translate schema-authored labels
|
|
63
|
+
* or Grasshopper-sourced names/metadata.
|
|
64
|
+
*/
|
|
65
|
+
lang?: Locale;
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
let {
|
|
@@ -81,13 +90,21 @@
|
|
|
81
90
|
header,
|
|
82
91
|
onReady,
|
|
83
92
|
externalScopeKey,
|
|
84
|
-
clientSlot
|
|
93
|
+
clientSlot,
|
|
94
|
+
lang
|
|
85
95
|
}: Props = $props();
|
|
86
96
|
|
|
87
97
|
// Make the host's client-input slot available to InputControl deep in the tree.
|
|
88
98
|
// svelte-ignore state_referenced_locally
|
|
89
99
|
setClientSlot(clientSlot);
|
|
90
100
|
|
|
101
|
+
// Provide the UI locale once for the whole app subtree (viewer, panels, status
|
|
102
|
+
// text). Resolution: explicit `lang` → any host-provided locale → English. The
|
|
103
|
+
// getter is re-read reactively, so switching `lang` updates the chrome live.
|
|
104
|
+
const hostLocale = getLocaleContext();
|
|
105
|
+
setLocaleContext(() => lang ?? hostLocale.locale);
|
|
106
|
+
const t = $derived(getLocaleContext().messages);
|
|
107
|
+
|
|
91
108
|
const resolvedScopeKey = $derived(externalScopeKey || definitionKey || schema?.id || '');
|
|
92
109
|
|
|
93
110
|
// Solve Session owns the value/lifecycle state machine; the request/response driver
|
|
@@ -182,7 +199,7 @@
|
|
|
182
199
|
</div>
|
|
183
200
|
{:else if !schema}
|
|
184
201
|
<div class="min-h-100 flex items-center justify-center">
|
|
185
|
-
<StateDisplay type="loading" size="large" message=
|
|
202
|
+
<StateDisplay type="loading" size="large" message={t.loadingSchema} />
|
|
186
203
|
</div>
|
|
187
204
|
{:else}
|
|
188
205
|
{#key definitionKey}
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
import InputControl from './InputControl.svelte';
|
|
15
15
|
import OutputDisplay from './OutputDisplay.svelte';
|
|
16
16
|
import { evaluateGroupVisibility } from '$lib/schema/visibility-rules';
|
|
17
|
+
import { getLocaleContext } from '$lib/i18n/localeContext.svelte';
|
|
18
|
+
|
|
19
|
+
const locale = getLocaleContext();
|
|
20
|
+
const t = $derived(locale.messages);
|
|
17
21
|
|
|
18
22
|
interface Props {
|
|
19
23
|
tab: TabConfig;
|
|
@@ -79,7 +83,7 @@
|
|
|
79
83
|
<ScrollArea class="h-full" orientation="vertical">
|
|
80
84
|
<div class="p-4 tab-content-container">
|
|
81
85
|
{#if tab.groups.length === 0}
|
|
82
|
-
<StateDisplay type="empty" size="medium" message=
|
|
86
|
+
<StateDisplay type="empty" size="medium" message={t.tabNoGroups} />
|
|
83
87
|
{:else}
|
|
84
88
|
<div class="gap-8 flex flex-col">
|
|
85
89
|
{#each tab.groups as group (group.id)}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Button } from './button';
|
|
3
3
|
import { Zap, Check, Loader, Play } from '@lucide/svelte';
|
|
4
|
+
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
5
|
+
|
|
6
|
+
const locale = getLocaleContext();
|
|
7
|
+
const t = $derived(locale.messages);
|
|
4
8
|
|
|
5
9
|
interface Props {
|
|
6
10
|
hasPendingChanges: boolean;
|
|
@@ -25,16 +29,16 @@
|
|
|
25
29
|
>
|
|
26
30
|
{#if isSolving}
|
|
27
31
|
<Loader class="h-4 w-4 animate-spin" />
|
|
28
|
-
|
|
32
|
+
{t.solvingEllipsis}
|
|
29
33
|
{:else if hasNeverSolved}
|
|
30
34
|
<Play class="h-4 w-4" />
|
|
31
|
-
|
|
35
|
+
{t.pressToCalculate}
|
|
32
36
|
{:else if hasPendingChanges}
|
|
33
37
|
<Zap class="h-4 w-4" />
|
|
34
|
-
|
|
38
|
+
{t.calculate}
|
|
35
39
|
{:else}
|
|
36
40
|
<Check class="h-4 w-4" />
|
|
37
|
-
|
|
41
|
+
{t.upToDate}
|
|
38
42
|
{/if}
|
|
39
43
|
</Button>
|
|
40
44
|
</div>
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
3
|
+
|
|
2
4
|
interface Props {
|
|
3
5
|
show: boolean;
|
|
4
6
|
}
|
|
5
7
|
|
|
6
8
|
let { show }: Props = $props();
|
|
9
|
+
|
|
10
|
+
const locale = getLocaleContext();
|
|
11
|
+
const t = $derived(locale.messages);
|
|
7
12
|
</script>
|
|
8
13
|
|
|
9
14
|
{#if show}
|
|
@@ -19,8 +24,8 @@
|
|
|
19
24
|
></div>
|
|
20
25
|
</div>
|
|
21
26
|
<div class="leading-tight flex flex-col">
|
|
22
|
-
<span class="text-sm font-semibold">
|
|
23
|
-
<span class="text-xs opacity-70">
|
|
27
|
+
<span class="text-sm font-semibold">{t.solving}</span>
|
|
28
|
+
<span class="text-xs opacity-70">{t.solvingSubtitle}</span>
|
|
24
29
|
</div>
|
|
25
30
|
</div>
|
|
26
31
|
{/if}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getContext, setContext } from 'svelte';
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { SupportedTypes } from '@selvajs/schemas';
|
|
3
4
|
|
|
4
5
|
// Carries the host app's slot renderer down to InputControl without threading a
|
|
5
6
|
// prop through every layout layer (ComputeApp → AppLayout → TabLayout →
|
|
@@ -7,8 +8,10 @@ import type { Snippet } from 'svelte';
|
|
|
7
8
|
//
|
|
8
9
|
// An input with source.kind === 'client' and source.client.presentation === 'slot'
|
|
9
10
|
// reserves its cell but renders nothing itself. Instead Selva invokes this snippet
|
|
10
|
-
// so the host can render its own element (e.g. an "Edit JSON" button
|
|
11
|
-
// interprets what the host renders; `slotLabel` is passed
|
|
11
|
+
// so the host can render its own element (e.g. an "Edit JSON" button, or a custom
|
|
12
|
+
// picker). Selva never interprets what the host renders; `slotLabel` is passed
|
|
13
|
+
// through untouched. The host may COMMIT a value back via `onValueChange`, which
|
|
14
|
+
// flows into the solve exactly like any built-in widget's change.
|
|
12
15
|
|
|
13
16
|
export interface ClientSlotArgs {
|
|
14
17
|
/** Grasshopper parameter instance GUID (LayoutItem.paramId / SchemaInput.id). */
|
|
@@ -18,6 +21,13 @@ export interface ClientSlotArgs {
|
|
|
18
21
|
slotLabel?: string;
|
|
19
22
|
/** The current value held for this input (e.g. the prefilled JSON), if any. */
|
|
20
23
|
value: unknown;
|
|
24
|
+
/**
|
|
25
|
+
* Commit a value for this input. Identical channel to a built-in widget's change
|
|
26
|
+
* — the value lands in the solve session and is sent to Compute on the next solve.
|
|
27
|
+
* `forceSolve` requests a solve even in manual-solve mode (system reconciliation).
|
|
28
|
+
* Lets a slot be an interactive control (a custom picker), not just a display cell.
|
|
29
|
+
*/
|
|
30
|
+
onValueChange: (value: SupportedTypes, forceSolve?: boolean) => void;
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
export type ClientSlot = Snippet<[ClientSlotArgs]>;
|
package/src/lib/i18n/messages.ts
CHANGED
|
@@ -52,6 +52,26 @@ export interface ViewerMessages {
|
|
|
52
52
|
// Metadata dialog
|
|
53
53
|
objectFallbackName: string;
|
|
54
54
|
noMetadata: string;
|
|
55
|
+
|
|
56
|
+
// App layout / compute shell
|
|
57
|
+
loadingSchema: string;
|
|
58
|
+
parametersTab: string;
|
|
59
|
+
moreTab: string;
|
|
60
|
+
expandPanel: string;
|
|
61
|
+
collapsePanel: string;
|
|
62
|
+
/** `{side}` is replaced with `panelSideLeft` / `panelSideRight`. */
|
|
63
|
+
expandSidePanel: string;
|
|
64
|
+
panelSideLeft: string;
|
|
65
|
+
panelSideRight: string;
|
|
66
|
+
tabNoGroups: string;
|
|
67
|
+
|
|
68
|
+
// Solve controls
|
|
69
|
+
solving: string;
|
|
70
|
+
solvingEllipsis: string;
|
|
71
|
+
solvingSubtitle: string;
|
|
72
|
+
pressToCalculate: string;
|
|
73
|
+
calculate: string;
|
|
74
|
+
upToDate: string;
|
|
55
75
|
}
|
|
56
76
|
|
|
57
77
|
const en: ViewerMessages = {
|
|
@@ -87,7 +107,24 @@ const en: ViewerMessages = {
|
|
|
87
107
|
noResultsFor: 'No results for "{query}"',
|
|
88
108
|
|
|
89
109
|
objectFallbackName: 'Object',
|
|
90
|
-
noMetadata: 'No metadata'
|
|
110
|
+
noMetadata: 'No metadata',
|
|
111
|
+
|
|
112
|
+
loadingSchema: 'Loading schema...',
|
|
113
|
+
parametersTab: 'Parameters',
|
|
114
|
+
moreTab: 'More',
|
|
115
|
+
expandPanel: 'Expand panel',
|
|
116
|
+
collapsePanel: 'Collapse panel',
|
|
117
|
+
expandSidePanel: 'Expand {side} panel',
|
|
118
|
+
panelSideLeft: 'left',
|
|
119
|
+
panelSideRight: 'right',
|
|
120
|
+
tabNoGroups: 'This tab has no groups configured.',
|
|
121
|
+
|
|
122
|
+
solving: 'Solving',
|
|
123
|
+
solvingEllipsis: 'Solving...',
|
|
124
|
+
solvingSubtitle: 'Grasshopper is calculating...',
|
|
125
|
+
pressToCalculate: 'Press to calculate',
|
|
126
|
+
calculate: 'Calculate',
|
|
127
|
+
upToDate: 'Up to date'
|
|
91
128
|
};
|
|
92
129
|
|
|
93
130
|
const de: ViewerMessages = {
|
|
@@ -123,7 +160,24 @@ const de: ViewerMessages = {
|
|
|
123
160
|
noResultsFor: 'Keine Ergebnisse für „{query}“',
|
|
124
161
|
|
|
125
162
|
objectFallbackName: 'Objekt',
|
|
126
|
-
noMetadata: 'Keine Metadaten'
|
|
163
|
+
noMetadata: 'Keine Metadaten',
|
|
164
|
+
|
|
165
|
+
loadingSchema: 'Schema wird geladen...',
|
|
166
|
+
parametersTab: 'Parameter',
|
|
167
|
+
moreTab: 'Mehr',
|
|
168
|
+
expandPanel: 'Bereich aufklappen',
|
|
169
|
+
collapsePanel: 'Bereich zuklappen',
|
|
170
|
+
expandSidePanel: '{side} Bereich aufklappen',
|
|
171
|
+
panelSideLeft: 'Linken',
|
|
172
|
+
panelSideRight: 'Rechten',
|
|
173
|
+
tabNoGroups: 'Für diesen Tab sind keine Gruppen konfiguriert.',
|
|
174
|
+
|
|
175
|
+
solving: 'Berechnung läuft',
|
|
176
|
+
solvingEllipsis: 'Berechnung läuft...',
|
|
177
|
+
solvingSubtitle: 'Grasshopper berechnet...',
|
|
178
|
+
pressToCalculate: 'Zum Berechnen klicken',
|
|
179
|
+
calculate: 'Berechnen',
|
|
180
|
+
upToDate: 'Aktuell'
|
|
127
181
|
};
|
|
128
182
|
|
|
129
183
|
export const VIEWER_MESSAGES: Record<Locale, ViewerMessages> = { en, de };
|
package/src/lib/public.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
//
|
|
8
8
|
// Scope: the compute-app SDK — everything an external host app needs to embed a
|
|
9
9
|
// Grasshopper-driven app (ComputeApp), drive solves, and wire pre-step
|
|
10
|
-
// producers. Verified against real
|
|
10
|
+
// producers. Verified against real external host apps: they import
|
|
11
11
|
// ComputeApp + its types, the solve seam, and external/storage. Nothing else.
|
|
12
12
|
//
|
|
13
13
|
// Deliberately NOT public: design-system primitives (Button, Card, Dialog, …),
|
|
@@ -56,10 +56,10 @@ export {
|
|
|
56
56
|
} from './compute/createSolveSession.svelte';
|
|
57
57
|
|
|
58
58
|
// Client-slot context type (host apps render their own cell for client-sourced
|
|
59
|
-
// inputs
|
|
59
|
+
// inputs, and may commit a value back via ClientSlotArgs.onValueChange).
|
|
60
60
|
export type { ClientSlotArgs, ClientSlot } from './contexts/clientSlotContext.svelte';
|
|
61
61
|
|
|
62
|
-
// Pre-step producer transit storage (
|
|
62
|
+
// Pre-step producer transit storage (host apps wire producers via these).
|
|
63
63
|
export * from './external/storage';
|
|
64
64
|
|
|
65
65
|
// Schema utilities a ComputeApp host reasonably needs to read/shape values.
|