@lapismd/design-core 0.1.0 → 0.2.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/CHANGELOG.md +44 -0
- package/LICENSE.md +3 -3
- package/README.md +21 -1
- package/package.json +34 -15
- package/spec/src/styling-and-themes.md +11 -9
- package/src/shared/ai/chat.css +25 -1
- package/src/shared/ai/chat.ts +12 -0
- package/src/shared/ai/composer-input/ComposerInput.svelte +3 -2
- package/src/shared/ai/context.svelte.ts +2 -0
- package/src/shared/ai/experimental/reaction-bar/ReactionBar.svelte +1 -1
- package/src/shared/ai/layout/Layout.svelte +116 -10
- package/src/shared/ai/message-list/MessageList.svelte +123 -43
- package/src/shared/ai/message-list/MessageListHistoryFixture.svelte +112 -0
- package/src/shared/ai/message-list/MessageListLegacyFixture.svelte +52 -0
- package/src/shared/ai/message-list/VirtualizedMessageRows.svelte +299 -0
- package/src/shared/ai/message-list/position.ts +7 -0
- package/src/shared/ai/new-messages.svelte.ts +24 -5
- package/src/shared/ai/stream-scroll.svelte.ts +135 -6
- package/src/shared/ai/tool-call-detail/ToolCallDetail.svelte +71 -0
- package/src/shared/ai/tool-call-detail/tool-call-detail.ts +209 -0
- package/src/shared/ai/tool-calls/ToolCalls.svelte +1 -1
- package/src/shared/ai/trigger-menu.ts +2 -3
- package/src/shared/diff/core/merge/actions.ts +4 -3
- package/src/shared/diff/core/merge/render-model.ts +14 -10
- package/src/shared/diff/file-diff/FileDiff.css +68 -1
- package/src/shared/diff/file-diff/FileDiff.svelte +27 -14
- package/src/shared/diff/file-diff/FileDiffComposer.svelte +98 -16
- package/src/shared/diff/file-diff/index.ts +2 -0
- package/src/shared/diff/file-diff/types.ts +13 -0
- package/src/shared/diff/file-listing/FileListingViewModeToggle.css +42 -5
- package/src/shared/diff/index.ts +2 -0
- package/src/shared/diff/merge-editor/MergeEditor.svelte +2 -7
- package/src/shared/filter/filter-query/filters.ts +2 -2
- package/src/shared/filter/filter-query/parser.ts +9 -8
- package/src/shared/filter/power-search/PowerSearch.svelte +1 -1
- package/src/shared/filter/search-filter-bar/SearchFilterBar.svelte +2 -2
- package/src/shared/filter/search-filter-bar/SearchFilterInput.svelte +1 -1
- package/src/shared/filter/search-filter-bar/SearchFilterPredicateEditor.svelte +1 -1
- package/src/shared/filter/search-filter-bar/search-filter-syntax.ts +1 -1
- package/src/shared/forms/autocomplete-input/AutocompleteInput.svelte +19 -7
- package/src/shared/forms/confirm-button/ConfirmButton.svelte +38 -0
- package/src/shared/forms/confirm-button/ConfirmButtonExample.svelte +14 -0
- package/src/shared/forms/confirm-button/index.ts +1 -0
- package/src/shared/forms/confirm-dialog/ConfirmDialog.css +21 -0
- package/src/shared/forms/confirm-dialog/ConfirmDialog.svelte +116 -0
- package/src/shared/forms/confirm-dialog/ConfirmDialogExample.svelte +17 -0
- package/src/shared/forms/confirm-dialog/index.ts +2 -0
- package/src/shared/forms/dialog-frame/DialogFrame.css +130 -0
- package/src/shared/forms/dialog-frame/DialogFrame.svelte +108 -0
- package/src/shared/forms/dialog-frame/DialogFrameExample.svelte +37 -0
- package/src/shared/forms/dialog-frame/index.ts +6 -0
- package/src/shared/forms/dialog.ts +8 -0
- package/src/shared/forms/index.ts +11 -0
- package/src/shared/shadcn/button/button.svelte +4 -0
- package/src/shared/shadcn/code-block/highlightRanges.ts +10 -6
- package/src/shared/shadcn/code-block/syntax/defineSyntaxTheme.ts +4 -2
- package/src/shared/shadcn/code-block/tokenizer.ts +3 -3
- package/src/shared/shadcn/command/command-dialog.svelte +1 -1
- package/src/shared/shadcn/command/command-group.svelte +3 -1
- package/src/shared/shadcn/command-view/command-view.svelte +1 -6
- package/src/shared/shadcn/dialog/dialog-content.svelte +3 -4
- package/src/shared/shadcn/dialog/dialog-portal.svelte +10 -2
- package/src/shared/shadcn/dropdown-menu/dropdown-menu-checkbox-item.svelte +0 -691
- package/src/shared/shadcn/dropdown-menu/dropdown-menu-content.svelte +691 -0
- package/src/shared/shadcn/dropdown-menu/dropdown-menu-radio-group.svelte +1 -1
- package/src/shared/shadcn/scroll-area/scroll-area.svelte +2 -0
- package/src/shared/shadcn/spinner/spinner.svelte +3 -3
- package/src/shared/shadcn/tooltip/tooltip-content.svelte +10 -4
- package/src/shared/shadcn/tooltip/tooltip.tokens.ts +4 -0
- package/src/shared/shell/app-shell/AppShell.css +112 -32
- package/src/shared/shell/app-shell/AppShell.demo.css +37 -0
- package/src/shared/shell/app-shell/AppShellBodyContent.svelte +5 -1
- package/src/shared/shell/app-shell/AppShellBodySidebar.svelte +5 -1
- package/src/shared/shell/app-shell/AppShellMain.svelte +12 -1
- package/src/shared/shell/app-shell/AppShellMobilePanelSelector.svelte +1 -1
- package/src/shared/shell/app-shell/AppShellRoot.svelte +27 -13
- package/src/shared/shell/app-shell/AppShellSidebar.svelte +121 -19
- package/src/shared/shell/app-shell/AppShellSidebarClose.svelte +17 -17
- package/src/shared/shell/app-shell/AppShellSidebarToggle.svelte +38 -58
- package/src/shared/shell/app-shell/AppShellSurfaceLayer.svelte +173 -0
- package/src/shared/shell/app-shell/AppShellSurfaceLayerBody.svelte +32 -0
- package/src/shared/shell/app-shell/AppShellSurfaceLayerHeader.svelte +26 -0
- package/src/shared/shell/app-shell/app-shell-context.svelte.ts +6 -3
- package/src/shared/shell/app-shell/app-shell-controller.svelte.ts +269 -11
- package/src/shared/shell/app-shell/app-shell-sidebar-context.svelte.ts +1 -0
- package/src/shared/shell/app-shell/examples/AppShellBodyDemo.svelte +4 -0
- package/src/shared/shell/app-shell/examples/AppShellCompleteDemo.svelte +1 -0
- package/src/shared/shell/app-shell/examples/AppShellConversationDemo.svelte +1 -0
- package/src/shared/shell/app-shell/examples/AppShellExamples.css +624 -0
- package/src/shared/shell/app-shell/examples/AppShellFilesSidebarDemo.svelte +1 -0
- package/src/shared/shell/app-shell/examples/AppShellMarkdownDocumentDemo.svelte +6 -1
- package/src/shared/shell/app-shell/examples/AppShellMarkdownFilesDemo.svelte +1 -0
- package/src/shared/shell/app-shell/examples/AppShellModeSwitchDemo.svelte +1 -0
- package/src/shared/shell/app-shell/examples/AppShellMultiPanelDemo.svelte +187 -0
- package/src/shared/shell/app-shell/examples/AppShellProjectSidebarDemo.svelte +1 -0
- package/src/shared/shell/app-shell/examples/AppShellSidebarDemo.svelte +1 -0
- package/src/shared/shell/app-shell/examples/AppShellToolbarDemo.svelte +1 -0
- package/src/shared/shell/app-shell/index.ts +14 -0
- package/src/shared/shell/shell.tokens.css +2 -0
- package/src/shared/shell/shell.tokens.ts +5 -0
- package/src/shared/workspace/menu/WorkspaceContextMenuItems.svelte +2 -1
- package/src/shared/workspace/menu/WorkspaceMenuItems.svelte +2 -1
- package/src/shared/workspace/problems/ProblemsQuickFix.svelte +1 -4
- package/src/shared/workspace/settings/AppSettingsContent.svelte +6 -0
- package/src/shared/workspace/settings/AppSettingsNavigation.svelte +7 -0
- package/src/shared/workspace/settings/CopyableValue.svelte +81 -0
- package/src/shared/workspace/settings/DeclarativeSettingsSection.svelte +20 -0
- package/src/shared/workspace/settings/WorkspaceSettingField.svelte +110 -41
- package/src/shared/workspace/settings/WorkspaceSettingMultiSelect.svelte +7 -6
- package/src/shared/workspace/settings/WorkspaceSettingObjectTable.svelte +187 -23
- package/src/shared/workspace/settings/WorkspaceSettingSelect.svelte +29 -29
- package/src/shared/workspace/settings/WorkspaceSettings.css +190 -4
- package/src/shared/workspace/settings/WorkspaceSettingsStoryCustomField.svelte +2 -2
- package/src/shared/workspace/settings/WorkspaceSettingsStoryCustomPage.svelte +13 -0
- package/src/shared/workspace/settings/WorkspaceSettingsSurface.svelte +50 -0
- package/src/shared/workspace/settings/configuration.ts +5 -0
- package/src/shared/workspace/settings/copyable-value.ts +10 -0
- package/src/shared/workspace/settings/index.ts +5 -0
- package/src/shared/workspace/settings/settings-controller.svelte.ts +349 -15
- package/src/shared/workspace/settings/types.ts +90 -8
- package/src/storybook/app-shell-reference/AppShellTwoExpandedSidebarsReference.css +25 -0
- package/src/storybook/app-shell-reference/AppShellTwoExpandedSidebarsReference.svelte +48 -0
- package/src/storybook/app-shell-reference/index.ts +1 -0
- package/src/theme.css +10 -0
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import WorkspaceSettingObjectTable from "./WorkspaceSettingObjectTable.svelte";
|
|
22
22
|
import WorkspaceSettingSelect from "./WorkspaceSettingSelect.svelte";
|
|
23
23
|
import WorkspaceSettingToggleTable from "./WorkspaceSettingToggleTable.svelte";
|
|
24
|
+
import CopyableValue from "./CopyableValue.svelte";
|
|
24
25
|
|
|
25
26
|
let {
|
|
26
27
|
controller,
|
|
@@ -31,7 +32,12 @@
|
|
|
31
32
|
} = $props();
|
|
32
33
|
|
|
33
34
|
let value = $derived(controller.get(field.id));
|
|
34
|
-
let error = $derived(controller.
|
|
35
|
+
let error = $derived(controller.getError(field.id));
|
|
36
|
+
let busy = $derived(controller.isBusy(field.id));
|
|
37
|
+
let actionResult = $derived(controller.getActionResult(field.id));
|
|
38
|
+
let controlDisabled = $derived(
|
|
39
|
+
field.disabled === true || field.readOnly === true,
|
|
40
|
+
);
|
|
35
41
|
let options = $state<WorkspaceSettingOption[]>([]);
|
|
36
42
|
let optionRequest = 0;
|
|
37
43
|
let keyValueOptions = $derived.by(() => {
|
|
@@ -100,7 +106,8 @@
|
|
|
100
106
|
return field.type === "key-value" ||
|
|
101
107
|
field.type === "object-array" ||
|
|
102
108
|
field.type === "object-grid" ||
|
|
103
|
-
field.type === "object-map"
|
|
109
|
+
field.type === "object-map" ||
|
|
110
|
+
(field.type === "custom" && field.presentation === "full-width")
|
|
104
111
|
? "stacked"
|
|
105
112
|
: "row";
|
|
106
113
|
}
|
|
@@ -123,7 +130,7 @@
|
|
|
123
130
|
const next = { ...current };
|
|
124
131
|
delete next[currentKey];
|
|
125
132
|
next[nextKey] = nextValue;
|
|
126
|
-
controller.
|
|
133
|
+
void controller.set(field.id, next);
|
|
127
134
|
}
|
|
128
135
|
|
|
129
136
|
function nextKey(field: WorkspaceKeyValueSetting) {
|
|
@@ -180,17 +187,17 @@
|
|
|
180
187
|
<Switch
|
|
181
188
|
id={`setting-${field.id}`}
|
|
182
189
|
checked={value === true}
|
|
183
|
-
disabled={
|
|
184
|
-
onCheckedChange={(checked) => controller.
|
|
190
|
+
disabled={controlDisabled}
|
|
191
|
+
onCheckedChange={(checked) => void controller.set(field.id, checked)}
|
|
185
192
|
/>
|
|
186
193
|
{:else if field.type === "string" && field.presentation === "icon"}
|
|
187
194
|
<WorkspaceIconPicker
|
|
188
195
|
id={`setting-${field.id}`}
|
|
189
196
|
value={String(value ?? "")}
|
|
190
|
-
disabled={
|
|
197
|
+
disabled={controlDisabled}
|
|
191
198
|
placeholder={field.placeholder ?? "Select an icon"}
|
|
192
199
|
ariaLabel={field.title}
|
|
193
|
-
onValueChange={(next) => controller.
|
|
200
|
+
onValueChange={(next) => void controller.set(field.id, next)}
|
|
194
201
|
/>
|
|
195
202
|
{:else if field.type === "string" && field.presentation === "textarea"}
|
|
196
203
|
<Textarea
|
|
@@ -198,30 +205,30 @@
|
|
|
198
205
|
rows={1}
|
|
199
206
|
value={String(value ?? "")}
|
|
200
207
|
placeholder={field.placeholder}
|
|
201
|
-
disabled={
|
|
208
|
+
disabled={controlDisabled}
|
|
202
209
|
aria-invalid={Boolean(error)}
|
|
203
210
|
oninput={(event) =>
|
|
204
|
-
controller.
|
|
211
|
+
void controller.set(field.id, event.currentTarget.value)}
|
|
205
212
|
/>
|
|
206
213
|
{:else if field.type === "string" && (field.presentation === "combobox" || field.optionsSource)}
|
|
207
214
|
<WorkspaceSettingSelect
|
|
208
215
|
id={`setting-${field.id}`}
|
|
209
216
|
items={options}
|
|
210
217
|
value={String(value ?? field.default)}
|
|
211
|
-
disabled={
|
|
218
|
+
disabled={controlDisabled}
|
|
212
219
|
ariaLabel={field.title}
|
|
213
220
|
placeholder={field.placeholder}
|
|
214
|
-
onValueChange={(next: string) => controller.
|
|
221
|
+
onValueChange={(next: string) => void controller.set(field.id, next)}
|
|
215
222
|
/>
|
|
216
223
|
{:else if field.type === "string" && field.presentation === "password"}
|
|
217
224
|
<PasswordInput
|
|
218
225
|
id={`setting-${field.id}`}
|
|
219
226
|
value={String(value ?? "")}
|
|
220
227
|
placeholder={field.placeholder}
|
|
221
|
-
disabled={
|
|
228
|
+
disabled={controlDisabled}
|
|
222
229
|
aria-invalid={Boolean(error)}
|
|
223
230
|
oninput={(event) =>
|
|
224
|
-
controller.
|
|
231
|
+
void controller.set(field.id, event.currentTarget.value)}
|
|
225
232
|
/>
|
|
226
233
|
{:else if field.type === "string"}
|
|
227
234
|
<Input
|
|
@@ -239,10 +246,10 @@
|
|
|
239
246
|
: "text"}
|
|
240
247
|
value={String(value ?? "")}
|
|
241
248
|
placeholder={field.placeholder}
|
|
242
|
-
disabled={
|
|
249
|
+
disabled={controlDisabled}
|
|
243
250
|
aria-invalid={Boolean(error)}
|
|
244
251
|
oninput={(event) =>
|
|
245
|
-
controller.
|
|
252
|
+
void controller.set(field.id, event.currentTarget.value)}
|
|
246
253
|
/>
|
|
247
254
|
{:else if (field.type === "number" || field.type === "integer") && field.minimum !== undefined && field.maximum !== undefined}
|
|
248
255
|
<div class="ui-workspace-setting-range">
|
|
@@ -253,9 +260,10 @@
|
|
|
253
260
|
min={field.minimum}
|
|
254
261
|
max={field.maximum}
|
|
255
262
|
step={field.step ?? (field.type === "integer" ? 1 : 0.1)}
|
|
256
|
-
disabled={
|
|
263
|
+
disabled={controlDisabled}
|
|
257
264
|
aria-label={field.title}
|
|
258
|
-
onValueChange={(next: number) =>
|
|
265
|
+
onValueChange={(next: number) =>
|
|
266
|
+
void controller.set(field.id, next)}
|
|
259
267
|
/>
|
|
260
268
|
<output for={`setting-${field.id}`}
|
|
261
269
|
>{Number(value ?? field.default)}</output
|
|
@@ -269,18 +277,18 @@
|
|
|
269
277
|
min={field.minimum}
|
|
270
278
|
max={field.maximum}
|
|
271
279
|
step={field.step ?? (field.type === "integer" ? 1 : "any")}
|
|
272
|
-
disabled={
|
|
280
|
+
disabled={controlDisabled}
|
|
273
281
|
onchange={(event) =>
|
|
274
|
-
controller.
|
|
282
|
+
void controller.set(field.id, event.currentTarget.valueAsNumber)}
|
|
275
283
|
/>
|
|
276
284
|
{:else if field.type === "enum"}
|
|
277
285
|
<WorkspaceSettingSelect
|
|
278
286
|
id={`setting-${field.id}`}
|
|
279
287
|
items={options}
|
|
280
288
|
value={String(value ?? field.default)}
|
|
281
|
-
disabled={
|
|
289
|
+
disabled={controlDisabled}
|
|
282
290
|
ariaLabel={field.title}
|
|
283
|
-
onValueChange={(next: string) => controller.
|
|
291
|
+
onValueChange={(next: string) => void controller.set(field.id, next)}
|
|
284
292
|
/>
|
|
285
293
|
{:else if field.type === "multi-enum"}
|
|
286
294
|
<WorkspaceSettingSelect
|
|
@@ -292,10 +300,11 @@
|
|
|
292
300
|
(entry): entry is string => typeof entry === "string",
|
|
293
301
|
)
|
|
294
302
|
: field.default}
|
|
295
|
-
disabled={
|
|
303
|
+
disabled={controlDisabled}
|
|
296
304
|
ariaLabel={field.title}
|
|
297
305
|
placeholder="Select options..."
|
|
298
|
-
onValueChange={(next: string[]) =>
|
|
306
|
+
onValueChange={(next: string[]) =>
|
|
307
|
+
void controller.set(field.id, next)}
|
|
299
308
|
/>
|
|
300
309
|
{:else if field.type === "list"}
|
|
301
310
|
<WorkspaceSettingList
|
|
@@ -303,19 +312,26 @@
|
|
|
303
312
|
label={field.title}
|
|
304
313
|
itemLabels={field.itemLabels}
|
|
305
314
|
value={Array.isArray(value) ? value : []}
|
|
306
|
-
disabled={
|
|
315
|
+
disabled={controlDisabled}
|
|
307
316
|
maximumItems={field.maximumItems}
|
|
308
|
-
onValueChange={(next) => controller.
|
|
317
|
+
onValueChange={(next) => void controller.set(field.id, next)}
|
|
309
318
|
/>
|
|
310
319
|
{:else if field.type === "object-array" || field.type === "object-grid"}
|
|
311
320
|
<WorkspaceSettingObjectTable
|
|
312
321
|
label={field.title}
|
|
313
322
|
properties={field.properties}
|
|
314
323
|
value={value ?? field.default}
|
|
315
|
-
disabled={
|
|
324
|
+
disabled={controlDisabled}
|
|
316
325
|
minimumItems={field.minimumItems}
|
|
317
326
|
maximumItems={field.maximumItems}
|
|
318
|
-
|
|
327
|
+
rowKey={field.rowKey}
|
|
328
|
+
reorderable={field.reorderable}
|
|
329
|
+
allowAdd={field.allowAdd}
|
|
330
|
+
allowRemove={field.allowRemove}
|
|
331
|
+
stateRevision={controller.sourceRevision}
|
|
332
|
+
addLabel={field.addLabel}
|
|
333
|
+
rowActions={field.rowActions}
|
|
334
|
+
onValueChange={(next) => void controller.set(field.id, next)}
|
|
319
335
|
/>
|
|
320
336
|
{:else if field.type === "object-map"}
|
|
321
337
|
<WorkspaceSettingObjectTable
|
|
@@ -323,8 +339,8 @@
|
|
|
323
339
|
properties={field.properties}
|
|
324
340
|
mode="map"
|
|
325
341
|
value={value ?? field.default}
|
|
326
|
-
disabled={
|
|
327
|
-
onValueChange={(next) => controller.
|
|
342
|
+
disabled={controlDisabled}
|
|
343
|
+
onValueChange={(next) => void controller.set(field.id, next)}
|
|
328
344
|
/>
|
|
329
345
|
{:else if field.type === "key-value"}
|
|
330
346
|
<div class="ui-workspace-setting-key-value">
|
|
@@ -370,7 +386,7 @@
|
|
|
370
386
|
onclick={() => {
|
|
371
387
|
const next = { ...keyValue(field) };
|
|
372
388
|
delete next[key];
|
|
373
|
-
controller.
|
|
389
|
+
void controller.set(field.id, next);
|
|
374
390
|
}}
|
|
375
391
|
>
|
|
376
392
|
<WorkspaceIcon name="trash-2" />
|
|
@@ -383,20 +399,62 @@
|
|
|
383
399
|
<WorkspaceSettingAddButton
|
|
384
400
|
label={field.addLabel ?? "Add entry"}
|
|
385
401
|
onclick={() =>
|
|
386
|
-
controller.
|
|
402
|
+
void controller.set(field.id, {
|
|
387
403
|
...keyValue(field),
|
|
388
404
|
[nextKey(field)]: keyValueOptions[0]?.value ?? "",
|
|
389
405
|
})}
|
|
390
406
|
/>
|
|
391
407
|
</div>
|
|
408
|
+
{:else if field.type === "output"}
|
|
409
|
+
{#if field.presentation === "copyable"}
|
|
410
|
+
<CopyableValue
|
|
411
|
+
value={value == null ? null : String(value)}
|
|
412
|
+
label={field.copyLabel ?? field.title}
|
|
413
|
+
emptyLabel={field.emptyLabel}
|
|
414
|
+
leadingCharacters={field.leadingCharacters}
|
|
415
|
+
trailingCharacters={field.trailingCharacters}
|
|
416
|
+
/>
|
|
417
|
+
{:else}
|
|
418
|
+
<output
|
|
419
|
+
id={`setting-${field.id}`}
|
|
420
|
+
class="ui-workspace-setting-output"
|
|
421
|
+
data-presentation={field.presentation ?? "text"}
|
|
422
|
+
>
|
|
423
|
+
{#if field.presentation === "code"}
|
|
424
|
+
<code
|
|
425
|
+
>{value == null
|
|
426
|
+
? (field.emptyLabel ?? "None")
|
|
427
|
+
: String(value)}</code
|
|
428
|
+
>
|
|
429
|
+
{:else}
|
|
430
|
+
{value == null ? (field.emptyLabel ?? "None") : String(value)}
|
|
431
|
+
{/if}
|
|
432
|
+
</output>
|
|
433
|
+
{/if}
|
|
392
434
|
{:else if field.type === "custom"}
|
|
393
|
-
{@const CustomComponent = field.
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
435
|
+
{@const CustomComponent = field.adapter
|
|
436
|
+
? controller.resolveCustomAdapter(field.id, field.adapter)?.component
|
|
437
|
+
: field.component}
|
|
438
|
+
{#if CustomComponent}
|
|
439
|
+
<CustomComponent
|
|
440
|
+
id={field.id}
|
|
441
|
+
{field}
|
|
442
|
+
{value}
|
|
443
|
+
disabled={controlDisabled}
|
|
444
|
+
readOnly={field.readOnly}
|
|
445
|
+
{busy}
|
|
446
|
+
{error}
|
|
447
|
+
setValue={(next) => controller.set(field.id, next)}
|
|
448
|
+
update={(next) => controller.update(field.id, next)}
|
|
449
|
+
/>
|
|
450
|
+
{:else}
|
|
451
|
+
<Alert.Root class="ui-workspace-setting-unsupported">
|
|
452
|
+
<Alert.Title>Unavailable custom field</Alert.Title>
|
|
453
|
+
<Alert.Description>
|
|
454
|
+
The adapter for this setting is not registered.
|
|
455
|
+
</Alert.Description>
|
|
456
|
+
</Alert.Root>
|
|
457
|
+
{/if}
|
|
400
458
|
{:else if field.type === "unsupported"}
|
|
401
459
|
<Alert.Root class="ui-workspace-setting-unsupported">
|
|
402
460
|
<Alert.Title>Unsupported setting</Alert.Title>
|
|
@@ -410,7 +468,8 @@
|
|
|
410
468
|
<Button
|
|
411
469
|
id={`setting-${field.id}`}
|
|
412
470
|
variant={field.variant ?? "outline"}
|
|
413
|
-
disabled={field.
|
|
471
|
+
disabled={controller.isDisabled(field.id) || busy}
|
|
472
|
+
aria-busy={busy}
|
|
414
473
|
onclick={() => controller.runAction(field.id)}
|
|
415
474
|
>
|
|
416
475
|
{#if field.icon}<WorkspaceIcon name={field.icon} />{/if}
|
|
@@ -418,14 +477,24 @@
|
|
|
418
477
|
</Button>
|
|
419
478
|
{/if}
|
|
420
479
|
|
|
421
|
-
{#if field.type
|
|
480
|
+
{#if field.type === "action" && actionResult}
|
|
481
|
+
<p
|
|
482
|
+
class="ui-workspace-setting-action-result"
|
|
483
|
+
data-tone={actionResult.tone}
|
|
484
|
+
role={actionResult.tone === "error" ? "alert" : "status"}
|
|
485
|
+
>
|
|
486
|
+
{actionResult.message}
|
|
487
|
+
</p>
|
|
488
|
+
{/if}
|
|
489
|
+
|
|
490
|
+
{#if field.type !== "action" && field.type !== "output" && "default" in field && field.default !== undefined}
|
|
422
491
|
<Button
|
|
423
492
|
class="ui-workspace-setting-restore"
|
|
424
493
|
variant="ghost"
|
|
425
494
|
size="icon-sm"
|
|
426
495
|
aria-label={`Restore ${field.title} default`}
|
|
427
496
|
title="Restore default"
|
|
428
|
-
disabled={
|
|
497
|
+
disabled={controlDisabled}
|
|
429
498
|
onclick={() => controller.restoreDefault(field.id)}
|
|
430
499
|
>
|
|
431
500
|
<WorkspaceIcon name="rotate-ccw" />
|
|
@@ -85,7 +85,9 @@
|
|
|
85
85
|
dataUiComponent="workspace-setting-multiselect"
|
|
86
86
|
data-ui-part="trigger"
|
|
87
87
|
>
|
|
88
|
-
<span class="ui-workspace-setting-select__value"
|
|
88
|
+
<span class="ui-workspace-setting-select__value"
|
|
89
|
+
>{triggerContent}</span
|
|
90
|
+
>
|
|
89
91
|
<span data-ui-part="chevron">
|
|
90
92
|
<WorkspaceIcon name="chevrons-up-down" />
|
|
91
93
|
</span>
|
|
@@ -100,17 +102,16 @@
|
|
|
100
102
|
data-ui-component="workspace-setting-multiselect"
|
|
101
103
|
data-ui-part="content"
|
|
102
104
|
>
|
|
103
|
-
<CommandView.Root
|
|
104
|
-
shouldFilter={false}
|
|
105
|
-
label={ariaLabel ?? placeholder}
|
|
106
|
-
>
|
|
105
|
+
<CommandView.Root shouldFilter={false} label={ariaLabel ?? placeholder}>
|
|
107
106
|
<CommandView.Input
|
|
108
107
|
bind:value={query}
|
|
109
108
|
placeholder="Search options..."
|
|
110
109
|
autocomplete="off"
|
|
111
110
|
spellcheck="false"
|
|
112
111
|
/>
|
|
113
|
-
<CommandView.List
|
|
112
|
+
<CommandView.List
|
|
113
|
+
aria-label={ariaLabel ? `${ariaLabel} options` : "Options"}
|
|
114
|
+
>
|
|
114
115
|
<CommandView.Empty>No options found.</CommandView.Empty>
|
|
115
116
|
{#if filtered.length > 0}
|
|
116
117
|
<CommandView.Group>
|
|
@@ -5,13 +5,17 @@
|
|
|
5
5
|
import * as Table from "@lapismd/design-core/shadcn/table";
|
|
6
6
|
import WorkspaceIcon from "../icon/WorkspaceIcon.svelte";
|
|
7
7
|
import WorkspaceSettingAddButton from "./WorkspaceSettingAddButton.svelte";
|
|
8
|
+
import CopyableValue from "./CopyableValue.svelte";
|
|
8
9
|
import {
|
|
9
10
|
asObjectMap,
|
|
10
11
|
asObjectRows,
|
|
11
12
|
createObjectRow,
|
|
12
13
|
nextObjectMapKey,
|
|
13
14
|
} from "./object-collection.js";
|
|
14
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
WorkspaceObjectProperty,
|
|
17
|
+
WorkspaceObjectRowAction,
|
|
18
|
+
} from "./types.js";
|
|
15
19
|
|
|
16
20
|
let {
|
|
17
21
|
label,
|
|
@@ -22,6 +26,12 @@
|
|
|
22
26
|
minimumItems,
|
|
23
27
|
maximumItems,
|
|
24
28
|
addLabel,
|
|
29
|
+
rowKey,
|
|
30
|
+
reorderable = false,
|
|
31
|
+
allowAdd = true,
|
|
32
|
+
allowRemove = true,
|
|
33
|
+
stateRevision = 0,
|
|
34
|
+
rowActions = [],
|
|
25
35
|
onValueChange,
|
|
26
36
|
}: {
|
|
27
37
|
label: string;
|
|
@@ -32,9 +42,23 @@
|
|
|
32
42
|
minimumItems?: number;
|
|
33
43
|
maximumItems?: number;
|
|
34
44
|
addLabel?: string;
|
|
45
|
+
rowKey?: string;
|
|
46
|
+
reorderable?: boolean;
|
|
47
|
+
allowAdd?: boolean;
|
|
48
|
+
allowRemove?: boolean;
|
|
49
|
+
stateRevision?: number;
|
|
50
|
+
rowActions?: WorkspaceObjectRowAction[];
|
|
35
51
|
onValueChange: (value: unknown) => void;
|
|
36
52
|
} = $props();
|
|
37
53
|
|
|
54
|
+
let busyActions = $state<Record<string, boolean>>({});
|
|
55
|
+
let actionResults = $state<
|
|
56
|
+
Record<string, { tone: "success" | "warning" | "error"; message: string }>
|
|
57
|
+
>({});
|
|
58
|
+
let visibleProperties = $derived(
|
|
59
|
+
properties.filter((property) => property.presentation !== "hidden"),
|
|
60
|
+
);
|
|
61
|
+
|
|
38
62
|
let rows = $derived.by(() => {
|
|
39
63
|
if (mode === "map") {
|
|
40
64
|
return Object.entries(asObjectMap(value)).map(([key, row]) => ({
|
|
@@ -43,15 +67,22 @@
|
|
|
43
67
|
}));
|
|
44
68
|
}
|
|
45
69
|
return asObjectRows(value).map((row, index) => ({
|
|
46
|
-
key:
|
|
70
|
+
key:
|
|
71
|
+
rowKey && typeof row[rowKey] === "string" && String(row[rowKey]).trim()
|
|
72
|
+
? String(row[rowKey])
|
|
73
|
+
: String(index),
|
|
47
74
|
row,
|
|
48
75
|
}));
|
|
49
76
|
});
|
|
50
77
|
let canAdd = $derived(
|
|
51
|
-
|
|
78
|
+
allowAdd &&
|
|
79
|
+
!disabled &&
|
|
80
|
+
(maximumItems === undefined || rows.length < maximumItems),
|
|
52
81
|
);
|
|
53
82
|
let canRemove = $derived(
|
|
54
|
-
|
|
83
|
+
allowRemove &&
|
|
84
|
+
!disabled &&
|
|
85
|
+
(minimumItems === undefined || rows.length > minimumItems),
|
|
55
86
|
);
|
|
56
87
|
let resolvedAddLabel = $derived(
|
|
57
88
|
addLabel ?? (mode === "map" ? `Add ${label} entry` : `Add ${label} row`),
|
|
@@ -131,6 +162,69 @@
|
|
|
131
162
|
.map((entry) => entry.row),
|
|
132
163
|
);
|
|
133
164
|
}
|
|
165
|
+
|
|
166
|
+
function moveRow(index: number, offset: -1 | 1) {
|
|
167
|
+
if (mode !== "array" || disabled || !reorderable) return;
|
|
168
|
+
const target = index + offset;
|
|
169
|
+
if (target < 0 || target >= rows.length) return;
|
|
170
|
+
const next = rows.map((entry) => entry.row);
|
|
171
|
+
[next[index], next[target]] = [next[target]!, next[index]!];
|
|
172
|
+
emitArray(next);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function actionVisible(
|
|
176
|
+
action: WorkspaceObjectRowAction,
|
|
177
|
+
row: Record<string, unknown>,
|
|
178
|
+
): boolean {
|
|
179
|
+
return typeof action.hidden === "function"
|
|
180
|
+
? !action.hidden(row)
|
|
181
|
+
: action.hidden !== true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function actionDisabled(
|
|
185
|
+
action: WorkspaceObjectRowAction,
|
|
186
|
+
row: Record<string, unknown>,
|
|
187
|
+
key: string,
|
|
188
|
+
): boolean {
|
|
189
|
+
stateRevision;
|
|
190
|
+
const configured =
|
|
191
|
+
typeof action.disabled === "function"
|
|
192
|
+
? action.disabled(row)
|
|
193
|
+
: action.disabled === true;
|
|
194
|
+
return (
|
|
195
|
+
disabled ||
|
|
196
|
+
configured ||
|
|
197
|
+
busyActions[`${key}:${action.id}`] === true ||
|
|
198
|
+
action.busy?.(row) === true
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function runRowAction(
|
|
203
|
+
action: WorkspaceObjectRowAction,
|
|
204
|
+
row: Record<string, unknown>,
|
|
205
|
+
index: number,
|
|
206
|
+
key: string,
|
|
207
|
+
): Promise<void> {
|
|
208
|
+
if (actionDisabled(action, row, key)) return;
|
|
209
|
+
if (action.confirm && !(await action.confirm(row))) return;
|
|
210
|
+
const actionKey = `${key}:${action.id}`;
|
|
211
|
+
delete actionResults[actionKey];
|
|
212
|
+
busyActions[actionKey] = true;
|
|
213
|
+
try {
|
|
214
|
+
const result = await action.run(row, index);
|
|
215
|
+
if (result) actionResults[actionKey] = result;
|
|
216
|
+
} catch (caught) {
|
|
217
|
+
actionResults[actionKey] = {
|
|
218
|
+
tone: "error",
|
|
219
|
+
message:
|
|
220
|
+
caught instanceof Error
|
|
221
|
+
? caught.message
|
|
222
|
+
: "Unable to run this action",
|
|
223
|
+
};
|
|
224
|
+
} finally {
|
|
225
|
+
busyActions[actionKey] = false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
134
228
|
</script>
|
|
135
229
|
|
|
136
230
|
<div class="ui-workspace-setting-object-collection">
|
|
@@ -140,14 +234,14 @@
|
|
|
140
234
|
{#if mode === "map"}
|
|
141
235
|
<Table.Head>Name</Table.Head>
|
|
142
236
|
{/if}
|
|
143
|
-
{#each
|
|
237
|
+
{#each visibleProperties as property (property.id)}
|
|
144
238
|
<Table.Head>{property.title}</Table.Head>
|
|
145
239
|
{/each}
|
|
146
240
|
<Table.Head>Actions</Table.Head>
|
|
147
241
|
</Table.Row>
|
|
148
242
|
</Table.Header>
|
|
149
243
|
<Table.Body>
|
|
150
|
-
{#each rows as entry, index (
|
|
244
|
+
{#each rows as entry, index (entry.key)}
|
|
151
245
|
<Table.Row>
|
|
152
246
|
{#if mode === "map"}
|
|
153
247
|
<Table.Cell>
|
|
@@ -160,19 +254,40 @@
|
|
|
160
254
|
/>
|
|
161
255
|
</Table.Cell>
|
|
162
256
|
{/if}
|
|
163
|
-
{#each
|
|
257
|
+
{#each visibleProperties as property (property.id)}
|
|
164
258
|
<Table.Cell>
|
|
165
259
|
{#if property.type === "boolean"}
|
|
166
260
|
<Switch
|
|
167
261
|
checked={Boolean(entry.row[property.id])}
|
|
168
|
-
{disabled}
|
|
262
|
+
disabled={disabled || property.readOnly}
|
|
169
263
|
aria-label={`${label} ${property.title} row ${index + 1}`}
|
|
170
264
|
onCheckedChange={(checked) =>
|
|
171
265
|
updateCell(index, property.id, checked)}
|
|
172
266
|
/>
|
|
267
|
+
{:else if property.presentation === "copyable"}
|
|
268
|
+
<CopyableValue
|
|
269
|
+
value={String(entry.row[property.id] ?? "")}
|
|
270
|
+
label={`${label} ${property.title}`}
|
|
271
|
+
/>
|
|
272
|
+
{:else if property.presentation === "code"}
|
|
273
|
+
<code class="ui-workspace-setting-object-code"
|
|
274
|
+
>{String(entry.row[property.id] ?? "")}</code
|
|
275
|
+
>
|
|
276
|
+
{:else if property.presentation === "status"}
|
|
277
|
+
<span class="ui-workspace-setting-object-status"
|
|
278
|
+
>{String(entry.row[property.id] ?? "")}</span
|
|
279
|
+
>
|
|
280
|
+
{:else if property.readOnly}
|
|
281
|
+
<span class="ui-workspace-setting-object-value"
|
|
282
|
+
>{String(entry.row[property.id] ?? "")}</span
|
|
283
|
+
>
|
|
173
284
|
{:else}
|
|
174
285
|
<Input
|
|
175
|
-
type={property.type === "string"
|
|
286
|
+
type={property.type === "string"
|
|
287
|
+
? property.presentation === "url"
|
|
288
|
+
? "url"
|
|
289
|
+
: "text"
|
|
290
|
+
: "number"}
|
|
176
291
|
step={property.type === "integer" ? 1 : "any"}
|
|
177
292
|
aria-label={`${label} ${property.title} row ${index + 1}`}
|
|
178
293
|
value={String(entry.row[property.id] ?? "")}
|
|
@@ -190,23 +305,72 @@
|
|
|
190
305
|
</Table.Cell>
|
|
191
306
|
{/each}
|
|
192
307
|
<Table.Cell>
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
308
|
+
<div class="ui-workspace-setting-object-actions">
|
|
309
|
+
{#if mode === "array" && reorderable}
|
|
310
|
+
<Button
|
|
311
|
+
variant="ghost"
|
|
312
|
+
size="icon-sm"
|
|
313
|
+
aria-label={`Move ${label} row ${index + 1} up`}
|
|
314
|
+
disabled={disabled || index === 0}
|
|
315
|
+
onclick={() => moveRow(index, -1)}
|
|
316
|
+
>
|
|
317
|
+
<WorkspaceIcon name="arrow-up" />
|
|
318
|
+
</Button>
|
|
319
|
+
<Button
|
|
320
|
+
variant="ghost"
|
|
321
|
+
size="icon-sm"
|
|
322
|
+
aria-label={`Move ${label} row ${index + 1} down`}
|
|
323
|
+
disabled={disabled || index === rows.length - 1}
|
|
324
|
+
onclick={() => moveRow(index, 1)}
|
|
325
|
+
>
|
|
326
|
+
<WorkspaceIcon name="arrow-down" />
|
|
327
|
+
</Button>
|
|
328
|
+
{/if}
|
|
329
|
+
{#each rowActions.filter( (action) => actionVisible(action, entry.row), ) as action (action.id)}
|
|
330
|
+
<Button
|
|
331
|
+
variant={action.variant ?? "ghost"}
|
|
332
|
+
size={action.icon ? "icon-sm" : "sm"}
|
|
333
|
+
aria-label={action.label}
|
|
334
|
+
title={action.label}
|
|
335
|
+
disabled={actionDisabled(action, entry.row, entry.key)}
|
|
336
|
+
onclick={() =>
|
|
337
|
+
void runRowAction(action, entry.row, index, entry.key)}
|
|
338
|
+
>
|
|
339
|
+
{#if action.icon}<WorkspaceIcon name={action.icon} />{/if}
|
|
340
|
+
{#if !action.icon}{action.label}{/if}
|
|
341
|
+
</Button>
|
|
342
|
+
{@const result = actionResults[`${entry.key}:${action.id}`]}
|
|
343
|
+
{#if result}
|
|
344
|
+
<span
|
|
345
|
+
class="ui-workspace-setting-object-action-result"
|
|
346
|
+
data-tone={result.tone}
|
|
347
|
+
role={result.tone === "error" ? "alert" : "status"}
|
|
348
|
+
>{result.message}</span
|
|
349
|
+
>
|
|
350
|
+
{/if}
|
|
351
|
+
{/each}
|
|
352
|
+
{#if !disabled && allowRemove}
|
|
353
|
+
<Button
|
|
354
|
+
variant="ghost"
|
|
355
|
+
size="icon-sm"
|
|
356
|
+
aria-label={`Remove ${label} row ${index + 1}`}
|
|
357
|
+
disabled={!canRemove}
|
|
358
|
+
onclick={() => removeRow(index)}
|
|
359
|
+
>
|
|
360
|
+
<WorkspaceIcon name="x" />
|
|
361
|
+
</Button>
|
|
362
|
+
{/if}
|
|
363
|
+
</div>
|
|
202
364
|
</Table.Cell>
|
|
203
365
|
</Table.Row>
|
|
204
366
|
{/each}
|
|
205
367
|
</Table.Body>
|
|
206
368
|
</Table.Root>
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
369
|
+
{#if !disabled && allowAdd}
|
|
370
|
+
<WorkspaceSettingAddButton
|
|
371
|
+
label={resolvedAddLabel}
|
|
372
|
+
disabled={!canAdd}
|
|
373
|
+
onclick={addRow}
|
|
374
|
+
/>
|
|
375
|
+
{/if}
|
|
212
376
|
</div>
|