@svadmin/ui 0.70.0 → 0.72.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/actions.js +1 -1
- package/dist/app.css +102 -63
- package/dist/app.theme.css +102 -63
- package/dist/component-registry.svelte.d.ts +33 -19
- package/dist/component-registry.svelte.js +6 -4
- package/dist/components/AdminApp.svelte +55 -53
- package/dist/components/AdminApp.svelte.d.ts +2 -2
- package/dist/components/ApiSettings.svelte +19 -1
- package/dist/components/AuditLogDrawer.svelte +14 -3
- package/dist/components/AutoForm.svelte +146 -48
- package/dist/components/AutoForm.svelte.d.ts +2 -0
- package/dist/components/AutoTable.svelte +358 -258
- package/dist/components/AutoTable.svelte.d.ts +1 -0
- package/dist/components/BoundRecordDetailDrawer.svelte +166 -0
- package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +9 -0
- package/dist/components/Breadcrumbs.svelte +7 -4
- package/dist/components/CancelTaskButton.svelte +9 -5
- package/dist/components/ColumnHeaderFilter.svelte +4 -3
- package/dist/components/ColumnSettings.svelte +1 -0
- package/dist/components/ComboboxField.svelte +90 -30
- package/dist/components/ComboboxField.svelte.d.ts +2 -2
- package/dist/components/CreatePage.svelte +3 -1
- package/dist/components/DetailDrawer.svelte +4 -0
- package/dist/components/DetailDrawer.svelte.d.ts +1 -0
- package/dist/components/DevTools.svelte +6 -19
- package/dist/components/DraggableGrid.svelte +1 -0
- package/dist/components/DraggableHeader.svelte +5 -9
- package/dist/components/DraggableHeader.svelte.d.ts +43 -18
- package/dist/components/DraggableRowTable.svelte +2 -2
- package/dist/components/DynamicFormList.svelte +17 -17
- package/dist/components/DynamicFormList.svelte.d.ts +2 -1
- package/dist/components/EditPage.svelte +3 -1
- package/dist/components/EditableTable.svelte +4 -0
- package/dist/components/ErrorPage.svelte +3 -1
- package/dist/components/FieldDisplay.svelte +14 -0
- package/dist/components/FieldDisplay.svelte.d.ts +7 -0
- package/dist/components/FieldRenderer.svelte +9 -5
- package/dist/components/FilterBuilder.svelte +19 -8
- package/dist/components/FilterDropdown.svelte.d.ts +1 -1
- package/dist/components/Header.svelte +5 -6
- package/dist/components/ImportWizard.svelte +3 -2
- package/dist/components/InfiniteList.svelte +22 -8
- package/dist/components/InfiniteList.svelte.d.ts +5 -3
- package/dist/components/InlineEdit.svelte +114 -44
- package/dist/components/JsonSchemaForm.svelte +19 -14
- package/dist/components/Layout.svelte +19 -15
- package/dist/components/LazyTaskQueueDrawer.svelte +26 -0
- package/dist/components/LazyTaskQueueDrawer.svelte.d.ts +8 -0
- package/dist/components/ListPage.svelte +7 -6
- package/dist/components/MasterDetailView.svelte +3 -3
- package/dist/components/MediaLibraryModal.svelte +5 -2
- package/dist/components/MentionsInput.svelte +4 -3
- package/dist/components/NavigateToResource.svelte +3 -2
- package/dist/components/PermissionMatrix.svelte +4 -3
- package/dist/components/ProfilePage.svelte +2 -2
- package/dist/components/QuickEditDrawer.svelte +39 -0
- package/dist/components/QuickEditDrawer.svelte.d.ts +8 -0
- package/dist/components/RecordDetailDrawer.svelte +14 -108
- package/dist/components/RecordRowActions.svelte +41 -0
- package/dist/components/RecordRowActions.svelte.d.ts +14 -0
- package/dist/components/ResourceAccessGuard.svelte +7 -5
- package/dist/components/RetryTaskButton.svelte +9 -5
- package/dist/components/RowActions.svelte +5 -3
- package/dist/components/SensitiveDataMask.svelte +3 -3
- package/dist/components/SettingsPage.svelte +1 -1
- package/dist/components/ShowPage.svelte +44 -38
- package/dist/components/Sidebar.svelte +10 -8
- package/dist/components/SplitPaneLayout.svelte +4 -2
- package/dist/components/StepsForm.svelte +161 -44
- package/dist/components/StepsForm.svelte.d.ts +1 -0
- package/dist/components/TaskDetails.svelte +20 -7
- package/dist/components/TaskList.svelte +19 -7
- package/dist/components/TaskQueueDrawer.svelte +21 -22
- package/dist/components/Toast.svelte +4 -2
- package/dist/components/TooltipButton.svelte +1 -1
- package/dist/components/TreeSelect.svelte +4 -2
- package/dist/components/ValidatedField.svelte +27 -0
- package/dist/components/ValidatedField.svelte.d.ts +35 -0
- package/dist/components/account/ImportMembersPage.svelte +2 -2
- package/dist/components/account/SecurityLogPage.svelte +3 -1
- package/dist/components/account/SettingsEnterprisePage.svelte +7 -5
- package/dist/components/account/TeamMembersPage.svelte +1 -1
- package/dist/components/buttons/CloneButton.svelte +6 -4
- package/dist/components/buttons/DeleteButton.svelte +95 -29
- package/dist/components/buttons/DeleteButton.svelte.d.ts +2 -0
- package/dist/components/buttons/EditButton.svelte +6 -4
- package/dist/components/buttons/ExportButton.svelte +22 -4
- package/dist/components/buttons/ImportButton.svelte +83 -38
- package/dist/components/buttons/ImportButton.svelte.d.ts +6 -10
- package/dist/components/buttons/RefreshButton.svelte +48 -8
- package/dist/components/buttons/ShowButton.svelte +6 -4
- package/dist/components/content/ApiKeyList.svelte +3 -1
- package/dist/components/content/ContentPageShell.svelte +3 -1
- package/dist/components/content/FileList.svelte +3 -1
- package/dist/components/content/MemberList.svelte +3 -1
- package/dist/components/content/NetworkTable.svelte +3 -1
- package/dist/components/content/SecurityEventTable.svelte +3 -1
- package/dist/components/content/StatusTabs.svelte +2 -2
- package/dist/components/fieldComponentMap.d.ts +18 -13
- package/dist/components/fieldComponentMap.js +95 -47
- package/dist/components/fields/AvatarField.svelte +1 -1
- package/dist/components/fields/ChoiceField.svelte +16 -0
- package/dist/components/fields/ChoiceField.svelte.d.ts +10 -0
- package/dist/components/fields/CodeField.svelte +11 -12
- package/dist/components/fields/CodeField.svelte.d.ts +1 -1
- package/dist/components/fields/DateRangeField.svelte +2 -4
- package/dist/components/fields/ImageField.svelte +3 -1
- package/dist/components/fields/ImagesField.svelte +23 -0
- package/dist/components/fields/ImagesField.svelte.d.ts +6 -0
- package/dist/components/fields/JsonField.svelte +19 -13
- package/dist/components/fields/PasswordField.svelte +5 -0
- package/dist/components/fields/PasswordField.svelte.d.ts +6 -0
- package/dist/components/fields/json-value.d.ts +11 -0
- package/dist/components/fields/json-value.js +28 -0
- package/dist/components/profile/ActivityTimeline.svelte +2 -2
- package/dist/components/record-detail.svelte.d.ts +16 -0
- package/dist/components/record-detail.svelte.js +28 -0
- package/dist/components/saved-list-views.js +29 -29
- package/dist/components/table-contract.d.ts +13 -0
- package/dist/components/table-contract.js +42 -0
- package/dist/components/task-utils.js +4 -2
- package/dist/components/ui/alert/alert-variants.d.ts +2 -3
- package/dist/components/ui/alert/alert-variants.js +1 -2
- package/dist/components/ui/alert/alert.svelte +4 -5
- package/dist/components/ui/alert/alert.svelte.d.ts +3 -8
- package/dist/components/ui/alert/index.js +0 -2
- package/dist/components/ui/avatar/avatar-variants.d.ts +2 -3
- package/dist/components/ui/avatar/avatar-variants.js +1 -2
- package/dist/components/ui/avatar/avatar.svelte +5 -6
- package/dist/components/ui/avatar/avatar.svelte.d.ts +3 -8
- package/dist/components/ui/badge/badge-variants.d.ts +2 -3
- package/dist/components/ui/badge/badge-variants.js +1 -2
- package/dist/components/ui/badge/badge.svelte +4 -5
- package/dist/components/ui/badge/badge.svelte.d.ts +3 -8
- package/dist/components/ui/badge/index.js +0 -2
- package/dist/components/ui/button/button-variants.d.ts +2 -3
- package/dist/components/ui/button/button-variants.js +1 -2
- package/dist/components/ui/button/button.svelte +100 -44
- package/dist/components/ui/button/button.svelte.d.ts +8 -12
- package/dist/components/ui/button/index.js +0 -2
- package/dist/components/ui/command/command-dialog.svelte +3 -1
- package/dist/components/ui/command/command-dialog.svelte.d.ts +1 -1
- package/dist/components/ui/command/command-group.svelte +4 -2
- package/dist/components/ui/command/command-item.svelte +4 -2
- package/dist/components/ui/command/command-separator.svelte +3 -1
- package/dist/components/ui/command/index.d.ts +5 -5
- package/dist/components/ui/input/input.svelte +40 -20
- package/dist/components/ui/input/input.svelte.d.ts +5 -4
- package/dist/components/ui/sheet/sheet.svelte +9 -3
- package/dist/components/ui/sheet/sheet.svelte.d.ts +1 -0
- package/dist/components/ui/tooltip/tooltip.svelte +4 -2
- package/dist/index.d.ts +1 -1
- package/dist/inline-value.d.ts +5 -0
- package/dist/inline-value.js +24 -0
- package/dist/numeric-input.d.ts +2 -0
- package/dist/numeric-input.js +9 -0
- package/dist/types.d.ts +3 -3
- package/package.json +11 -5
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions, definedReactiveOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import { fly } from 'svelte/transition';
|
|
3
|
-
import {
|
|
5
|
+
import { tick } from 'svelte';
|
|
6
|
+
import { captureAdminContext, captureAuthSession, deriveValidator, useStepsForm, useResourceContract, getContractFormFields, useCan } from '@svadmin/core';
|
|
4
7
|
import type { FieldDefinition } from '@svadmin/core';
|
|
5
|
-
import { getResource } from '@svadmin/core';
|
|
6
8
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
7
9
|
|
|
8
10
|
import { Button } from './ui/button/index.js';
|
|
@@ -11,6 +13,8 @@
|
|
|
11
13
|
import { Progress } from './ui/progress/index.js';
|
|
12
14
|
import { Save, ArrowLeft, ArrowRight, Check, Loader2 } from '@lucide/svelte';
|
|
13
15
|
import FieldRenderer from './FieldRenderer.svelte';
|
|
16
|
+
import ConfirmDialog from './ConfirmDialog.svelte';
|
|
17
|
+
import { Skeleton } from './ui/skeleton/index.js';
|
|
14
18
|
|
|
15
19
|
const i18n = useTranslation();
|
|
16
20
|
|
|
@@ -24,18 +28,26 @@
|
|
|
24
28
|
id?: string | number;
|
|
25
29
|
mode?: 'create' | 'edit';
|
|
26
30
|
steps: StepDef[];
|
|
31
|
+
onSuccess?: () => void;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
let { resourceName, id, mode = 'create', steps }: Props = $props();
|
|
34
|
+
let { resourceName, id, mode = 'create', steps, onSuccess }: Props = $props();
|
|
30
35
|
const adminContext = captureAdminContext();
|
|
36
|
+
const binding = useResourceContract(() => resourceName);
|
|
31
37
|
|
|
32
|
-
const resource = $derived(getResource(resourceName));
|
|
38
|
+
const resource = $derived(adminContext.getResource(resourceName));
|
|
33
39
|
const primaryKey = $derived(resource.primaryKey ?? 'id');
|
|
40
|
+
const writableFields = $derived(getContractFormFields(binding.resource, mode));
|
|
41
|
+
const permission = useCan(() => definedOptions({ resource: resourceName, action: mode, id }));
|
|
42
|
+
const allowed = $derived(permission.allowed && (mode === 'create' ? resource.canCreate !== false : resource.canEdit !== false));
|
|
34
43
|
|
|
35
44
|
// All fields used across all steps
|
|
36
45
|
const flatStepsFields = $derived(steps.flatMap(s => s.fields)
|
|
37
46
|
.map(key => resource.fields.find(f => f.key === key))
|
|
38
|
-
.filter((f): f is FieldDefinition => f != null));
|
|
47
|
+
.filter((f): f is FieldDefinition => f != null && writableFields.includes(f.key)));
|
|
48
|
+
const configurationValid = $derived(steps.every(step => step.fields.every(key =>
|
|
49
|
+
flatStepsFields.some(field => field.key === key && key !== primaryKey && field.showInForm !== false &&
|
|
50
|
+
(mode === 'create' ? field.showInCreate !== false : field.showInEdit !== false)))));
|
|
39
51
|
|
|
40
52
|
// Default values from field metadata
|
|
41
53
|
function getDefaultForType(field: FieldDefinition): unknown {
|
|
@@ -57,61 +69,152 @@
|
|
|
57
69
|
})());
|
|
58
70
|
|
|
59
71
|
const validator = $derived(deriveValidator(flatStepsFields, { translate: i18n.t }));
|
|
60
|
-
const stepsCount = $derived(steps.length);
|
|
61
72
|
|
|
62
73
|
// ─── useStepsForm: form state + step navigation ──────────────────
|
|
63
|
-
const form = useStepsForm({
|
|
64
|
-
get resource() { return
|
|
74
|
+
const form = useStepsForm(definedReactiveOptions({
|
|
75
|
+
get resource() { return binding.resource; },
|
|
65
76
|
get action() { return mode; },
|
|
66
77
|
get id() { return id; },
|
|
67
78
|
get defaultValues() { return defaults; },
|
|
68
|
-
get
|
|
79
|
+
get steps() { return steps.map(step => ({ fields: step.fields })); },
|
|
80
|
+
get enabled() { return allowed && configurationValid; },
|
|
81
|
+
get dataProviderName() { return binding.dataProviderName; },
|
|
69
82
|
redirect: 'list',
|
|
70
83
|
warnWhenUnsavedChanges: true,
|
|
71
84
|
get validate() { return validator; },
|
|
72
|
-
|
|
85
|
+
get onMutationSuccess() {
|
|
86
|
+
const callback = onSuccess;
|
|
87
|
+
return callback ? () => callback() : undefined;
|
|
88
|
+
},
|
|
89
|
+
}));
|
|
73
90
|
|
|
74
|
-
const totalSteps = $derived(steps.
|
|
75
|
-
const progressValue = $derived(((form.steps.currentStep + 1) / totalSteps) * 100);
|
|
91
|
+
const totalSteps = $derived(form.steps.totalSteps);
|
|
92
|
+
const progressValue = $derived(totalSteps > 0 ? ((form.steps.currentStep + 1) / totalSteps) * 100 : 0);
|
|
76
93
|
const isLastStep = $derived(form.steps.currentStep >= totalSteps - 1);
|
|
77
94
|
|
|
78
95
|
// Fields for the current step
|
|
79
96
|
const currentFields = $derived(
|
|
80
97
|
steps[form.steps.currentStep]?.fields
|
|
81
98
|
.map(key => resource.fields.find(f => f.key === key))
|
|
82
|
-
.filter((f): f is FieldDefinition => f != null && f.key !== primaryKey) ?? []
|
|
99
|
+
.filter((f): f is FieldDefinition => f != null && f.key !== primaryKey && writableFields.includes(f.key)) ?? []
|
|
83
100
|
);
|
|
84
101
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
102
|
+
let element = $state.raw<HTMLFormElement>();
|
|
103
|
+
let submitError = $state<string | null>(null);
|
|
104
|
+
let submission = $state.raw<object>();
|
|
105
|
+
const scope = $derived({
|
|
106
|
+
contract: binding.resource, id, mode, allowed, configurationValid,
|
|
107
|
+
steps: JSON.stringify(steps), provider: adminContext.providers?.[binding.dataProviderName],
|
|
108
|
+
meta: binding.meta, tenant: adminContext.tenantCacheKey?.__svadminTenant,
|
|
109
|
+
auth: adminContext.authProvider, router: adminContext.routerProvider,
|
|
110
|
+
session: captureAuthSession(adminContext.authProvider),
|
|
111
|
+
});
|
|
112
|
+
const rendered = $derived({ scope, step: form.steps.currentStep });
|
|
113
|
+
let mounted = true;
|
|
114
|
+
let confirmation = $state.raw<typeof rendered>();
|
|
115
|
+
const currentRendered = (origin: typeof rendered) => mounted && origin === rendered &&
|
|
116
|
+
origin.scope.session.isCurrent() && form.ready;
|
|
117
|
+
const confirmOpen = $derived(confirmation !== undefined && currentRendered(confirmation));
|
|
118
|
+
$effect(() => {
|
|
119
|
+
void scope;
|
|
120
|
+
submitError = null;
|
|
121
|
+
submission = undefined;
|
|
122
|
+
confirmation = undefined;
|
|
123
|
+
});
|
|
124
|
+
$effect(() => () => { mounted = false; submission = undefined; confirmation = undefined; });
|
|
125
|
+
function errorId(field: string): string { return `${resourceName}-step-${field}-error`; }
|
|
126
|
+
async function focusInvalid(origin: typeof scope) {
|
|
127
|
+
await tick();
|
|
128
|
+
if (mounted && scope === origin && origin.session.isCurrent() && element?.isConnected) {
|
|
129
|
+
element.querySelector<HTMLElement>('[aria-invalid="true"]')?.focus();
|
|
97
130
|
}
|
|
98
|
-
return valid;
|
|
99
131
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
form.steps.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
132
|
+
const changeStep = $derived.by(() => {
|
|
133
|
+
const origin = rendered;
|
|
134
|
+
return (step: number) => () => {
|
|
135
|
+
if (!currentRendered(origin) || !allowed || form.submitting) return;
|
|
136
|
+
submitError = null;
|
|
137
|
+
if (!form.steps.gotoStep(step)) void focusInvalid(origin.scope);
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
const fieldChange = $derived.by(() => {
|
|
141
|
+
const origin = rendered;
|
|
142
|
+
return (key: string) => (value: unknown) => {
|
|
143
|
+
if (currentRendered(origin)) form.setFieldValue(key, value);
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
function leave(origin: typeof rendered) {
|
|
147
|
+
if (!currentRendered(origin)) return;
|
|
148
|
+
confirmation = undefined;
|
|
149
|
+
void adminContext.navigate(`/${encodeURIComponent(resourceName)}`).catch(() => {
|
|
150
|
+
if (currentRendered(origin) && element?.isConnected) submitError = i18n.t('common.operationFailed');
|
|
151
|
+
});
|
|
111
152
|
}
|
|
153
|
+
const back = $derived.by(() => {
|
|
154
|
+
const origin = rendered;
|
|
155
|
+
const previous = changeStep(origin.step - 1);
|
|
156
|
+
return () => {
|
|
157
|
+
if (!currentRendered(origin) || form.submitting) return;
|
|
158
|
+
if (origin.step > 0) previous();
|
|
159
|
+
else if (form.isDirty) confirmation = origin;
|
|
160
|
+
else leave(origin);
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
const confirmLeave = $derived.by(() => {
|
|
164
|
+
const origin = confirmation;
|
|
165
|
+
return () => { if (origin && confirmation === origin) leave(origin); };
|
|
166
|
+
});
|
|
167
|
+
const cancelLeave = $derived.by(() => {
|
|
168
|
+
const origin = confirmation;
|
|
169
|
+
return () => { if (origin && confirmation === origin) confirmation = undefined; };
|
|
170
|
+
});
|
|
171
|
+
const retry = $derived.by(() => {
|
|
172
|
+
const origin = scope;
|
|
173
|
+
const refetch = form.query.refetch;
|
|
174
|
+
return () => {
|
|
175
|
+
if (mounted && scope === origin && origin.session.isCurrent()) void refetch().catch(() => {});
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const handleSubmit = $derived.by(() => {
|
|
180
|
+
const origin = rendered;
|
|
181
|
+
return async () => {
|
|
182
|
+
if (!currentRendered(origin) || !allowed || form.submitting || submission) return;
|
|
183
|
+
submitError = null;
|
|
184
|
+
if (!isLastStep) {
|
|
185
|
+
if (!form.steps.nextStep()) void focusInvalid(origin.scope);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const token = {};
|
|
189
|
+
submission = token;
|
|
190
|
+
try { await form.submit(); }
|
|
191
|
+
catch {
|
|
192
|
+
if (submission === token && mounted && scope === origin.scope && origin.scope.session.isCurrent()) {
|
|
193
|
+
submitError = i18n.t('common.operationFailed');
|
|
194
|
+
void focusInvalid(origin.scope);
|
|
195
|
+
}
|
|
196
|
+
} finally {
|
|
197
|
+
if (submission === token) submission = undefined;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
const submitEvent = $derived.by(() => {
|
|
202
|
+
const submit = handleSubmit;
|
|
203
|
+
return (event: Event) => { event.preventDefault(); void submit(); };
|
|
204
|
+
});
|
|
112
205
|
</script>
|
|
113
206
|
|
|
114
207
|
<div class="svadmin-u-b3542e058833">
|
|
208
|
+
{#if permission.isLoading || form.loading}
|
|
209
|
+
<Skeleton class="svadmin-u-426b8b75185b svadmin-u-6da6a3c3f741" />
|
|
210
|
+
{:else if !allowed || !configurationValid || (!form.ready && form.error)}
|
|
211
|
+
<p role="alert">{i18n.t('common.operationFailed')}</p>
|
|
212
|
+
{#if allowed && configurationValid && form.query.isError}
|
|
213
|
+
<Button type="button" variant="outline" onclick={retry}>
|
|
214
|
+
{i18n.t('common.retry')}
|
|
215
|
+
</Button>
|
|
216
|
+
{/if}
|
|
217
|
+
{:else if form.ready}
|
|
115
218
|
<!-- Step indicators -->
|
|
116
219
|
<div class="svadmin-u-6ed543e2fbbb">
|
|
117
220
|
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-fc7473ca09eb">
|
|
@@ -120,7 +223,9 @@
|
|
|
120
223
|
variant="ghost"
|
|
121
224
|
type="button"
|
|
122
225
|
class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4 svadmin-u-ceb69a6b0e5f svadmin-u-b8f0a08ece1e svadmin-u-660d2effb880 svadmin-u-d5eab218aa34 {i === form.steps.currentStep ? 'svadmin-u-20aaf08a7ed1 svadmin-u-e83a7042bc91' : 'svadmin-u-bfa603190748'}"
|
|
123
|
-
|
|
226
|
+
aria-current={i === form.steps.currentStep ? 'step' : undefined}
|
|
227
|
+
disabled={form.submitting || !!submission}
|
|
228
|
+
onclick={changeStep(i)}
|
|
124
229
|
>
|
|
125
230
|
<span
|
|
126
231
|
class="svadmin-u-60fbb7713999 svadmin-u-d0a52b312f7d svadmin-u-cbbf90f9a828 svadmin-u-3960ffc248d9 svadmin-u-86843cf1e227 svadmin-u-ac204c108886 svadmin-u-359090c2d529 svadmin-u-2689f3958069 svadmin-u-ceb69a6b0e5f"
|
|
@@ -154,16 +259,21 @@
|
|
|
154
259
|
</Badge>
|
|
155
260
|
</Card.CardHeader>
|
|
156
261
|
<Card.CardContent>
|
|
157
|
-
{#key
|
|
158
|
-
<form class="svadmin-u-b43b4c086d9a" onsubmit={
|
|
262
|
+
{#key rendered}
|
|
263
|
+
<form bind:this={element} class="svadmin-u-b43b4c086d9a" novalidate onsubmit={submitEvent} in:fly={{ x: 30, duration: 250 }}>
|
|
264
|
+
{#if submitError || form.error}
|
|
265
|
+
<p role="alert">{submitError ?? i18n.t('common.operationFailed')}</p>
|
|
266
|
+
{/if}
|
|
159
267
|
{#each currentFields as field (field.key)}
|
|
160
268
|
<FieldRenderer
|
|
161
269
|
{field}
|
|
162
270
|
value={form.values[field.key]}
|
|
163
|
-
onchange={(
|
|
271
|
+
onchange={fieldChange(field.key)}
|
|
272
|
+
invalid={!!form.errors[field.key]}
|
|
273
|
+
{...definedOptions({ errorId: form.errors[field.key] ? errorId(field.key) : undefined })}
|
|
164
274
|
/>
|
|
165
275
|
{#if form.errors[field.key]}
|
|
166
|
-
<p class="svadmin-u-811148b13d1e svadmin-u-1d5904e7e755 svadmin-u-b6b02c0ebef6">{form.errors[field.key]}</p>
|
|
276
|
+
<p id={errorId(field.key)} role="alert" aria-live="polite" class="svadmin-u-811148b13d1e svadmin-u-1d5904e7e755 svadmin-u-b6b02c0ebef6">{form.errors[field.key]}</p>
|
|
167
277
|
{/if}
|
|
168
278
|
{/each}
|
|
169
279
|
|
|
@@ -171,13 +281,14 @@
|
|
|
171
281
|
<Button
|
|
172
282
|
type="button"
|
|
173
283
|
variant="outline"
|
|
174
|
-
|
|
284
|
+
disabled={form.submitting || !!submission}
|
|
285
|
+
onclick={back}
|
|
175
286
|
>
|
|
176
287
|
<ArrowLeft class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" data-icon="inline-start" />
|
|
177
288
|
{form.steps.currentStep === 0 ? i18n.t('common.cancel') : i18n.t('common.back')}
|
|
178
289
|
</Button>
|
|
179
290
|
|
|
180
|
-
<Button type="submit" disabled={form.submitting}>
|
|
291
|
+
<Button type="submit" disabled={form.submitting || !!submission}>
|
|
181
292
|
{#if form.submitting}
|
|
182
293
|
<Loader2 class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3 svadmin-u-afbdd13a380e" data-icon="inline-start" />
|
|
183
294
|
{:else if isLastStep}
|
|
@@ -192,4 +303,10 @@
|
|
|
192
303
|
{/key}
|
|
193
304
|
</Card.CardContent>
|
|
194
305
|
</Card.Root>
|
|
306
|
+
{/if}
|
|
195
307
|
</div>
|
|
308
|
+
|
|
309
|
+
{#key confirmation}
|
|
310
|
+
<ConfirmDialog open={confirmOpen} message={i18n.t('common.unsavedChanges')}
|
|
311
|
+
onconfirm={confirmLeave} oncancel={cancelLeave} />
|
|
312
|
+
{/key}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import { useTask, getTaskProvider } from '@svadmin/core';
|
|
5
|
+
import { decodeTaskRecord } from '@svadmin/core/schema';
|
|
3
6
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
4
7
|
|
|
5
8
|
import type { TaskProvider, TaskRecord } from '@svadmin/core';
|
|
@@ -27,7 +30,7 @@
|
|
|
27
30
|
let {
|
|
28
31
|
task,
|
|
29
32
|
taskId,
|
|
30
|
-
taskProvider = getTaskProvider({ optional: true })
|
|
33
|
+
taskProvider = getTaskProvider({ optional: true }) ?? undefined,
|
|
31
34
|
title,
|
|
32
35
|
useProviderData = !task && !!taskId,
|
|
33
36
|
queryOptions,
|
|
@@ -59,13 +62,21 @@
|
|
|
59
62
|
},
|
|
60
63
|
});
|
|
61
64
|
|
|
62
|
-
const
|
|
65
|
+
const view = $derived.by(() => {
|
|
66
|
+
const value = task === undefined ? query.data : task;
|
|
67
|
+
try {
|
|
68
|
+
return { data: value === undefined ? undefined : decodeTaskRecord(value, taskId), invalid: false };
|
|
69
|
+
} catch {
|
|
70
|
+
return { data: undefined, invalid: true };
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
const resolvedTask = $derived(view.data);
|
|
63
74
|
const resolvedTitle = $derived(title ?? i18n.t('task.detailsTitle'));
|
|
64
75
|
|
|
65
76
|
function formatDate(value: unknown) {
|
|
66
|
-
if (!value) return '—';
|
|
67
|
-
const date = new Date(value
|
|
68
|
-
if (Number.isNaN(date.getTime())) return
|
|
77
|
+
if (typeof value !== 'string' || !value) return '—';
|
|
78
|
+
const date = new Date(value);
|
|
79
|
+
if (Number.isNaN(date.getTime())) return '—';
|
|
69
80
|
return date.toLocaleString();
|
|
70
81
|
}
|
|
71
82
|
|
|
@@ -90,7 +101,9 @@
|
|
|
90
101
|
</Card.Description>
|
|
91
102
|
</Card.Header>
|
|
92
103
|
<Card.Content class="svadmin-u-b43b4c086d9a svadmin-u-9335c39f6eff">
|
|
93
|
-
{#if
|
|
104
|
+
{#if view.invalid || (useProviderData && query.isError)}
|
|
105
|
+
<p role="alert">{i18n.t('validation.invalidFormat')}</p>
|
|
106
|
+
{:else if useProviderData && taskProvider && query.isLoading}
|
|
94
107
|
<div class="svadmin-u-60fbb7713999 svadmin-u-aadad6871af8 svadmin-u-3960ffc248d9 svadmin-u-86843cf1e227 svadmin-u-bfa603190748">
|
|
95
108
|
<Loader2 class="svadmin-u-d2347e8497a9 svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3 svadmin-u-afbdd13a380e" />
|
|
96
109
|
{i18n.t('task.loadingDetail')}
|
|
@@ -102,7 +115,7 @@
|
|
|
102
115
|
</div>
|
|
103
116
|
|
|
104
117
|
{#if typeof resolveTaskProgress(resolvedTask) === 'number'}
|
|
105
|
-
<TaskProgressBar value
|
|
118
|
+
<TaskProgressBar {...definedOptions({ "value": resolveTaskProgress(resolvedTask) })} />
|
|
106
119
|
{/if}
|
|
107
120
|
|
|
108
121
|
<div class="svadmin-u-f3c543ad5fe9 svadmin-u-0c3bc98565dd svadmin-u-e4d6f343b9ff">
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import { useTaskList, getTaskProvider } from '@svadmin/core';
|
|
5
|
+
import { decodeTaskList } from '@svadmin/core/schema';
|
|
3
6
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
4
7
|
|
|
5
8
|
import type { TaskProvider, TaskRecord } from '@svadmin/core';
|
|
@@ -24,7 +27,7 @@
|
|
|
24
27
|
|
|
25
28
|
let {
|
|
26
29
|
tasks,
|
|
27
|
-
taskProvider = getTaskProvider({ optional: true })
|
|
30
|
+
taskProvider = getTaskProvider({ optional: true }) ?? undefined,
|
|
28
31
|
params,
|
|
29
32
|
dlq = false,
|
|
30
33
|
title,
|
|
@@ -70,14 +73,21 @@
|
|
|
70
73
|
},
|
|
71
74
|
});
|
|
72
75
|
|
|
73
|
-
const
|
|
76
|
+
const view = $derived.by(() => {
|
|
77
|
+
try {
|
|
78
|
+
return { data: decodeTaskList({ data: tasks === undefined ? query.data?.data ?? [] : tasks }).data, invalid: false };
|
|
79
|
+
} catch {
|
|
80
|
+
return { data: [], invalid: true };
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const resolvedTasks = $derived(view.data);
|
|
74
84
|
const resolvedTitle = $derived(title ?? (dlq ? i18n.t('task.dlqTitle') : i18n.t('task.listTitle')));
|
|
75
85
|
const resolvedEmptyText = $derived(emptyText ?? (dlq ? i18n.t('task.noDlq') : i18n.t('task.noTasks')));
|
|
76
86
|
|
|
77
87
|
function formatDate(value: unknown) {
|
|
78
|
-
if (!value) return '—';
|
|
79
|
-
const date = new Date(value
|
|
80
|
-
if (Number.isNaN(date.getTime())) return
|
|
88
|
+
if (typeof value !== 'string' || !value) return '—';
|
|
89
|
+
const date = new Date(value);
|
|
90
|
+
if (Number.isNaN(date.getTime())) return '—';
|
|
81
91
|
return date.toLocaleString();
|
|
82
92
|
}
|
|
83
93
|
</script>
|
|
@@ -94,7 +104,9 @@
|
|
|
94
104
|
</Card.Description>
|
|
95
105
|
</Card.Header>
|
|
96
106
|
<Card.Content class="svadmin-u-9335c39f6eff">
|
|
97
|
-
{#if
|
|
107
|
+
{#if view.invalid || (useProviderData && query.isError)}
|
|
108
|
+
<p role="alert">{i18n.t('validation.invalidFormat')}</p>
|
|
109
|
+
{:else if useProviderData && taskProvider && query.isLoading}
|
|
98
110
|
<div class="svadmin-u-60fbb7713999 svadmin-u-aadad6871af8 svadmin-u-3960ffc248d9 svadmin-u-86843cf1e227 svadmin-u-bfa603190748">
|
|
99
111
|
<Loader2 class="svadmin-u-d2347e8497a9 svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3 svadmin-u-afbdd13a380e" />
|
|
100
112
|
{i18n.t('task.loadingList')}
|
|
@@ -122,7 +134,7 @@
|
|
|
122
134
|
<div class="svadmin-u-359090c2d529 svadmin-u-bfa603190748">{resolveTaskMessage(task)}</div>
|
|
123
135
|
{/if}
|
|
124
136
|
{#if showProgress && typeof resolveTaskProgress(task) === 'number'}
|
|
125
|
-
<TaskProgressBar value
|
|
137
|
+
<TaskProgressBar {...definedOptions({ "value": resolveTaskProgress(task) })} />
|
|
126
138
|
{/if}
|
|
127
139
|
</div>
|
|
128
140
|
</Table.Cell>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedReactiveOptions, definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import { ListTodo, Loader2, Plus, Search, RefreshCw } from '@lucide/svelte';
|
|
3
5
|
import { getTaskProvider, useSubmitTask, useTaskList } from '@svadmin/core';
|
|
6
|
+
import { decodeTaskSubmitOptions } from '@svadmin/core/schema';
|
|
4
7
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
5
8
|
import type { TaskProvider, TaskRecord } from '@svadmin/core';
|
|
6
9
|
import { Button } from './ui/button/index.js';
|
|
@@ -28,7 +31,7 @@
|
|
|
28
31
|
|
|
29
32
|
let {
|
|
30
33
|
open = $bindable(false),
|
|
31
|
-
taskProvider = getTaskProvider({ optional: true })
|
|
34
|
+
taskProvider = getTaskProvider({ optional: true }) ?? undefined,
|
|
32
35
|
title,
|
|
33
36
|
initialTab = 'tasks',
|
|
34
37
|
}: {
|
|
@@ -38,8 +41,7 @@
|
|
|
38
41
|
initialTab?: TaskTab;
|
|
39
42
|
} = $props();
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
let activeTab = $state<TaskTab>('tasks');
|
|
44
|
+
let activeTab = $derived(initialTab);
|
|
43
45
|
let selectedTaskId = $state<string | null>(null);
|
|
44
46
|
let searchQuery = $state('');
|
|
45
47
|
let statusFilter = $state('all');
|
|
@@ -55,7 +57,7 @@
|
|
|
55
57
|
);
|
|
56
58
|
const resolvedTitle = $derived(title ?? i18n.t('task.drawerTitle'));
|
|
57
59
|
|
|
58
|
-
const taskQuery = useTaskList({
|
|
60
|
+
const taskQuery = useTaskList(definedReactiveOptions({
|
|
59
61
|
get taskProvider() {
|
|
60
62
|
return taskProvider;
|
|
61
63
|
},
|
|
@@ -66,9 +68,9 @@
|
|
|
66
68
|
refetchIntervalInBackground: true,
|
|
67
69
|
};
|
|
68
70
|
},
|
|
69
|
-
});
|
|
71
|
+
}));
|
|
70
72
|
|
|
71
|
-
const dlqQuery = useTaskList({
|
|
73
|
+
const dlqQuery = useTaskList(definedReactiveOptions({
|
|
72
74
|
get dlq() {
|
|
73
75
|
return true;
|
|
74
76
|
},
|
|
@@ -82,7 +84,7 @@
|
|
|
82
84
|
refetchIntervalInBackground: true,
|
|
83
85
|
};
|
|
84
86
|
},
|
|
85
|
-
});
|
|
87
|
+
}));
|
|
86
88
|
|
|
87
89
|
const submitTask = useSubmitTask();
|
|
88
90
|
|
|
@@ -123,10 +125,6 @@
|
|
|
123
125
|
allTasks.filter(isTaskActive).length,
|
|
124
126
|
);
|
|
125
127
|
|
|
126
|
-
$effect(() => {
|
|
127
|
-
activeTab = initialTab;
|
|
128
|
-
});
|
|
129
|
-
|
|
130
128
|
$effect(() => {
|
|
131
129
|
if (statusFilter !== 'all' && !availableStatuses.includes(statusFilter)) {
|
|
132
130
|
statusFilter = 'all';
|
|
@@ -141,19 +139,19 @@
|
|
|
141
139
|
}
|
|
142
140
|
|
|
143
141
|
if (!selectedTaskId) {
|
|
144
|
-
selectedTaskId = current[0]
|
|
142
|
+
selectedTaskId = current[0]?.id ?? null;
|
|
145
143
|
return;
|
|
146
144
|
}
|
|
147
145
|
|
|
148
146
|
if (!current.some((task) => task.id === selectedTaskId)) {
|
|
149
|
-
selectedTaskId = current[0]
|
|
147
|
+
selectedTaskId = current[0]?.id ?? null;
|
|
150
148
|
}
|
|
151
149
|
});
|
|
152
150
|
|
|
153
151
|
function formatTime(value: unknown) {
|
|
154
|
-
if (!value) return '—';
|
|
155
|
-
const date = new Date(value
|
|
156
|
-
if (Number.isNaN(date.getTime())) return
|
|
152
|
+
if (typeof value !== 'string' || !value) return '—';
|
|
153
|
+
const date = new Date(value);
|
|
154
|
+
if (Number.isNaN(date.getTime())) return '—';
|
|
157
155
|
return date.toLocaleString();
|
|
158
156
|
}
|
|
159
157
|
|
|
@@ -174,7 +172,8 @@
|
|
|
174
172
|
let body: Record<string, unknown> | undefined;
|
|
175
173
|
if (submitBodyText.trim()) {
|
|
176
174
|
try {
|
|
177
|
-
|
|
175
|
+
const parsed: unknown = JSON.parse(submitBodyText);
|
|
176
|
+
body = decodeTaskSubmitOptions({ body: parsed }).body;
|
|
178
177
|
} catch {
|
|
179
178
|
submitError = i18n.t('task.invalidJson');
|
|
180
179
|
return;
|
|
@@ -182,16 +181,16 @@
|
|
|
182
181
|
}
|
|
183
182
|
|
|
184
183
|
try {
|
|
185
|
-
const handle = await submitTask.mutation.mutateAsync({
|
|
184
|
+
const handle = await submitTask.mutation.mutateAsync(definedOptions({
|
|
186
185
|
taskName: submitTaskName.trim(),
|
|
187
186
|
taskProvider,
|
|
188
|
-
options: {
|
|
187
|
+
options: definedOptions({
|
|
189
188
|
body,
|
|
190
189
|
idempotencyKey: submitIdempotencyKey.trim() || undefined,
|
|
191
|
-
},
|
|
192
|
-
});
|
|
190
|
+
}),
|
|
191
|
+
}));
|
|
193
192
|
activeTab = 'tasks';
|
|
194
|
-
selectedTaskId = handle.id
|
|
193
|
+
selectedTaskId = handle.id;
|
|
195
194
|
submitOpen = false;
|
|
196
195
|
resetSubmitForm();
|
|
197
196
|
await Promise.all([
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import { onMount } from 'svelte';
|
|
3
5
|
import { Toaster, toast as sonner, type ToasterProps } from 'svelte-sonner';
|
|
4
6
|
import {
|
|
@@ -29,7 +31,7 @@
|
|
|
29
31
|
const queue = getToastQueue();
|
|
30
32
|
if (queue.length > 0) {
|
|
31
33
|
for (const t of queue) {
|
|
32
|
-
const options = { duration: t.duration, id: t.key ?? t.id };
|
|
34
|
+
const options = definedOptions({ duration: t.duration, id: t.key ?? t.id });
|
|
33
35
|
switch (t.type) {
|
|
34
36
|
case 'success': sonner.success(t.message, options); break;
|
|
35
37
|
case 'error': sonner.error(t.message, options); break;
|
|
@@ -86,7 +88,7 @@
|
|
|
86
88
|
{#each getToasts().slice(-3) as item (item.id)}
|
|
87
89
|
<UndoableNotification
|
|
88
90
|
message={item.message}
|
|
89
|
-
duration
|
|
91
|
+
{...definedOptions({ "duration": item.duration })}
|
|
90
92
|
embedded
|
|
91
93
|
managedExternally
|
|
92
94
|
onUndo={() => undo(item.id, item.onUndo)}
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
<Tooltip.Root bind:open>
|
|
61
61
|
<Tooltip.Trigger aria-describedby={restProps['aria-describedby']}>
|
|
62
62
|
{#snippet child({ props })}
|
|
63
|
-
{@const primitiveOnclick = props
|
|
63
|
+
{@const primitiveOnclick = props['onclick'] as ((event: MouseEvent) => void) | undefined}
|
|
64
64
|
{@const buttonProps = { ...mergeButtonProps(props), href, target, rel, variant, size, type, class: className, 'aria-label': restProps['aria-label'] ?? tooltip, 'aria-describedby': props['aria-describedby'], onclick: (event: MouseEvent) => handleClick(event, primitiveOnclick), disabled, tabindex } as any}
|
|
65
65
|
<Button {...buttonProps}>
|
|
66
66
|
{@render children()}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import { SvelteSet } from 'svelte/reactivity';
|
|
3
5
|
import { ChevronRight, ChevronDown, Check, X, Search, ChevronsUpDown } from '@lucide/svelte';
|
|
4
6
|
import { cn } from '../utils.js';
|
|
@@ -140,10 +142,10 @@
|
|
|
140
142
|
const matchChildren = filteredChildren && filteredChildren.length > 0;
|
|
141
143
|
|
|
142
144
|
if (matchSelf || matchChildren) {
|
|
143
|
-
filtered.push({
|
|
145
|
+
filtered.push(definedOptions({
|
|
144
146
|
...node,
|
|
145
147
|
children: filteredChildren ?? node.children,
|
|
146
|
-
});
|
|
148
|
+
}));
|
|
147
149
|
}
|
|
148
150
|
}
|
|
149
151
|
return filtered;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script lang="ts" generics="P extends Record<string, unknown>">
|
|
2
|
+
import type { Component } from 'svelte';
|
|
3
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
4
|
+
import type { FieldDisplayProps } from './fieldComponentMap';
|
|
5
|
+
|
|
6
|
+
let { component: Display, resolve, input }: {
|
|
7
|
+
component: Component<P>;
|
|
8
|
+
resolve: (input: FieldDisplayProps) => { ok: true; props: P } | { ok: false };
|
|
9
|
+
input: FieldDisplayProps;
|
|
10
|
+
} = $props();
|
|
11
|
+
|
|
12
|
+
const i18n = useTranslation();
|
|
13
|
+
const result = $derived.by(() => {
|
|
14
|
+
try {
|
|
15
|
+
return resolve(input);
|
|
16
|
+
} catch {
|
|
17
|
+
// Invalid recursive values and schema failures must not reach the renderer.
|
|
18
|
+
return { ok: false } as const;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
{#if result.ok}
|
|
24
|
+
<Display {...result.props} />
|
|
25
|
+
{:else}
|
|
26
|
+
<span role="status" data-svadmin-invalid-field>{i18n.t('validation.invalidFormat')}</span>
|
|
27
|
+
{/if}
|