@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.
Files changed (111) hide show
  1. package/dist/components/ErrorScreen.svelte +6 -2
  2. package/dist/components/compute/AppLayout.svelte +157 -71
  3. package/dist/components/compute/AppLayout.svelte.d.ts +6 -0
  4. package/dist/components/compute/ComputeApp.svelte +39 -20
  5. package/dist/components/compute/ComputeApp.svelte.d.ts +19 -2
  6. package/dist/components/compute/ComputeMessagesDialog.svelte +30 -40
  7. package/dist/components/compute/ParameterPresetManager.svelte +7 -44
  8. package/dist/components/compute/ParameterPresetManager.svelte.d.ts +4 -5
  9. package/dist/components/compute/SolveMessages.svelte +30 -37
  10. package/dist/components/compute/SolveMessages.svelte.d.ts +0 -12
  11. package/dist/components/layout/PageFooter.svelte +21 -11
  12. package/dist/components/preview/ChartOutput.svelte +12 -7
  13. package/dist/components/preview/Group.svelte +33 -18
  14. package/dist/components/preview/HelpButton.svelte +4 -2
  15. package/dist/components/preview/ImageOutput.svelte +427 -68
  16. package/dist/components/preview/InputControl.svelte +11 -2
  17. package/dist/components/preview/OutputDisplay.svelte +17 -12
  18. package/dist/components/preview/TabContent.svelte +3 -0
  19. package/dist/components/preview/inputs/ColorInput.svelte +3 -1
  20. package/dist/components/preview/inputs/ColorInput.svelte.d.ts +1 -0
  21. package/dist/components/preview/inputs/DropdownInput.svelte +4 -1
  22. package/dist/components/preview/inputs/FileInput.svelte +50 -34
  23. package/dist/components/preview/inputs/NumberInput.svelte +92 -10
  24. package/dist/components/preview/inputs/NumberInput.svelte.d.ts +2 -0
  25. package/dist/components/preview/inputs/TextInput.svelte +8 -3
  26. package/dist/components/primitives/CalculateButton.svelte +49 -23
  27. package/dist/components/primitives/CalculateButton.svelte.d.ts +3 -0
  28. package/dist/components/primitives/SolvingIndicator.svelte +21 -17
  29. package/dist/components/primitives/badge/badge.svelte +2 -2
  30. package/dist/components/primitives/badge/badge.svelte.d.ts +4 -4
  31. package/dist/components/primitives/button/button.svelte +2 -2
  32. package/dist/components/primitives/button/button.svelte.d.ts +4 -4
  33. package/dist/components/primitives/checkbox/checkbox.svelte +1 -1
  34. package/dist/components/primitives/dialog/dialog-content.svelte +6 -2
  35. package/dist/components/primitives/input/input.svelte +2 -2
  36. package/dist/components/primitives/input-group/input-group.svelte +1 -1
  37. package/dist/components/primitives/mode-toggle/mode-toggle.svelte +4 -1
  38. package/dist/components/primitives/scroll-area/scroll-area.svelte +1 -1
  39. package/dist/components/primitives/search/search.svelte +1 -1
  40. package/dist/components/primitives/select/select-trigger.svelte +1 -1
  41. package/dist/components/primitives/slider/slider.svelte +7 -2
  42. package/dist/components/primitives/slider/slider.svelte.d.ts +5 -1
  43. package/dist/components/primitives/switch/switch.svelte +1 -1
  44. package/dist/components/primitives/tabs/tabs-trigger.svelte +1 -1
  45. package/dist/components/primitives/tabs/tabs-trigger.svelte.d.ts +1 -1
  46. package/dist/components/primitives/textarea/textarea.svelte +1 -1
  47. package/dist/components/primitives/toggle/toggle.svelte +1 -1
  48. package/dist/components/primitives/toggle/toggle.svelte.d.ts +1 -1
  49. package/dist/components/viewer/Viewer.svelte +20 -17
  50. package/dist/compute/run-panel-action.d.ts +8 -0
  51. package/dist/compute/run-panel-action.js +12 -0
  52. package/dist/compute/useSolveSession.svelte.js +3 -0
  53. package/dist/i18n/localeContext.svelte.d.ts +10 -3
  54. package/dist/i18n/localeContext.svelte.js +27 -5
  55. package/dist/i18n/messages.d.ts +115 -2
  56. package/dist/i18n/messages.js +199 -11
  57. package/dist/index.d.ts +1 -1
  58. package/dist/public.d.ts +1 -1
  59. package/dist/schema/visibility-rules.js +21 -0
  60. package/dist/styles/base.css +9 -6
  61. package/dist/types/actionButton.d.ts +17 -3
  62. package/dist/types/presetLabels.d.ts +3 -2
  63. package/dist/types/presetLabels.js +9 -8
  64. package/package.json +4 -4
  65. package/src/lib/components/ErrorScreen.svelte +6 -2
  66. package/src/lib/components/compute/AppLayout.svelte +157 -71
  67. package/src/lib/components/compute/ComputeApp.svelte +39 -20
  68. package/src/lib/components/compute/ComputeMessagesDialog.svelte +30 -40
  69. package/src/lib/components/compute/ParameterPresetManager.svelte +7 -44
  70. package/src/lib/components/compute/SolveMessages.svelte +30 -37
  71. package/src/lib/components/layout/PageFooter.svelte +21 -11
  72. package/src/lib/components/preview/ChartOutput.svelte +12 -7
  73. package/src/lib/components/preview/Group.svelte +33 -18
  74. package/src/lib/components/preview/HelpButton.svelte +4 -2
  75. package/src/lib/components/preview/ImageOutput.svelte +427 -68
  76. package/src/lib/components/preview/InputControl.svelte +11 -2
  77. package/src/lib/components/preview/OutputDisplay.svelte +17 -12
  78. package/src/lib/components/preview/TabContent.svelte +3 -0
  79. package/src/lib/components/preview/inputs/ColorInput.svelte +3 -1
  80. package/src/lib/components/preview/inputs/DropdownInput.svelte +4 -1
  81. package/src/lib/components/preview/inputs/FileInput.svelte +50 -34
  82. package/src/lib/components/preview/inputs/NumberInput.svelte +92 -10
  83. package/src/lib/components/preview/inputs/TextInput.svelte +8 -3
  84. package/src/lib/components/primitives/CalculateButton.svelte +49 -23
  85. package/src/lib/components/primitives/SolvingIndicator.svelte +21 -17
  86. package/src/lib/components/primitives/badge/badge.svelte +2 -2
  87. package/src/lib/components/primitives/button/button.svelte +2 -2
  88. package/src/lib/components/primitives/checkbox/checkbox.svelte +1 -1
  89. package/src/lib/components/primitives/dialog/dialog-content.svelte +6 -2
  90. package/src/lib/components/primitives/input/input.svelte +2 -2
  91. package/src/lib/components/primitives/input-group/input-group.svelte +1 -1
  92. package/src/lib/components/primitives/mode-toggle/mode-toggle.svelte +4 -1
  93. package/src/lib/components/primitives/scroll-area/scroll-area.svelte +1 -1
  94. package/src/lib/components/primitives/search/search.svelte +1 -1
  95. package/src/lib/components/primitives/select/select-trigger.svelte +1 -1
  96. package/src/lib/components/primitives/slider/slider.svelte +7 -2
  97. package/src/lib/components/primitives/switch/switch.svelte +1 -1
  98. package/src/lib/components/primitives/tabs/tabs-trigger.svelte +1 -1
  99. package/src/lib/components/primitives/textarea/textarea.svelte +1 -1
  100. package/src/lib/components/primitives/toggle/toggle.svelte +1 -1
  101. package/src/lib/components/viewer/Viewer.svelte +20 -17
  102. package/src/lib/compute/run-panel-action.ts +16 -0
  103. package/src/lib/compute/useSolveSession.svelte.ts +3 -0
  104. package/src/lib/i18n/localeContext.svelte.ts +40 -5
  105. package/src/lib/i18n/messages.ts +351 -13
  106. package/src/lib/index.ts +1 -0
  107. package/src/lib/public.ts +1 -0
  108. package/src/lib/schema/visibility-rules.ts +23 -0
  109. package/src/lib/styles/base.css +9 -6
  110. package/src/lib/types/actionButton.ts +16 -3
  111. package/src/lib/types/presetLabels.ts +13 -11
@@ -5,6 +5,9 @@
5
5
  // Only the strings @selvajs/ui renders itself. Names coming from a Grasshopper
6
6
  // definition — meshes, layers, metadata keys — live in the user's .gh file and
7
7
  // cannot be translated here.
8
+ //
9
+ // A `{name}` in a message is a placeholder the component fills in. Pairs ending in
10
+ // `One`/`Other` are the singular and plural forms of one message.
8
11
 
9
12
  export type Locale = 'en' | 'de';
10
13
 
@@ -33,6 +36,15 @@ export interface ViewerMessages {
33
36
  viewBottom: string;
34
37
  viewIso: string;
35
38
 
39
+ // Display looks
40
+ lookTechnical: string;
41
+ lookStudio: string;
42
+ lookShowcase: string;
43
+ lookArctic: string;
44
+ lookXray: string;
45
+ lookLineart: string;
46
+ lookWireframe: string;
47
+
36
48
  // Scene manager
37
49
  searchObjects: string;
38
50
  clearSearch: string;
@@ -67,8 +79,6 @@ export interface ViewerMessages {
67
79
  moreTab: string;
68
80
  scrollTabsBack: string;
69
81
  scrollTabsForward: string;
70
- expandPanel: string;
71
- collapsePanel: string;
72
82
  /** `{side}` is replaced with `panelSideLeft` / `panelSideRight`. */
73
83
  expandSidePanel: string;
74
84
  panelSideLeft: string;
@@ -82,6 +92,118 @@ export interface ViewerMessages {
82
92
  pressToCalculate: string;
83
93
  calculate: string;
84
94
  upToDate: string;
95
+ /** The panel footer's ⋯ menu: saving and loading inputs, plus the host's own actions. */
96
+ moreActions: string;
97
+ /** Under a menu action that solves before it runs, while the inputs have changed. */
98
+ calculatesFirst: string;
99
+
100
+ // Footer and message centre
101
+ /** `{count}` is replaced with the number of errors. */
102
+ errorCountOne: string;
103
+ errorCountOther: string;
104
+ /** `{count}` is replaced with the number of warnings. */
105
+ warningCountOne: string;
106
+ warningCountOther: string;
107
+ /** `{count}` is replaced with the number of notes. */
108
+ noteCountOne: string;
109
+ noteCountOther: string;
110
+ /** `{count}` is replaced with the number of messages. */
111
+ lastSolveMessagesOne: string;
112
+ lastSolveMessagesOther: string;
113
+ messagesTitle: string;
114
+ /** `{name}` is replaced with the copyright holder, `{year}` with the current year. */
115
+ copyright: string;
116
+ close: string;
117
+ toggleTheme: string;
118
+ errorTitle: string;
119
+ goHome: string;
120
+
121
+ // Input fields
122
+ selectOption: string;
123
+ noOptionsYet: string;
124
+ /** `{min}` is replaced with the field's minimum. */
125
+ minValue: string;
126
+ /** `{max}` is replaced with the field's maximum. */
127
+ maxValue: string;
128
+ fineDragHint: string;
129
+ /** `{max}` is replaced with the field's character limit. */
130
+ maxLength: string;
131
+ invalidFormat: string;
132
+ invalidPattern: string;
133
+
134
+ // File input
135
+ fileModeUpload: string;
136
+ fileModeUrl: string;
137
+ fileUrlLabel: string;
138
+ fetchFile: string;
139
+ fetchingFile: string;
140
+ fileUrlPublic: string;
141
+ chooseFile: string;
142
+ /** `{formats}` is replaced with the accepted extensions. */
143
+ acceptedFormats: string;
144
+ /** `{ext}` is replaced with the file's extension. */
145
+ uploadedFile: string;
146
+ /** `{ext}` is replaced with the file's extension, `{size}` with its size in MB. */
147
+ uploadedFileSize: string;
148
+ /** `{ext}` is replaced with the file's extension, `{size}` with its size in KB. */
149
+ fileLoaded: string;
150
+ urlInvalid: string;
151
+ urlProtocolUnsupported: string;
152
+ /** `{status}` is replaced with the HTTP status the server answered with. */
153
+ urlServerError: string;
154
+ /** `{ext}` is replaced with the rejected extension, `{formats}` with the accepted ones. */
155
+ fileFormatNotAccepted: string;
156
+ /** `{size}` is replaced with the file's size in MB, `{max}` with the limit in MB. */
157
+ fileTooLarge: string;
158
+ fileTooLargeUseUpload: string;
159
+ fileReadFailed: string;
160
+ /** `{host}` is replaced with the URL's host name. */
161
+ urlUnreachable: string;
162
+ urlUnreachableHint: string;
163
+ urlHintSharePoint: string;
164
+ urlHintGoogleDrive: string;
165
+ urlHintDropbox: string;
166
+ unknownError: string;
167
+
168
+ // Outputs
169
+ waitingForData: string;
170
+ waitingForFiles: string;
171
+ /** `{count}` is replaced with the number of files. */
172
+ fileCount: string;
173
+ duplicateFilesTitle: string;
174
+ duplicatePathsOne: string;
175
+ duplicatePathsOther: string;
176
+ duplicateFilesHint: string;
177
+ downloadFile: string;
178
+ /** `{count}` is replaced with the number of files. */
179
+ downloadFiles: string;
180
+ downloading: string;
181
+ downloadFailed: string;
182
+ copy: string;
183
+ copied: string;
184
+ chartReceiving: string;
185
+ chartRenderFailed: string;
186
+ /** `{error}` is replaced with the browser's reason. */
187
+ fullscreenUnavailable: string;
188
+ /** `{error}` is replaced with the browser's reason. */
189
+ exitFullscreenFailed: string;
190
+
191
+ // Image output
192
+ imageViewer: string;
193
+ imageLoading: string;
194
+ /** `{ext}` is replaced with the file's extension, `{formats}` with the supported ones. */
195
+ imageUnsupported: string;
196
+ imageUnknownFormat: string;
197
+ /** `{file}` is replaced with the file's name. */
198
+ imageDecodeFailed: string;
199
+ previousImage: string;
200
+ nextImage: string;
201
+ zoomHint: string;
202
+ zoomIn: string;
203
+ zoomOut: string;
204
+ zoomFit: string;
205
+ zoomActual: string;
206
+ downloadImage: string;
85
207
  }
86
208
 
87
209
  const en: ViewerMessages = {
@@ -107,6 +229,14 @@ const en: ViewerMessages = {
107
229
  viewBottom: 'Bottom',
108
230
  viewIso: 'Isometric',
109
231
 
232
+ lookTechnical: 'Technical',
233
+ lookStudio: 'Studio',
234
+ lookShowcase: 'Showcase',
235
+ lookArctic: 'Arctic',
236
+ lookXray: 'X-Ray',
237
+ lookLineart: 'Line Art',
238
+ lookWireframe: 'Wireframe',
239
+
110
240
  searchObjects: 'Search objects...',
111
241
  clearSearch: 'Clear search',
112
242
  expandLayer: 'Expand layer',
@@ -123,7 +253,7 @@ const en: ViewerMessages = {
123
253
  objectFallbackName: 'Object',
124
254
  noMetadata: 'No metadata',
125
255
 
126
- loadingSchema: 'Loading schema...',
256
+ loadingSchema: 'Loading…',
127
257
  solveBlocked: 'The definition stopped this solve. No results were produced.',
128
258
  solveBlockedTitle: 'Solve stopped',
129
259
  solveMessagesTitle: 'Solve messages',
@@ -137,8 +267,6 @@ const en: ViewerMessages = {
137
267
  moreTab: 'More',
138
268
  scrollTabsBack: 'Previous tabs',
139
269
  scrollTabsForward: 'More tabs',
140
- expandPanel: 'Expand panel',
141
- collapsePanel: 'Collapse panel',
142
270
  expandSidePanel: 'Expand {side} panel',
143
271
  panelSideLeft: 'left',
144
272
  panelSideRight: 'right',
@@ -146,10 +274,99 @@ const en: ViewerMessages = {
146
274
 
147
275
  solving: 'Solving',
148
276
  solvingEllipsis: 'Solving...',
149
- solvingSubtitle: 'Grasshopper is calculating...',
277
+ solvingSubtitle: 'This can take a moment…',
150
278
  pressToCalculate: 'Press to calculate',
151
279
  calculate: 'Calculate',
152
- upToDate: 'Up to date'
280
+ upToDate: 'Up to date',
281
+ moreActions: 'More actions',
282
+ calculatesFirst: 'Calculates first',
283
+
284
+ errorCountOne: '{count} error',
285
+ errorCountOther: '{count} errors',
286
+ warningCountOne: '{count} warning',
287
+ warningCountOther: '{count} warnings',
288
+ noteCountOne: '{count} note',
289
+ noteCountOther: '{count} notes',
290
+ lastSolveMessagesOne: '{count} message from the last solve',
291
+ lastSolveMessagesOther: '{count} messages from the last solve',
292
+ messagesTitle: 'Messages',
293
+ copyright: 'by {name} © {year}',
294
+ close: 'Close',
295
+ toggleTheme: 'Toggle theme',
296
+ errorTitle: 'Error',
297
+ goHome: 'Go Home',
298
+
299
+ selectOption: 'Select an option...',
300
+ noOptionsYet: 'No options available yet.',
301
+ minValue: 'Minimum value is {min}',
302
+ maxValue: 'Maximum value is {max}',
303
+ fineDragHint: 'Hold Shift to drag finely',
304
+ maxLength: 'Maximum {max} characters allowed',
305
+ invalidFormat: 'Invalid format',
306
+ invalidPattern: 'Invalid validation pattern configured',
307
+
308
+ fileModeUpload: 'Upload',
309
+ fileModeUrl: 'URL',
310
+ fileUrlLabel: 'File URL',
311
+ fetchFile: 'Fetch',
312
+ fetchingFile: 'Fetching...',
313
+ fileUrlPublic: 'URL must be publicly accessible (no login required).',
314
+ chooseFile: 'Choose File or Drag & Drop',
315
+ acceptedFormats: 'Accepted formats: {formats}',
316
+ uploadedFile: 'Uploaded file ({ext})',
317
+ uploadedFileSize: 'Uploaded file ({ext}, {size}MB)',
318
+ fileLoaded: 'Loaded {ext} ({size} KB)',
319
+ urlInvalid: 'Invalid URL — make sure it starts with https://',
320
+ urlProtocolUnsupported: 'Only http:// and https:// URLs are supported.',
321
+ urlServerError: 'Server returned {status}. Check the URL is correct and publicly accessible.',
322
+ fileFormatNotAccepted: 'File format "{ext}" is not accepted. Allowed: {formats}',
323
+ fileTooLarge: 'File too large: {size}MB (max {max}MB).',
324
+ fileTooLargeUseUpload: 'Download it and use Upload instead.',
325
+ fileReadFailed: 'Failed to read the downloaded file.',
326
+ urlUnreachable:
327
+ 'Could not reach "{host}" — the file may not exist, or the server does not allow browser access.',
328
+ urlUnreachableHint:
329
+ 'Check that the URL is correct and the file is publicly accessible. If the server requires login, download the file and use the Upload option instead.',
330
+ urlHintSharePoint:
331
+ 'SharePoint/OneDrive blocks browser access. Open the file in SharePoint, use "Download" to save it locally, then upload it here.',
332
+ urlHintGoogleDrive:
333
+ 'Google Drive blocks browser access. Use "Download" in Google Drive to save the file locally, then upload it here.',
334
+ urlHintDropbox:
335
+ 'Dropbox links may block browser access. Try changing "?dl=0" to "?dl=1" in the URL, or download and upload instead.',
336
+ unknownError: 'Unknown error',
337
+
338
+ waitingForData: 'Waiting for data...',
339
+ waitingForFiles: 'Waiting for file data...',
340
+ fileCount: '{count} files',
341
+ duplicateFilesTitle: 'Duplicate file names detected',
342
+ duplicatePathsOne: 'The following path appears more than once:',
343
+ duplicatePathsOther: 'The following paths appear more than once:',
344
+ duplicateFilesHint:
345
+ 'Rename outputs in your Grasshopper definition so each file has a unique name.',
346
+ downloadFile: 'Download File',
347
+ downloadFiles: 'Download {count} Files',
348
+ downloading: 'Downloading...',
349
+ downloadFailed: 'Download failed',
350
+ copy: 'Copy',
351
+ copied: 'Copied!',
352
+ chartReceiving: 'Receiving chart data...',
353
+ chartRenderFailed: 'Failed to render chart',
354
+ fullscreenUnavailable: 'Fullscreen unavailable: {error}',
355
+ exitFullscreenFailed: 'Exit fullscreen failed: {error}',
356
+
357
+ imageViewer: 'image viewer',
358
+ imageLoading: 'Loading image…',
359
+ imageUnsupported: 'Unsupported image format: {ext}. Supported: {formats}',
360
+ imageUnknownFormat: '(unknown)',
361
+ imageDecodeFailed: "Couldn't decode {file}",
362
+ previousImage: 'Previous image',
363
+ nextImage: 'Next image',
364
+ zoomHint: 'Hold Ctrl or ⌘ and scroll to zoom',
365
+ zoomIn: 'Zoom in',
366
+ zoomOut: 'Zoom out',
367
+ zoomFit: 'Fit',
368
+ zoomActual: 'Actual pixels',
369
+ downloadImage: 'Download image'
153
370
  };
154
371
 
155
372
  const de: ViewerMessages = {
@@ -175,6 +392,14 @@ const de: ViewerMessages = {
175
392
  viewBottom: 'Unten',
176
393
  viewIso: 'Isometrisch',
177
394
 
395
+ lookTechnical: 'Technisch',
396
+ lookStudio: 'Studio',
397
+ lookShowcase: 'Präsentation',
398
+ lookArctic: 'Arktis',
399
+ lookXray: 'Röntgen',
400
+ lookLineart: 'Strichzeichnung',
401
+ lookWireframe: 'Drahtgitter',
402
+
178
403
  searchObjects: 'Objekte suchen...',
179
404
  clearSearch: 'Suche löschen',
180
405
  expandLayer: 'Ebene aufklappen',
@@ -191,7 +416,7 @@ const de: ViewerMessages = {
191
416
  objectFallbackName: 'Objekt',
192
417
  noMetadata: 'Keine Metadaten',
193
418
 
194
- loadingSchema: 'Schema wird geladen...',
419
+ loadingSchema: 'Wird geladen…',
195
420
  solveBlocked: 'Die Definition hat diese Berechnung gestoppt. Es wurden keine Ergebnisse erzeugt.',
196
421
  solveBlockedTitle: 'Berechnung gestoppt',
197
422
  solveMessagesTitle: 'Meldungen zur Berechnung',
@@ -206,8 +431,6 @@ const de: ViewerMessages = {
206
431
  moreTab: 'Mehr',
207
432
  scrollTabsBack: 'Vorherige Tabs',
208
433
  scrollTabsForward: 'Weitere Tabs',
209
- expandPanel: 'Bereich aufklappen',
210
- collapsePanel: 'Bereich zuklappen',
211
434
  expandSidePanel: '{side} Bereich aufklappen',
212
435
  panelSideLeft: 'Linken',
213
436
  panelSideRight: 'Rechten',
@@ -215,10 +438,100 @@ const de: ViewerMessages = {
215
438
 
216
439
  solving: 'Berechnung läuft',
217
440
  solvingEllipsis: 'Berechnung läuft...',
218
- solvingSubtitle: 'Grasshopper berechnet...',
219
- pressToCalculate: 'Zum Berechnen klicken',
441
+ solvingSubtitle: 'Das kann einen Moment dauern…',
442
+ pressToCalculate: 'Berechnen',
220
443
  calculate: 'Berechnen',
221
- upToDate: 'Aktuell'
444
+ upToDate: 'Berechnet',
445
+ moreActions: 'Weitere Aktionen',
446
+ calculatesFirst: 'Berechnet zuerst',
447
+
448
+ errorCountOne: '{count} Fehler',
449
+ errorCountOther: '{count} Fehler',
450
+ warningCountOne: '{count} Warnung',
451
+ warningCountOther: '{count} Warnungen',
452
+ noteCountOne: '{count} Hinweis',
453
+ noteCountOther: '{count} Hinweise',
454
+ lastSolveMessagesOne: '{count} Meldung der letzten Berechnung',
455
+ lastSolveMessagesOther: '{count} Meldungen der letzten Berechnung',
456
+ messagesTitle: 'Meldungen',
457
+ copyright: 'von {name} © {year}',
458
+ close: 'Schliessen',
459
+ toggleTheme: 'Farbschema wechseln',
460
+ errorTitle: 'Fehler',
461
+ goHome: 'Zur Startseite',
462
+
463
+ selectOption: 'Option wählen…',
464
+ noOptionsYet: 'Noch keine Optionen verfügbar.',
465
+ minValue: 'Der kleinste erlaubte Wert ist {min}',
466
+ maxValue: 'Der grösste erlaubte Wert ist {max}',
467
+ fineDragHint: 'Umschalttaste halten für feine Schritte',
468
+ maxLength: 'Höchstens {max} Zeichen erlaubt',
469
+ invalidFormat: 'Ungültiges Format',
470
+ invalidPattern: 'Ungültiges Prüfmuster hinterlegt',
471
+
472
+ fileModeUpload: 'Hochladen',
473
+ fileModeUrl: 'URL',
474
+ fileUrlLabel: 'Datei-URL',
475
+ fetchFile: 'Abrufen',
476
+ fetchingFile: 'Wird abgerufen…',
477
+ fileUrlPublic: 'Die URL muss öffentlich erreichbar sein (ohne Anmeldung).',
478
+ chooseFile: 'Datei wählen oder hierher ziehen',
479
+ acceptedFormats: 'Erlaubte Formate: {formats}',
480
+ uploadedFile: 'Hochgeladene Datei ({ext})',
481
+ uploadedFileSize: 'Hochgeladene Datei ({ext}, {size} MB)',
482
+ fileLoaded: '{ext} geladen ({size} KB)',
483
+ urlInvalid: 'Ungültige URL – sie muss mit https:// beginnen',
484
+ urlProtocolUnsupported: 'Nur http://- und https://-URLs werden unterstützt.',
485
+ urlServerError:
486
+ 'Der Server antwortete mit {status}. Ist die URL korrekt und öffentlich erreichbar?',
487
+ fileFormatNotAccepted: 'Dateiformat „{ext}“ ist nicht erlaubt. Erlaubt: {formats}',
488
+ fileTooLarge: 'Datei zu gross: {size} MB (höchstens {max} MB).',
489
+ fileTooLargeUseUpload: 'Datei herunterladen und stattdessen über „Hochladen“ einfügen.',
490
+ fileReadFailed: 'Die heruntergeladene Datei konnte nicht gelesen werden.',
491
+ urlUnreachable:
492
+ '„{host}“ ist nicht erreichbar – die Datei existiert vielleicht nicht, oder der Server erlaubt keinen Zugriff aus dem Browser.',
493
+ urlUnreachableHint:
494
+ 'Prüfen, ob die URL stimmt und die Datei öffentlich erreichbar ist. Verlangt der Server eine Anmeldung: Datei herunterladen und über „Hochladen“ einfügen.',
495
+ urlHintSharePoint:
496
+ 'SharePoint/OneDrive sperrt den Zugriff aus dem Browser. Datei in SharePoint öffnen, mit „Herunterladen“ lokal speichern und hier hochladen.',
497
+ urlHintGoogleDrive:
498
+ 'Google Drive sperrt den Zugriff aus dem Browser. Datei in Google Drive mit „Herunterladen“ lokal speichern und hier hochladen.',
499
+ urlHintDropbox:
500
+ 'Dropbox-Links sperren den Zugriff aus dem Browser oft. In der URL „?dl=0“ durch „?dl=1“ ersetzen oder die Datei herunterladen und hochladen.',
501
+ unknownError: 'Unbekannter Fehler',
502
+
503
+ waitingForData: 'Noch keine Daten',
504
+ waitingForFiles: 'Noch keine Dateien',
505
+ fileCount: '{count} Dateien',
506
+ duplicateFilesTitle: 'Doppelte Dateinamen',
507
+ duplicatePathsOne: 'Dieser Pfad kommt mehrfach vor:',
508
+ duplicatePathsOther: 'Diese Pfade kommen mehrfach vor:',
509
+ duplicateFilesHint:
510
+ 'Ausgaben in der Grasshopper-Definition umbenennen, damit jede Datei einen eindeutigen Namen hat.',
511
+ downloadFile: 'Datei herunterladen',
512
+ downloadFiles: '{count} Dateien herunterladen',
513
+ downloading: 'Wird heruntergeladen…',
514
+ downloadFailed: 'Download fehlgeschlagen',
515
+ copy: 'Kopieren',
516
+ copied: 'Kopiert!',
517
+ chartReceiving: 'Diagrammdaten werden empfangen…',
518
+ chartRenderFailed: 'Diagramm konnte nicht dargestellt werden',
519
+ fullscreenUnavailable: 'Vollbild nicht verfügbar: {error}',
520
+ exitFullscreenFailed: 'Vollbild konnte nicht beendet werden: {error}',
521
+
522
+ imageViewer: 'Bildbetrachter',
523
+ imageLoading: 'Bild wird geladen…',
524
+ imageUnsupported: 'Bildformat nicht unterstützt: {ext}. Unterstützt: {formats}',
525
+ imageUnknownFormat: '(unbekannt)',
526
+ imageDecodeFailed: '{file} konnte nicht gelesen werden',
527
+ previousImage: 'Vorheriges Bild',
528
+ nextImage: 'Nächstes Bild',
529
+ zoomHint: 'Zum Zoomen Strg oder ⌘ halten und scrollen',
530
+ zoomIn: 'Vergrössern',
531
+ zoomOut: 'Verkleinern',
532
+ zoomFit: 'Einpassen',
533
+ zoomActual: 'Originalgrösse',
534
+ downloadImage: 'Bild herunterladen'
222
535
  };
223
536
 
224
537
  export const VIEWER_MESSAGES: Record<Locale, ViewerMessages> = { en, de };
@@ -229,3 +542,28 @@ export const DEFAULT_LOCALE: Locale = 'en';
229
542
  export function messagesFor(locale: Locale | undefined): ViewerMessages {
230
543
  return VIEWER_MESSAGES[locale ?? DEFAULT_LOCALE] ?? VIEWER_MESSAGES[DEFAULT_LOCALE];
231
544
  }
545
+
546
+ /** Layers a host's partial overrides over a full catalog. An `undefined` entry keeps the base string. */
547
+ export function withOverrides(
548
+ base: ViewerMessages,
549
+ overrides: Partial<ViewerMessages> | undefined
550
+ ): ViewerMessages {
551
+ if (!overrides) return base;
552
+ const merged = { ...base };
553
+ for (const [key, value] of Object.entries(overrides) as [keyof ViewerMessages, string][]) {
554
+ if (value !== undefined) merged[key] = value;
555
+ }
556
+ return merged;
557
+ }
558
+
559
+ /** Fills each `{name}` placeholder from `vars`; an unknown placeholder is left as written. */
560
+ export function formatMessage(template: string, vars: Record<string, string | number>): string {
561
+ return template.replace(/\{(\w+)\}/g, (placeholder, name: string) =>
562
+ name in vars ? String(vars[name]) : placeholder
563
+ );
564
+ }
565
+
566
+ /** Picks the singular or plural form for `count` and fills its `{count}`. */
567
+ export function formatCount(count: number, one: string, other: string): string {
568
+ return formatMessage(count === 1 ? one : other, { count });
569
+ }
package/src/lib/index.ts CHANGED
@@ -30,6 +30,7 @@ export {
30
30
  createRequestResponseDriver,
31
31
  type SolveSession,
32
32
  type SolveSessionArgs,
33
+ type SolveSettlement,
33
34
  type SolveDriver,
34
35
  type SolveReporter,
35
36
  type LiveSolveState,
package/src/lib/public.ts CHANGED
@@ -55,6 +55,7 @@ export {
55
55
  createRequestResponseDriver,
56
56
  type SolveSession,
57
57
  type SolveSessionArgs,
58
+ type SolveSettlement,
58
59
  type SolveDriver,
59
60
  type SolveReporter,
60
61
  type LiveSolveState,
@@ -1,10 +1,12 @@
1
1
  import type {
2
2
  LayoutItem,
3
+ OutputLayoutItem,
3
4
  VisibilityRule,
4
5
  VisibilityCondition,
5
6
  GroupVisibilityCondition,
6
7
  TabConfig
7
8
  } from '@selvajs/schemas';
9
+ import { isFileData } from '../utils/file-download';
8
10
 
9
11
  export interface VisibilityResult {
10
12
  visible: boolean;
@@ -76,6 +78,9 @@ export function evaluateVisibility(
76
78
  if ('visible' in item && item.visible === false) {
77
79
  return { visible: false, disabled: false };
78
80
  }
81
+ if (item.type === 'output' && drawsNothing(item, values[item.paramId])) {
82
+ return { visible: false, disabled: false };
83
+ }
79
84
  if (!item.visibilityCondition?.rules) return { visible: true, disabled: false };
80
85
 
81
86
  const { action = 'show', defaultValue } = item.visibilityCondition;
@@ -85,6 +90,24 @@ export function evaluateVisibility(
85
90
  return actionFn(met, defaultValue);
86
91
  }
87
92
 
93
+ /**
94
+ * An image or chart output has nothing to draw until a solve returns its data, and an empty
95
+ * preview box would hold its group, tab and pane open; it counts as hidden until then. A dynamic
96
+ * value list output never draws: it only feeds the options of an input.
97
+ */
98
+ function drawsNothing(item: OutputLayoutItem, value: unknown): boolean {
99
+ switch (item.widgetType) {
100
+ case 'image':
101
+ return !(Array.isArray(value) ? value.some(isFileData) : isFileData(value));
102
+ case 'chart':
103
+ return value === undefined || value === null || value === '';
104
+ case 'dynamicValueList':
105
+ return true;
106
+ default:
107
+ return false;
108
+ }
109
+ }
110
+
88
111
  /** Must stay identical to the key the renderers use in their `{#each}` blocks. */
89
112
  export function itemKey(item: LayoutItem): string {
90
113
  return item.type === 'linebreak' ? item.id : item.paramId;
@@ -19,11 +19,14 @@
19
19
  --muted-foreground: oklch(0.5 0 0);
20
20
  --accent: oklch(0.93 0 0);
21
21
  --accent-foreground: oklch(0.3 0 0);
22
- --destructive: oklch(0.6 0.18 27);
22
+ /* 4.5:1 as small text on background, card and muted, and under white text. */
23
+ --destructive: oklch(0.55 0.19 27);
23
24
  --destructive-foreground: oklch(0.985 0 0);
24
25
  --border: oklch(0.86 0 0);
25
- --input: oklch(0.91 0 0);
26
- --ring: oklch(0.67 0 0);
26
+ /* Field borders and unchecked boxes: at least 3:1 against background, card and muted, the
27
+ WCAG minimum for a control's boundary. Same for the focus ring, which is drawn opaque. */
28
+ --input: oklch(0.62 0 0);
29
+ --ring: oklch(0.55 0 0);
27
30
  --success: oklch(0.66 0.14 145);
28
31
  --success-foreground: oklch(0.99 0 0);
29
32
  --warning: oklch(0.78 0.12 75);
@@ -42,7 +45,7 @@
42
45
  --sidebar-accent: oklch(0.93 0 0);
43
46
  --sidebar-accent-foreground: oklch(0.3 0 0);
44
47
  --sidebar-border: oklch(0.88 0 0);
45
- --sidebar-ring: oklch(0.67 0 0);
48
+ --sidebar-ring: oklch(0.55 0 0);
46
49
  --inputparam: oklch(1 0 0);
47
50
  --outputparam: oklch(1 0 0);
48
51
  /* Scrim behind modal dialogs and drawers. */
@@ -67,7 +70,7 @@
67
70
  --destructive: oklch(0.7 0.17 22);
68
71
  --destructive-foreground: oklch(0.985 0 0);
69
72
  --border: oklch(1 0 0 / 14%);
70
- --input: oklch(1 0 0 / 12%);
73
+ --input: oklch(1 0 0 / 36%);
71
74
  --ring: oklch(0.74 0 0);
72
75
  --success: oklch(0.74 0.15 145);
73
76
  --success-foreground: oklch(0.18 0.03 145);
@@ -179,7 +182,7 @@
179
182
 
180
183
  @layer base {
181
184
  * {
182
- @apply border-border outline-ring/50;
185
+ @apply border-border outline-ring;
183
186
  }
184
187
  html,
185
188
  body {
@@ -1,9 +1,22 @@
1
+ import type { SolveSettlement } from '@selvajs/solve/client';
2
+
3
+ /** A host action, listed in the panel footer's ⋯ menu after saving and loading inputs. */
1
4
  export interface ActionButton {
2
5
  id: string;
3
6
  label: string;
4
7
  /** A Svelte component, rendered before the label. */
5
8
  icon?: any;
6
- variant?: 'default' | 'outline' | 'destructive' | 'secondary' | 'ghost';
7
- size?: 'default' | 'sm' | 'lg';
8
- onclick: () => void | Promise<void>;
9
+ variant?: 'default' | 'destructive';
10
+ /** Shows the action as unavailable instead of hiding it. Pair it with a `hint` saying why. */
11
+ disabled?: boolean;
12
+ /** A short line under the label: why it is disabled, or what it does. */
13
+ hint?: string;
14
+ /**
15
+ * The action needs the result for the current inputs: picking it solves first when inputs
16
+ * changed, waits for a solve that is running or under review, and runs `onclick` only if
17
+ * that solve is applied. `onclick` then receives it. A refused or failed solve already shows
18
+ * its messages, so the action just doesn't run.
19
+ */
20
+ solvesFirst?: boolean;
21
+ onclick: (settled?: Extract<SolveSettlement, { status: 'applied' }>) => void | Promise<void>;
9
22
  }
@@ -3,9 +3,9 @@
3
3
  * unset keys fall back to `DEFAULT_PRESET_LABELS`.
4
4
  */
5
5
  export interface PresetLabels {
6
- // Toolbar buttons
7
- saveButton: string;
8
- loadButton: string;
6
+ // Panel footer menu
7
+ saveMenuItem: string;
8
+ loadMenuItem: string;
9
9
 
10
10
  // Save dialog
11
11
  saveDialogTitle: string;
@@ -19,6 +19,7 @@ export interface PresetLabels {
19
19
  saveTagsLabel: string;
20
20
  saveTagsPlaceholder: string;
21
21
  saveNameRequired: string;
22
+ saveConfirmButton: string;
22
23
 
23
24
  // Load dialog
24
25
  loadDialogTitle: string;
@@ -47,12 +48,12 @@ export interface PresetLabels {
47
48
  }
48
49
 
49
50
  export const DEFAULT_PRESET_LABELS: PresetLabels = {
50
- saveButton: 'Save State',
51
- loadButton: 'Load State',
51
+ saveMenuItem: 'Save inputs…',
52
+ loadMenuItem: 'Load inputs…',
52
53
 
53
- saveDialogTitle: 'Save Parameter State',
54
- saveDialogDescription: 'Save the current parameter values as a .slvp file',
55
- saveNameLabel: 'State Name *',
54
+ saveDialogTitle: 'Save inputs',
55
+ saveDialogDescription: 'Save the current input values as a .slvp file',
56
+ saveNameLabel: 'Name *',
56
57
  saveNamePlaceholder: 'e.g., Design Option A',
57
58
  saveDescriptionLabel: 'Description',
58
59
  saveDescriptionPlaceholder: 'Optional description of this state',
@@ -60,10 +61,11 @@ export const DEFAULT_PRESET_LABELS: PresetLabels = {
60
61
  saveAuthorPlaceholder: 'Your name or email',
61
62
  saveTagsLabel: 'Tags',
62
63
  saveTagsPlaceholder: 'facade, option-a, client-approved (comma-separated)',
63
- saveNameRequired: 'Please enter a name for this state',
64
+ saveNameRequired: 'Please enter a name for these inputs',
65
+ saveConfirmButton: 'Save',
64
66
 
65
- loadDialogTitle: 'Load Parameter State',
66
- loadDialogDescription: 'Select a .slvp state file from your drive to load',
67
+ loadDialogTitle: 'Load inputs',
68
+ loadDialogDescription: 'Select a .slvp file from your drive to load',
67
69
  loadFromFileButton: 'Select .slvp File',
68
70
  loadEmptyList: 'No saved states found',
69
71
  loadImportError: 'Failed to import state: ',