@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
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
import type { FieldDisplayProps } from './fieldComponentMap';
|
|
3
|
+
declare function $$render<P extends Record<string, unknown>>(): {
|
|
4
|
+
props: {
|
|
5
|
+
component: Component<P>;
|
|
6
|
+
resolve: (input: FieldDisplayProps) => {
|
|
7
|
+
ok: true;
|
|
8
|
+
props: P;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
};
|
|
12
|
+
input: FieldDisplayProps;
|
|
13
|
+
};
|
|
14
|
+
exports: {};
|
|
15
|
+
bindings: "";
|
|
16
|
+
slots: {};
|
|
17
|
+
events: {};
|
|
18
|
+
};
|
|
19
|
+
declare class __sveltets_Render<P extends Record<string, unknown>> {
|
|
20
|
+
props(): ReturnType<typeof $$render<P>>['props'];
|
|
21
|
+
events(): ReturnType<typeof $$render<P>>['events'];
|
|
22
|
+
slots(): ReturnType<typeof $$render<P>>['slots'];
|
|
23
|
+
bindings(): "";
|
|
24
|
+
exports(): {};
|
|
25
|
+
}
|
|
26
|
+
interface $$IsomorphicComponent {
|
|
27
|
+
new <P extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<P>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<P>['props']>, ReturnType<__sveltets_Render<P>['events']>, ReturnType<__sveltets_Render<P>['slots']>> & {
|
|
28
|
+
$$bindings?: ReturnType<__sveltets_Render<P>['bindings']>;
|
|
29
|
+
} & ReturnType<__sveltets_Render<P>['exports']>;
|
|
30
|
+
<P extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<P>['props']> & {}): ReturnType<__sveltets_Render<P>['exports']>;
|
|
31
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
32
|
+
}
|
|
33
|
+
declare const ValidatedField: $$IsomorphicComponent;
|
|
34
|
+
type ValidatedField<P extends Record<string, unknown>> = InstanceType<typeof ValidatedField<P>>;
|
|
35
|
+
export default ValidatedField;
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function handleFileSelect(e: Event) {
|
|
43
|
-
const file =
|
|
43
|
+
const file = e.target instanceof HTMLInputElement ? e.target.files?.[0] : undefined;
|
|
44
44
|
if (file) processFile(file);
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
{#each importOptions as option, i (option.key)}
|
|
163
163
|
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-0c3bc98565dd">
|
|
164
164
|
<Label for="import-option-{i}" class="svadmin-u-fc7473ca09eb svadmin-u-8ecebc9f80e6 svadmin-u-d4108abe6359">{i18n.t(option.key)}</Label>
|
|
165
|
-
<Switch id="import-option-{i}"
|
|
165
|
+
<Switch id="import-option-{i}" checked={optionStates[i] ?? option.checked} onCheckedChange={(checked) => { optionStates[i] = checked; }} />
|
|
166
166
|
</div>
|
|
167
167
|
{/each}
|
|
168
168
|
<div class="svadmin-u-60fbb7713999 svadmin-u-77c08e015d14 svadmin-u-b950dda299d3 svadmin-u-173fa8f06789">
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
3
5
|
import { Download } from '@lucide/svelte';
|
|
4
6
|
import { Button } from '../ui/button/index.js';
|
|
@@ -15,7 +17,7 @@
|
|
|
15
17
|
</script>
|
|
16
18
|
|
|
17
19
|
<ContentPageShell pageId="account-security-log" width="wide">
|
|
18
|
-
<ContentPageHeader title={i18n.t('account.securityLog')} description={i18n.t('account.securityLogDescription')} actions
|
|
20
|
+
<ContentPageHeader title={i18n.t('account.securityLog')} description={i18n.t('account.securityLogDescription')} {...definedOptions({ "actions": undefined })} />
|
|
19
21
|
<div class="svadmin-u-60fbb7713999 svadmin-u-77c08e015d14"><Button variant="outline" size="sm"><Download class="svadmin-u-783b0d9d1e2c" />{i18n.t('common.export')}</Button></div>
|
|
20
22
|
<FilterToolbar bind:query placeholder={i18n.t('common.search')} />
|
|
21
23
|
<SecurityEventTable events={filtered} />
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import {
|
|
3
5
|
captureAdminContext,
|
|
4
6
|
notifyWithProvider,
|
|
@@ -167,13 +169,13 @@
|
|
|
167
169
|
testing = true;
|
|
168
170
|
try {
|
|
169
171
|
const selectedProvider = target ?? identityProviders[0];
|
|
170
|
-
const result = await provider.testIdentityProvider({
|
|
172
|
+
const result = await provider.testIdentityProvider(definedOptions({
|
|
171
173
|
id: selectedProvider?.id,
|
|
172
174
|
protocol: selectedProvider?.protocol,
|
|
173
175
|
metadataUrl: target
|
|
174
176
|
? target.metadataUrl
|
|
175
177
|
: (metadataUrl.trim() || selectedProvider?.metadataUrl),
|
|
176
|
-
}, context);
|
|
178
|
+
}), context);
|
|
177
179
|
if (currentRequest !== requestId) return;
|
|
178
180
|
notifyWithProvider({
|
|
179
181
|
type: result.success ? 'success' : 'error',
|
|
@@ -189,14 +191,14 @@
|
|
|
189
191
|
}
|
|
190
192
|
}
|
|
191
193
|
|
|
192
|
-
const displayEvents = $derived(events.map((event): SecurityEvent => ({
|
|
194
|
+
const displayEvents = $derived(events.map((event): SecurityEvent => (definedOptions({
|
|
193
195
|
id: event.id,
|
|
194
196
|
event: event.event,
|
|
195
197
|
actor: event.actor,
|
|
196
198
|
location: event.location,
|
|
197
199
|
createdAt: event.createdAt instanceof Date ? event.createdAt.toLocaleString() : String(event.createdAt),
|
|
198
200
|
severity: event.severity === 'critical' ? 'danger' : (event.severity ?? 'info'),
|
|
199
|
-
})));
|
|
201
|
+
}))));
|
|
200
202
|
</script>
|
|
201
203
|
|
|
202
204
|
{#snippet auditControl()}
|
|
@@ -242,7 +244,7 @@
|
|
|
242
244
|
<DataState state="empty" title={isZh ? '尚未配置身份源' : 'No identity provider configured'} description={isZh ? '请在后端 IdentityGovernanceProvider 中创建并验证身份源。' : 'Create and verify an identity provider through the backend IdentityGovernanceProvider.'} />
|
|
243
245
|
{:else}
|
|
244
246
|
{#each identityProviders as connection (connection.id)}
|
|
245
|
-
<IntegrationCard integration={{ id: connection.id, name: connection.name, account: connection.protocol.toUpperCase(), connected: connection.status === 'connected', description: connection.domain ?? connection.metadataUrl }} onconnect={() => testConnection(connection)} />
|
|
247
|
+
<IntegrationCard integration={definedOptions({ id: connection.id, name: connection.name, account: connection.protocol.toUpperCase(), connected: connection.status === 'connected', description: connection.domain ?? connection.metadataUrl })} onconnect={() => testConnection(connection)} />
|
|
246
248
|
{/each}
|
|
247
249
|
{/if}
|
|
248
250
|
</section>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
let inviteStatus = $state('');
|
|
18
18
|
let members = $state<MemberSummary[]>(referenceDemoData.members.map((member) => ({ ...member, status: member.status === 'active' ? 'success' : member.status === 'invited' ? 'warning' : 'neutral' })));
|
|
19
19
|
const filtered = $derived(query ? members.filter((member) => `${member.name} ${member.email} ${member.department}`.toLowerCase().includes(query.toLowerCase())) : members);
|
|
20
|
-
function sendInvite() { const email = inviteEmail.trim(); if (!email) return; members = [...members, { id: `invite-${Date.now()}`, name: email.
|
|
20
|
+
function sendInvite() { const email = inviteEmail.trim(); if (!email) return; members = [...members, { id: `invite-${Date.now()}`, name: email.replace(/@.*$/, ''), email, role: 'Viewer', department: 'Pending assignment', status: 'warning' }]; inviteStatus = email; inviteEmail = ''; }
|
|
21
21
|
</script>
|
|
22
22
|
|
|
23
23
|
<ContentPageShell pageId="account-team-members" width="wide">
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import type { Snippet } from 'svelte';
|
|
3
5
|
import { getResource, useNavigation, useCan, useTranslation } from '@svadmin/core';
|
|
4
6
|
import { Button } from '../ui/button/index.js';
|
|
@@ -16,7 +18,7 @@
|
|
|
16
18
|
hideText = false,
|
|
17
19
|
accessControl = { enabled: true, hideIfUnauthorized: true },
|
|
18
20
|
class: className = '',
|
|
19
|
-
}
|
|
21
|
+
}: {
|
|
20
22
|
resource: string;
|
|
21
23
|
recordItemId: string | number;
|
|
22
24
|
label?: string;
|
|
@@ -24,7 +26,7 @@
|
|
|
24
26
|
hideText?: boolean;
|
|
25
27
|
accessControl?: ButtonAccessControl;
|
|
26
28
|
class?: string;
|
|
27
|
-
}
|
|
29
|
+
} = $props();
|
|
28
30
|
|
|
29
31
|
const nav = useNavigation();
|
|
30
32
|
const resourceDefinition = $derived.by(() => {
|
|
@@ -34,13 +36,13 @@
|
|
|
34
36
|
return null;
|
|
35
37
|
}
|
|
36
38
|
});
|
|
37
|
-
const can = useCan(() => ({
|
|
39
|
+
const can = useCan(() => (definedOptions({
|
|
38
40
|
resource,
|
|
39
41
|
action: 'create',
|
|
40
42
|
params: withRecordId(accessControl?.params, recordItemId),
|
|
41
43
|
meta: accessControl?.meta,
|
|
42
44
|
queryOptions: { enabled: accessControl?.enabled ?? true }
|
|
43
|
-
}));
|
|
45
|
+
})));
|
|
44
46
|
const displayText = $derived(label ?? i18n.t('common.clone'));
|
|
45
47
|
const hidden = $derived(resourceDefinition?.canCreate === false || (accessControl?.hideIfUnauthorized && !can.allowed));
|
|
46
48
|
</script>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions, definedReactiveOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import type { Snippet } from 'svelte';
|
|
3
|
-
import {
|
|
5
|
+
import { captureAdminContext, captureAuthSession, useResourceContract, useDelete, useCan, useTranslation, UndoError } from '@svadmin/core';
|
|
4
6
|
import { Button } from '../ui/button/index.js';
|
|
5
7
|
import { Trash2 } from '@lucide/svelte';
|
|
6
8
|
import type { ButtonAccessControl } from './access-control';
|
|
7
|
-
import { withRecordId } from './access-control';
|
|
8
9
|
|
|
9
10
|
const i18n = useTranslation();
|
|
10
11
|
|
|
@@ -17,8 +18,10 @@
|
|
|
17
18
|
accessControl = { enabled: true, hideIfUnauthorized: true },
|
|
18
19
|
onSuccess,
|
|
19
20
|
undoable = false,
|
|
21
|
+
undoableTimeout,
|
|
22
|
+
variables,
|
|
20
23
|
class: className = '',
|
|
21
|
-
}
|
|
24
|
+
}: {
|
|
22
25
|
resource: string;
|
|
23
26
|
recordItemId: string | number;
|
|
24
27
|
label?: string;
|
|
@@ -27,59 +30,119 @@
|
|
|
27
30
|
accessControl?: ButtonAccessControl;
|
|
28
31
|
onSuccess?: () => void;
|
|
29
32
|
undoable?: boolean;
|
|
33
|
+
undoableTimeout?: number;
|
|
34
|
+
variables?: unknown;
|
|
30
35
|
class?: string;
|
|
31
|
-
}
|
|
36
|
+
} = $props();
|
|
32
37
|
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} catch {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
const can = useCan(() => ({
|
|
38
|
+
const binding = useResourceContract(() => resource);
|
|
39
|
+
const context = captureAdminContext();
|
|
40
|
+
const resourceDefinition = $derived(context.getResource(resource));
|
|
41
|
+
const can = useCan(() => (definedOptions({
|
|
42
42
|
resource,
|
|
43
43
|
action: 'delete',
|
|
44
|
-
|
|
44
|
+
id: recordItemId,
|
|
45
|
+
params: accessControl?.params,
|
|
45
46
|
meta: accessControl?.meta,
|
|
46
47
|
queryOptions: { enabled: accessControl?.enabled ?? true }
|
|
47
|
-
}));
|
|
48
|
+
})));
|
|
48
49
|
const displayText = $derived(label ?? i18n.t('common.delete'));
|
|
49
|
-
const
|
|
50
|
+
const session = $derived(captureAuthSession(context.authProvider));
|
|
51
|
+
const policyAllowed = $derived(accessControl.enabled === false || can.allowed === true);
|
|
52
|
+
const allowed = $derived(session.available && policyAllowed);
|
|
53
|
+
const hidden = $derived(resourceDefinition.canDelete === false || (accessControl.hideIfUnauthorized && !allowed));
|
|
54
|
+
const deleteMut = useDelete(definedReactiveOptions({
|
|
55
|
+
get resource() { return binding.resource; },
|
|
56
|
+
get id() { return recordItemId; },
|
|
57
|
+
get enabled() { return policyAllowed && resourceDefinition.canDelete !== false; },
|
|
58
|
+
get undoable() { return undoable; },
|
|
59
|
+
get undoableTimeout() { return undoableTimeout; },
|
|
60
|
+
}));
|
|
50
61
|
let confirming = $state(false);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
let pending = $state(false);
|
|
63
|
+
let failed = $state(false);
|
|
64
|
+
const scope = $derived({
|
|
65
|
+
contract: binding.resource, id: recordItemId, variables,
|
|
66
|
+
provider: context.providers?.[binding.dataProviderName],
|
|
67
|
+
tenant: context.tenantCacheKey?.__svadminTenant,
|
|
68
|
+
meta: binding.meta, allowed, hidden, auth: context.authProvider, session,
|
|
69
|
+
});
|
|
70
|
+
let confirmationScope = $state.raw<typeof scope>();
|
|
71
|
+
let previousScope: typeof scope | undefined;
|
|
72
|
+
let active: object | undefined;
|
|
73
|
+
$effect(() => {
|
|
74
|
+
if (previousScope !== scope) {
|
|
75
|
+
// The hook owns session retirement; do not cancel its own checked logout delegate.
|
|
76
|
+
if (active && previousScope?.session.isCurrent()) deleteMut.mutation.reset();
|
|
77
|
+
previousScope = scope;
|
|
78
|
+
active = undefined;
|
|
79
|
+
confirming = false;
|
|
80
|
+
confirmationScope = undefined;
|
|
81
|
+
pending = false;
|
|
82
|
+
failed = false;
|
|
56
83
|
}
|
|
84
|
+
});
|
|
85
|
+
$effect(() => () => { active = undefined; });
|
|
86
|
+
|
|
87
|
+
function beginConfirmation() {
|
|
88
|
+
if (pending || !allowed || hidden || !session.isCurrent()) return;
|
|
89
|
+
confirmationScope = scope;
|
|
90
|
+
confirming = true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const confirmDelete = $derived.by(() => {
|
|
94
|
+
const captured = scope;
|
|
95
|
+
return () => handleDelete(captured);
|
|
96
|
+
});
|
|
97
|
+
async function handleDelete(capturedScope: typeof scope) {
|
|
98
|
+
if (pending || !allowed || hidden || !capturedScope.session.isCurrent() ||
|
|
99
|
+
capturedScope !== scope || !confirming || confirmationScope !== capturedScope) return;
|
|
100
|
+
const token = {};
|
|
101
|
+
const success = onSuccess;
|
|
102
|
+
previousScope = capturedScope;
|
|
103
|
+
active = token;
|
|
57
104
|
confirming = false;
|
|
58
|
-
|
|
59
|
-
|
|
105
|
+
confirmationScope = undefined;
|
|
106
|
+
pending = true;
|
|
107
|
+
failed = false;
|
|
108
|
+
try {
|
|
109
|
+
await deleteMut.mutation.mutateAsync(definedOptions({
|
|
110
|
+
variables, dataProviderName: binding.dataProviderName,
|
|
111
|
+
}));
|
|
112
|
+
if (active === token && scope === capturedScope && capturedScope.session.isCurrent()) success?.();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (!(error instanceof UndoError) && active === token && scope === capturedScope && capturedScope.session.isCurrent()) failed = true;
|
|
115
|
+
} finally {
|
|
116
|
+
if (active === token && scope === capturedScope && capturedScope.session.isCurrent()) {
|
|
117
|
+
active = undefined;
|
|
118
|
+
pending = false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
60
121
|
}
|
|
61
122
|
|
|
62
|
-
function cancel() { confirming = false; }
|
|
123
|
+
function cancel() { confirming = false; confirmationScope = undefined; }
|
|
63
124
|
</script>
|
|
64
125
|
|
|
65
126
|
{#if !hidden}
|
|
66
|
-
{#if confirming}
|
|
127
|
+
{#if confirming && confirmationScope === scope && session.isCurrent()}
|
|
67
128
|
<div class="svadmin-u-52083e7da442 svadmin-u-3960ffc248d9 svadmin-u-44ee8ba0a421">
|
|
68
|
-
<Button variant="destructive" size="sm" onclick={
|
|
129
|
+
<Button type="button" variant="destructive" size="sm" disabled={pending || !allowed} onclick={confirmDelete}>
|
|
69
130
|
{i18n.t('common.confirm')}
|
|
70
131
|
</Button>
|
|
71
|
-
<Button variant="ghost" size="sm" onclick={cancel}>
|
|
132
|
+
<Button type="button" variant="ghost" size="sm" disabled={pending} onclick={cancel}>
|
|
72
133
|
{i18n.t('common.cancel')}
|
|
73
134
|
</Button>
|
|
74
135
|
</div>
|
|
75
136
|
{:else}
|
|
76
137
|
<Button
|
|
138
|
+
type="button"
|
|
77
139
|
variant="ghost"
|
|
78
140
|
size={hideText ? 'icon' : 'sm'}
|
|
79
141
|
class="svadmin-u-811148b13d1e svadmin-u-51e95020d6f2 {className}"
|
|
80
142
|
aria-label={hideText ? displayText : undefined}
|
|
81
|
-
disabled={!
|
|
82
|
-
|
|
143
|
+
disabled={pending || !allowed}
|
|
144
|
+
aria-busy={pending}
|
|
145
|
+
onclick={beginConfirmation}
|
|
83
146
|
>
|
|
84
147
|
<Trash2 class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
85
148
|
{#if !hideText}
|
|
@@ -93,4 +156,7 @@
|
|
|
93
156
|
{/if}
|
|
94
157
|
</Button>
|
|
95
158
|
{/if}
|
|
159
|
+
{#if failed}
|
|
160
|
+
<p role="alert">{i18n.t('common.error')}</p>
|
|
161
|
+
{/if}
|
|
96
162
|
{/if}
|
|
@@ -9,6 +9,8 @@ type $$ComponentProps = {
|
|
|
9
9
|
accessControl?: ButtonAccessControl;
|
|
10
10
|
onSuccess?: () => void;
|
|
11
11
|
undoable?: boolean;
|
|
12
|
+
undoableTimeout?: number;
|
|
13
|
+
variables?: unknown;
|
|
12
14
|
class?: string;
|
|
13
15
|
};
|
|
14
16
|
declare const DeleteButton: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
2
4
|
import type { Snippet } from 'svelte';
|
|
3
5
|
import { getResource, useNavigation, useCan, useTranslation } from '@svadmin/core';
|
|
4
6
|
import { Button } from '../ui/button/index.js';
|
|
@@ -16,7 +18,7 @@
|
|
|
16
18
|
hideText = false,
|
|
17
19
|
accessControl = { enabled: true, hideIfUnauthorized: true },
|
|
18
20
|
class: className = '',
|
|
19
|
-
}
|
|
21
|
+
}: {
|
|
20
22
|
resource: string;
|
|
21
23
|
recordItemId: string | number;
|
|
22
24
|
label?: string;
|
|
@@ -24,7 +26,7 @@
|
|
|
24
26
|
hideText?: boolean;
|
|
25
27
|
accessControl?: ButtonAccessControl;
|
|
26
28
|
class?: string;
|
|
27
|
-
}
|
|
29
|
+
} = $props();
|
|
28
30
|
|
|
29
31
|
const nav = useNavigation();
|
|
30
32
|
const resourceDefinition = $derived.by(() => {
|
|
@@ -34,13 +36,13 @@
|
|
|
34
36
|
return null;
|
|
35
37
|
}
|
|
36
38
|
});
|
|
37
|
-
const can = useCan(() => ({
|
|
39
|
+
const can = useCan(() => (definedOptions({
|
|
38
40
|
resource,
|
|
39
41
|
action: 'edit',
|
|
40
42
|
params: withRecordId(accessControl?.params, recordItemId),
|
|
41
43
|
meta: accessControl?.meta,
|
|
42
44
|
queryOptions: { enabled: accessControl?.enabled ?? true }
|
|
43
|
-
}));
|
|
45
|
+
})));
|
|
44
46
|
const displayText = $derived(label ?? i18n.t('common.edit'));
|
|
45
47
|
const hidden = $derived(resourceDefinition?.canEdit === false || (accessControl?.hideIfUnauthorized && !can.allowed));
|
|
46
48
|
</script>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
-
import {
|
|
3
|
+
import { useCan, useTranslation, useExport, useResourceContract } from '@svadmin/core';
|
|
4
|
+
import { definedReactiveOptions } from '@svadmin/core/options';
|
|
4
5
|
import { Button } from '../ui/button/index.js';
|
|
5
6
|
import { Download } from '@lucide/svelte';
|
|
6
7
|
import type { ButtonAccessControl } from './access-control';
|
|
@@ -23,7 +24,12 @@
|
|
|
23
24
|
class?: string;
|
|
24
25
|
}>();
|
|
25
26
|
|
|
26
|
-
const
|
|
27
|
+
const binding = useResourceContract(() => resource);
|
|
28
|
+
const exporter = useExport(definedReactiveOptions({
|
|
29
|
+
get resource() { return binding.resource; },
|
|
30
|
+
get dataProviderName() { return binding.dataProviderName; },
|
|
31
|
+
get meta() { return binding.meta; },
|
|
32
|
+
}));
|
|
27
33
|
const can = useCan(() => ({
|
|
28
34
|
resource,
|
|
29
35
|
action: 'export',
|
|
@@ -33,6 +39,14 @@
|
|
|
33
39
|
}));
|
|
34
40
|
const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
|
|
35
41
|
const displayText = $derived(label ?? i18n.t('common.export'));
|
|
42
|
+
|
|
43
|
+
async function exportRecords() {
|
|
44
|
+
if (!can.allowed || exporter.isLoading) return;
|
|
45
|
+
try { await exporter.triggerExport(); }
|
|
46
|
+
catch {
|
|
47
|
+
// The hook exposes a sanitized error; stale-scope cancellations remain silent.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
36
50
|
</script>
|
|
37
51
|
|
|
38
52
|
{#if !hidden}
|
|
@@ -41,8 +55,9 @@
|
|
|
41
55
|
size={hideText ? 'icon' : 'sm'}
|
|
42
56
|
class={className}
|
|
43
57
|
aria-label={hideText ? displayText : undefined}
|
|
44
|
-
disabled={isLoading || !can.allowed}
|
|
45
|
-
|
|
58
|
+
disabled={exporter.isLoading || !can.allowed}
|
|
59
|
+
aria-busy={exporter.isLoading}
|
|
60
|
+
onclick={exportRecords}
|
|
46
61
|
>
|
|
47
62
|
<Download class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
48
63
|
{#if !hideText}
|
|
@@ -55,4 +70,7 @@
|
|
|
55
70
|
</span>
|
|
56
71
|
{/if}
|
|
57
72
|
</Button>
|
|
73
|
+
{#if exporter.error}
|
|
74
|
+
<p role="alert">{i18n.t('common.error')}</p>
|
|
75
|
+
{/if}
|
|
58
76
|
{/if}
|
|
@@ -1,76 +1,118 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
-
import {
|
|
3
|
+
import { captureAdminContext, captureAuthSession, useCan, useTranslation, useImport, useResourceContract,
|
|
4
|
+
type ImportResult, type ContractSchemas } from '@svadmin/core';
|
|
5
|
+
import { definedOptions, definedReactiveOptions } from '@svadmin/core/options';
|
|
4
6
|
import { Button } from '../ui/button/index.js';
|
|
5
7
|
import { Upload } from '@lucide/svelte';
|
|
6
8
|
import type { ButtonAccessControl } from './access-control';
|
|
7
9
|
|
|
8
10
|
const i18n = useTranslation();
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
resource,
|
|
12
|
-
label,
|
|
13
|
-
children,
|
|
14
|
-
hideText = false,
|
|
15
|
-
onFinish,
|
|
16
|
-
accessControl = { enabled: true, hideIfUnauthorized: true },
|
|
17
|
-
class: className = '',
|
|
18
|
-
} = $props<{
|
|
12
|
+
interface Props {
|
|
19
13
|
resource: string;
|
|
20
14
|
label?: string;
|
|
21
15
|
children?: Snippet;
|
|
22
16
|
hideText?: boolean;
|
|
23
|
-
onFinish?: (result:
|
|
17
|
+
onFinish?: (result: ImportResult<ContractSchemas>) => void | Promise<void>;
|
|
18
|
+
mapData?: (row: Record<string, unknown>) => unknown;
|
|
24
19
|
accessControl?: ButtonAccessControl;
|
|
25
20
|
class?: string;
|
|
26
|
-
}
|
|
21
|
+
}
|
|
22
|
+
let {
|
|
23
|
+
resource, label, children, hideText = false, onFinish, mapData,
|
|
24
|
+
accessControl = { enabled: true, hideIfUnauthorized: true }, class: className = '',
|
|
25
|
+
}: Props = $props();
|
|
27
26
|
|
|
28
27
|
let fileInput: HTMLInputElement | undefined = $state();
|
|
29
28
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const can = useCan(() => ({
|
|
29
|
+
const context = captureAdminContext();
|
|
30
|
+
const binding = useResourceContract(() => resource);
|
|
31
|
+
const can = useCan(() => definedOptions({
|
|
36
32
|
resource,
|
|
37
33
|
action: 'import',
|
|
38
34
|
params: accessControl?.params,
|
|
39
35
|
meta: accessControl?.meta,
|
|
40
36
|
queryOptions: { enabled: accessControl?.enabled ?? true },
|
|
41
37
|
}));
|
|
42
|
-
const
|
|
38
|
+
const session = $derived(captureAuthSession(context.authProvider));
|
|
39
|
+
const policyAllowed = $derived(accessControl.enabled === false || can.allowed === true);
|
|
40
|
+
const allowed = $derived(policyAllowed && session.available);
|
|
41
|
+
const canCreate = $derived(context.getResource(resource).canCreate !== false);
|
|
42
|
+
const hidden = $derived(!canCreate ||
|
|
43
|
+
(accessControl.hideIfUnauthorized && !allowed));
|
|
44
|
+
const importHook = useImport(definedReactiveOptions({
|
|
45
|
+
get resource() { return binding.resource; },
|
|
46
|
+
get dataProviderName() { return binding.dataProviderName; },
|
|
47
|
+
get enabled() { return policyAllowed && canCreate; },
|
|
48
|
+
get onFinish() { return onFinish; },
|
|
49
|
+
get mapData() { return mapData; },
|
|
50
|
+
}));
|
|
51
|
+
const scope = $derived({
|
|
52
|
+
contract: binding.resource, provider: context.providers?.[binding.dataProviderName],
|
|
53
|
+
tenant: context.tenantCacheKey?.__svadminTenant, meta: binding.meta, allowed, hidden,
|
|
54
|
+
auth: context.authProvider, router: context.routerProvider, session,
|
|
55
|
+
});
|
|
56
|
+
let mounted = true;
|
|
57
|
+
let picker: { scope: typeof scope; input: HTMLInputElement } | undefined;
|
|
58
|
+
let selection: object | undefined;
|
|
59
|
+
const currentScope = (origin: typeof scope) => mounted && scope === origin && origin.session.isCurrent();
|
|
60
|
+
$effect.pre(() => { void scope; picker = undefined; selection = undefined; });
|
|
61
|
+
$effect(() => () => { mounted = false; picker = undefined; selection = undefined; });
|
|
43
62
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
63
|
+
const triggerImport = $derived.by(() => {
|
|
64
|
+
const origin = scope;
|
|
65
|
+
return () => {
|
|
66
|
+
if (!currentScope(origin) || !allowed || hidden || importHook.isLoading || !fileInput) return;
|
|
67
|
+
picker = { scope: origin, input: fileInput };
|
|
68
|
+
fileInput.click();
|
|
69
|
+
};
|
|
70
|
+
});
|
|
47
71
|
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
72
|
+
const handleFileChange = $derived.by(() => {
|
|
73
|
+
const origin = scope;
|
|
74
|
+
return async (event: Event) => {
|
|
75
|
+
const input = event.currentTarget;
|
|
76
|
+
if (!(input instanceof HTMLInputElement) || !currentScope(origin) || picker?.scope !== origin ||
|
|
77
|
+
picker.input !== input || !allowed || hidden || importHook.isLoading) return;
|
|
78
|
+
const token = {};
|
|
79
|
+
selection = token;
|
|
80
|
+
picker = undefined;
|
|
81
|
+
try {
|
|
82
|
+
const file = input.files?.[0];
|
|
83
|
+
if (file) await importHook.handleChange({ file });
|
|
84
|
+
} catch {
|
|
85
|
+
// The hook exposes a sanitized error; event handlers must not reject globally.
|
|
86
|
+
} finally {
|
|
87
|
+
if (currentScope(origin) && selection === token && fileInput === input) {
|
|
88
|
+
input.value = '';
|
|
89
|
+
selection = undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
});
|
|
56
94
|
|
|
57
95
|
const displayText = $derived(label ?? i18n.t('common.import'));
|
|
58
96
|
</script>
|
|
59
97
|
|
|
60
98
|
{#if !hidden}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
99
|
+
{#key scope}
|
|
100
|
+
<input
|
|
101
|
+
type="file"
|
|
102
|
+
accept={importHook.inputProps.accept}
|
|
103
|
+
class="svadmin-u-99d72c7fc3e2"
|
|
104
|
+
bind:this={fileInput}
|
|
105
|
+
onchange={handleFileChange}
|
|
106
|
+
/>
|
|
107
|
+
{/key}
|
|
68
108
|
<Button
|
|
109
|
+
type="button"
|
|
69
110
|
variant="outline"
|
|
70
111
|
size={hideText ? 'icon' : 'sm'}
|
|
71
112
|
class={className}
|
|
72
113
|
aria-label={hideText ? displayText : undefined}
|
|
73
|
-
disabled={importHook.isLoading || !
|
|
114
|
+
disabled={importHook.isLoading || !allowed}
|
|
115
|
+
aria-busy={importHook.isLoading}
|
|
74
116
|
onclick={triggerImport}
|
|
75
117
|
>
|
|
76
118
|
<Upload class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
@@ -84,4 +126,7 @@
|
|
|
84
126
|
</span>
|
|
85
127
|
{/if}
|
|
86
128
|
</Button>
|
|
129
|
+
{#if importHook.error}
|
|
130
|
+
<p role="alert">{i18n.t('common.operationFailed')}</p>
|
|
131
|
+
{/if}
|
|
87
132
|
{/if}
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
+
import { type ImportResult, type ContractSchemas } from '@svadmin/core';
|
|
2
3
|
import type { ButtonAccessControl } from './access-control';
|
|
3
|
-
|
|
4
|
+
interface Props {
|
|
4
5
|
resource: string;
|
|
5
6
|
label?: string;
|
|
6
7
|
children?: Snippet;
|
|
7
8
|
hideText?: boolean;
|
|
8
|
-
onFinish?: (result:
|
|
9
|
-
|
|
10
|
-
errored: {
|
|
11
|
-
request: unknown;
|
|
12
|
-
error: unknown;
|
|
13
|
-
}[];
|
|
14
|
-
}) => void;
|
|
9
|
+
onFinish?: (result: ImportResult<ContractSchemas>) => void | Promise<void>;
|
|
10
|
+
mapData?: (row: Record<string, unknown>) => unknown;
|
|
15
11
|
accessControl?: ButtonAccessControl;
|
|
16
12
|
class?: string;
|
|
17
|
-
}
|
|
18
|
-
declare const ImportButton: import("svelte").Component
|
|
13
|
+
}
|
|
14
|
+
declare const ImportButton: import("svelte").Component<Props, {}, "">;
|
|
19
15
|
type ImportButton = ReturnType<typeof ImportButton>;
|
|
20
16
|
export default ImportButton;
|