@svadmin/ui 0.30.1 → 0.30.2
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/ambient.d.ts +4 -0
- package/dist/components/AdminApp.svelte +23 -9
- package/dist/components/AppearanceSettings.svelte +4 -1
- package/dist/components/AuditLogViewer.svelte +5 -3
- package/dist/components/Authenticated.svelte +10 -3
- package/dist/components/AutoForm.svelte +1 -1
- package/dist/components/AutoForm.svelte.d.ts +1 -1
- package/dist/components/AutoTable.svelte +31 -18
- package/dist/components/Breadcrumbs.svelte +31 -21
- package/dist/components/ChatDialog.svelte +2 -0
- package/dist/components/DraggableHeader.svelte +4 -1
- package/dist/components/Layout.svelte +9 -11
- package/dist/components/PermissionMatrix.svelte +11 -10
- package/dist/components/RolesSettings.svelte +1 -1
- package/dist/components/SettingsPage.svelte +15 -9
- package/dist/components/ShowPage.svelte +4 -0
- package/dist/components/Sidebar.svelte +27 -8
- package/dist/components/SidebarItem.svelte +8 -3
- package/dist/router-state.svelte.d.ts +1 -0
- package/dist/router-state.svelte.js +8 -0
- package/package.json +11 -12
- package/src/index.ts +155 -0
|
@@ -118,21 +118,35 @@
|
|
|
118
118
|
let authChecked = $state(false);
|
|
119
119
|
|
|
120
120
|
$effect(() => {
|
|
121
|
+
let cancelled = false;
|
|
121
122
|
// Explicitly track route so auth is re-checked on navigation
|
|
122
123
|
const _route = route;
|
|
123
124
|
|
|
124
125
|
if (!authProvider) {
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
if (!cancelled) {
|
|
127
|
+
isAuthenticated = true;
|
|
128
|
+
authChecked = true;
|
|
129
|
+
}
|
|
127
130
|
return;
|
|
128
131
|
}
|
|
129
132
|
authProvider.check().then(result => {
|
|
133
|
+
if (cancelled) return;
|
|
130
134
|
isAuthenticated = result.authenticated;
|
|
131
135
|
authChecked = true;
|
|
132
136
|
if (!result.authenticated && _route !== '/login' && _route !== '/register' && _route !== '/forgot-password' && _route !== '/update-password') {
|
|
133
137
|
navigate(result.redirectTo ?? '/login');
|
|
134
138
|
}
|
|
139
|
+
}).catch(err => {
|
|
140
|
+
if (cancelled) return;
|
|
141
|
+
console.warn('Auth check failed:', err);
|
|
142
|
+
isAuthenticated = false;
|
|
143
|
+
authChecked = true;
|
|
144
|
+
if (_route !== '/login' && _route !== '/register' && _route !== '/forgot-password' && _route !== '/update-password') {
|
|
145
|
+
navigate('/login');
|
|
146
|
+
}
|
|
135
147
|
});
|
|
148
|
+
|
|
149
|
+
return () => { cancelled = true; };
|
|
136
150
|
});
|
|
137
151
|
</script>
|
|
138
152
|
|
|
@@ -160,7 +174,7 @@
|
|
|
160
174
|
<Layout {title} {menu} {siteUrl} routeMode={resolvedRouteMode}>
|
|
161
175
|
{#key route + (params.resource ?? '') + (params.id ?? '')}
|
|
162
176
|
<div class="svadmin-page-enter">
|
|
163
|
-
{#if
|
|
177
|
+
{#if route.startsWith('/settings')}
|
|
164
178
|
<SettingsPage />
|
|
165
179
|
{:else if route === '/' || route === ''}
|
|
166
180
|
{#if dashboard}
|
|
@@ -171,30 +185,30 @@
|
|
|
171
185
|
<p class="text-muted-foreground">{t('common.dashboardHint')}</p>
|
|
172
186
|
</div>
|
|
173
187
|
{/if}
|
|
174
|
-
{:else if route === '/:resource'}
|
|
188
|
+
{:else if route === '/:resource' || route === '/:parent/:parentId/:resource'}
|
|
175
189
|
{#key params.resource}
|
|
176
190
|
{@const Comp = getComponentRegistry().AutoTable}
|
|
177
191
|
<Comp resourceName={params.resource} />
|
|
178
192
|
{/key}
|
|
179
|
-
{:else if route === '/:resource/create'}
|
|
193
|
+
{:else if route === '/:resource/create' || route === '/:parent/:parentId/:resource/create'}
|
|
180
194
|
{#key params.resource}
|
|
181
195
|
{@const Comp = getComponentRegistry().AutoForm}
|
|
182
196
|
<Comp resourceName={params.resource} mode="create" />
|
|
183
197
|
{/key}
|
|
184
|
-
{:else if route === '/:resource/edit/:id'}
|
|
198
|
+
{:else if route === '/:resource/edit/:id' || route === '/:parent/:parentId/:resource/edit/:id'}
|
|
185
199
|
{#key `${params.resource}-${params.id}`}
|
|
186
200
|
{@const Comp = getComponentRegistry().AutoForm}
|
|
187
201
|
<Comp resourceName={params.resource} mode="edit" id={params.id} />
|
|
188
202
|
{/key}
|
|
189
|
-
{:else if route === '/:resource/show/:id'}
|
|
203
|
+
{:else if route === '/:resource/show/:id' || route === '/:parent/:parentId/:resource/show/:id'}
|
|
190
204
|
{#key `${params.resource}-${params.id}`}
|
|
191
205
|
{@const Comp = getComponentRegistry().ShowPage}
|
|
192
206
|
<Comp resourceName={params.resource} id={params.id} />
|
|
193
207
|
{/key}
|
|
194
|
-
{:else if route === '/:resource/clone/:id'}
|
|
208
|
+
{:else if route === '/:resource/clone/:id' || route === '/:parent/:parentId/:resource/clone/:id'}
|
|
195
209
|
{#key `${params.resource}-clone-${params.id}`}
|
|
196
210
|
{@const Comp = getComponentRegistry().AutoForm}
|
|
197
|
-
<Comp resourceName={params.resource} mode="
|
|
211
|
+
<Comp resourceName={params.resource} mode="clone" id={params.id} />
|
|
198
212
|
{/key}
|
|
199
213
|
{/if}
|
|
200
214
|
</div>
|
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
|
|
28
28
|
function setDensity(d: 'compact' | 'standard') {
|
|
29
29
|
density = d;
|
|
30
|
-
if (typeof window !== 'undefined')
|
|
30
|
+
if (typeof window !== 'undefined') {
|
|
31
|
+
localStorage.setItem(DENSITY_KEY, d);
|
|
32
|
+
window.dispatchEvent(new CustomEvent('svadmin-density-change', { detail: d }));
|
|
33
|
+
}
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
// Page size
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { Input } from './ui/input/index.js';
|
|
10
10
|
import { Loader2, ChevronLeft, ChevronRight, Search, FileSearch, Eye, X } from '@lucide/svelte';
|
|
11
11
|
|
|
12
|
-
const authProvider = getAuthProvider();
|
|
12
|
+
const authProvider = getAuthProvider({ optional: true });
|
|
13
13
|
|
|
14
14
|
let logs = $state<AuditLog[]>([]);
|
|
15
15
|
let total = $state(0);
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
$effect(() => {
|
|
45
|
+
// Track `page` to re-fetch on pagination — this is the single trigger
|
|
46
|
+
void page;
|
|
45
47
|
loadLogs();
|
|
46
48
|
});
|
|
47
49
|
|
|
@@ -162,7 +164,7 @@
|
|
|
162
164
|
<Button
|
|
163
165
|
variant="outline" size="sm"
|
|
164
166
|
disabled={page <= 1}
|
|
165
|
-
onclick={() => { page--;
|
|
167
|
+
onclick={() => { page--; }}
|
|
166
168
|
>
|
|
167
169
|
<ChevronLeft class="h-4 w-4" />
|
|
168
170
|
</Button>
|
|
@@ -170,7 +172,7 @@
|
|
|
170
172
|
<Button
|
|
171
173
|
variant="outline" size="sm"
|
|
172
174
|
disabled={page >= totalPages}
|
|
173
|
-
onclick={() => { page++;
|
|
175
|
+
onclick={() => { page++; }}
|
|
174
176
|
>
|
|
175
177
|
<ChevronRight class="h-4 w-4" />
|
|
176
178
|
</Button>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { useIsAuthenticated } from '@svadmin/core';
|
|
2
|
+
import { useIsAuthenticated, getRouterProvider } from '@svadmin/core';
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
4
|
import { navigate } from '@svadmin/core';
|
|
5
5
|
|
|
@@ -23,8 +23,15 @@
|
|
|
23
23
|
if (fallback) return;
|
|
24
24
|
let url = redirectTo;
|
|
25
25
|
if (appendCurrentPathToQuery && typeof window !== 'undefined') {
|
|
26
|
-
const
|
|
27
|
-
|
|
26
|
+
const rp = getRouterProvider();
|
|
27
|
+
let currentString = window.location.pathname + window.location.search;
|
|
28
|
+
if (rp) {
|
|
29
|
+
const parsed = rp.parse();
|
|
30
|
+
const qs = new URLSearchParams(parsed.params).toString();
|
|
31
|
+
currentString = parsed.pathname + (qs ? `?${qs}` : '');
|
|
32
|
+
}
|
|
33
|
+
const current = encodeURIComponent(currentString);
|
|
34
|
+
url += url.includes('?') ? `&to=${current}` : `?to=${current}`;
|
|
28
35
|
}
|
|
29
36
|
navigate(url);
|
|
30
37
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
interface Props {
|
|
18
18
|
resourceName: string;
|
|
19
19
|
id?: string | number;
|
|
20
|
-
mode?: 'create' | 'edit';
|
|
20
|
+
mode?: 'create' | 'edit' | 'clone';
|
|
21
21
|
fieldRenderer?: Snippet<[{ field: FieldDefinition; value: unknown; onchange: (v: unknown) => void }]>;
|
|
22
22
|
formActions?: Snippet<[{ isLoading: boolean; onSubmit: () => void }]>;
|
|
23
23
|
headerContent?: Snippet;
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
type ColumnDef,
|
|
7
7
|
type SortingState,
|
|
8
8
|
type RowSelectionState,
|
|
9
|
-
type
|
|
9
|
+
type ColumnVisibilityState,
|
|
10
10
|
type ExpandedState,
|
|
11
11
|
} from '@tanstack/svelte-table';
|
|
12
|
-
import {
|
|
12
|
+
import { createCoreRowModel } from '@tanstack/table-core';
|
|
13
13
|
|
|
14
14
|
import { useList, useDelete, getResource } from '@svadmin/core';
|
|
15
15
|
import type { Pagination as PaginationState, Sort, Filter, FieldDefinition } from '@svadmin/core';
|
|
@@ -101,6 +101,19 @@
|
|
|
101
101
|
let filters = $state<Filter[]>([]);
|
|
102
102
|
let searchText = $state(urlState.search ?? '');
|
|
103
103
|
|
|
104
|
+
// Sync external controlled state
|
|
105
|
+
$effect(() => {
|
|
106
|
+
if (externalPagination) {
|
|
107
|
+
pagination = externalPagination;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
$effect(() => {
|
|
112
|
+
if (externalSorters) {
|
|
113
|
+
sorters = externalSorters;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
104
117
|
// URL sync
|
|
105
118
|
$effect(() => {
|
|
106
119
|
writeURLState({
|
|
@@ -168,7 +181,7 @@
|
|
|
168
181
|
let sorting = $state<SortingState>(untrack(() =>
|
|
169
182
|
sorters.map(s => ({ id: s.field, desc: s.order === 'desc' }))
|
|
170
183
|
));
|
|
171
|
-
let columnVisibility = $state<
|
|
184
|
+
let columnVisibility = $state<ColumnVisibilityState>((() => {
|
|
172
185
|
// Try to restore from localStorage first
|
|
173
186
|
const storageKey = `svadmin-columns-${resourceName}`;
|
|
174
187
|
if (typeof window !== 'undefined') {
|
|
@@ -178,7 +191,7 @@
|
|
|
178
191
|
} catch { /* ignore */ }
|
|
179
192
|
}
|
|
180
193
|
// Fall back to resource field definitions
|
|
181
|
-
const vis:
|
|
194
|
+
const vis: ColumnVisibilityState = {};
|
|
182
195
|
for (const f of resource.fields) {
|
|
183
196
|
if (f.showInList === false) vis[f.key] = false;
|
|
184
197
|
}
|
|
@@ -214,7 +227,7 @@
|
|
|
214
227
|
resource.fields.filter(f => f.showInList !== false)
|
|
215
228
|
);
|
|
216
229
|
|
|
217
|
-
const columns = $derived<ColumnDef<Record<string, unknown>>[]>([
|
|
230
|
+
const columns = $derived<ColumnDef<any, Record<string, unknown>>[]>([
|
|
218
231
|
// Selection column
|
|
219
232
|
...(selectable && canDelete ? [{
|
|
220
233
|
id: '_select',
|
|
@@ -222,7 +235,7 @@
|
|
|
222
235
|
cell: () => '',
|
|
223
236
|
size: 40,
|
|
224
237
|
enableSorting: false,
|
|
225
|
-
} satisfies ColumnDef<Record<string, unknown>>] : []),
|
|
238
|
+
} satisfies ColumnDef<any, Record<string, unknown>>] : []),
|
|
226
239
|
// Expand column
|
|
227
240
|
...(expandedRowRender ? [{
|
|
228
241
|
id: '_expand',
|
|
@@ -230,9 +243,9 @@
|
|
|
230
243
|
cell: () => '',
|
|
231
244
|
size: 40,
|
|
232
245
|
enableSorting: false,
|
|
233
|
-
} satisfies ColumnDef<Record<string, unknown>>] : []),
|
|
246
|
+
} satisfies ColumnDef<any, Record<string, unknown>>] : []),
|
|
234
247
|
// Data columns
|
|
235
|
-
...visibleFields.map((field): ColumnDef<Record<string, unknown>> => ({
|
|
248
|
+
...visibleFields.map((field): ColumnDef<any, Record<string, unknown>> => ({
|
|
236
249
|
id: field.key,
|
|
237
250
|
accessorKey: field.key,
|
|
238
251
|
header: () => field.label,
|
|
@@ -252,10 +265,10 @@
|
|
|
252
265
|
const tbl = createTable({
|
|
253
266
|
get data() { return query.data?.data ?? []; },
|
|
254
267
|
get columns() { return columns; },
|
|
255
|
-
getCoreRowModel:
|
|
268
|
+
getCoreRowModel: createCoreRowModel(),
|
|
256
269
|
manualPagination: true,
|
|
257
270
|
manualSorting: true,
|
|
258
|
-
getRowId: (row) => String(row[primaryKey]),
|
|
271
|
+
getRowId: (row: any) => String(row[primaryKey]),
|
|
259
272
|
state: {
|
|
260
273
|
get sorting() { return sorting; },
|
|
261
274
|
get columnVisibility() { return columnVisibility; },
|
|
@@ -263,19 +276,19 @@
|
|
|
263
276
|
get expanded() { return expanded; },
|
|
264
277
|
get columnOrder() { return columnOrder; },
|
|
265
278
|
},
|
|
266
|
-
onSortingChange: (updater) => {
|
|
279
|
+
onSortingChange: (updater: any) => {
|
|
267
280
|
sorting = typeof updater === 'function' ? updater(sorting) : updater;
|
|
268
281
|
},
|
|
269
|
-
onColumnVisibilityChange: (updater) => {
|
|
282
|
+
onColumnVisibilityChange: (updater: any) => {
|
|
270
283
|
columnVisibility = typeof updater === 'function' ? updater(columnVisibility) : updater;
|
|
271
284
|
},
|
|
272
|
-
onRowSelectionChange: (updater) => {
|
|
285
|
+
onRowSelectionChange: (updater: any) => {
|
|
273
286
|
rowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
|
|
274
287
|
},
|
|
275
|
-
onExpandedChange: (updater) => {
|
|
288
|
+
onExpandedChange: (updater: any) => {
|
|
276
289
|
expanded = typeof updater === 'function' ? updater(expanded) : updater;
|
|
277
290
|
},
|
|
278
|
-
onColumnOrderChange: (updater) => {
|
|
291
|
+
onColumnOrderChange: (updater: any) => {
|
|
279
292
|
columnOrder = typeof updater === 'function' ? updater(columnOrder) : updater;
|
|
280
293
|
},
|
|
281
294
|
get enableRowSelection() { return selectable && canDelete; },
|
|
@@ -390,7 +403,7 @@
|
|
|
390
403
|
{/snippet}
|
|
391
404
|
</DropdownMenu.Trigger>
|
|
392
405
|
<DropdownMenu.Content align="end" class="w-48">
|
|
393
|
-
{#each tbl.getAllLeafColumns().filter(c => !c.id.startsWith('_')) as column}
|
|
406
|
+
{#each tbl.getAllLeafColumns().filter((c: any) => !c.id.startsWith('_')) as column}
|
|
394
407
|
<DropdownMenu.CheckboxItem
|
|
395
408
|
checked={column.getIsVisible()}
|
|
396
409
|
onCheckedChange={(v) => column.toggleVisibility(!!v)}
|
|
@@ -515,9 +528,9 @@
|
|
|
515
528
|
<Table.Header>
|
|
516
529
|
{#each tbl.getHeaderGroups() as headerGroup}
|
|
517
530
|
<DraggableHeader
|
|
518
|
-
columns={headerGroup.headers.map(h => ({ id: h.column.id, header: h }))}
|
|
531
|
+
columns={headerGroup.headers.map((h: any) => ({ id: h.column.id, header: h }))}
|
|
519
532
|
resourceName={resourceName}
|
|
520
|
-
onReorder={(newOrder) => { columnOrder = newOrder.map(c => c.id); }}
|
|
533
|
+
onReorder={(newOrder) => { columnOrder = newOrder.map((c: any) => c.id); }}
|
|
521
534
|
>
|
|
522
535
|
{#snippet header(col, index, dragProps)}
|
|
523
536
|
{@const header = col.header as typeof headerGroup.headers[0]}
|
|
@@ -1,41 +1,51 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getResources } from '@svadmin/core';
|
|
3
|
-
import {
|
|
3
|
+
import { getPath } from '../router-state.svelte.js';
|
|
4
|
+
import { formatLink } from '@svadmin/core/router';
|
|
4
5
|
import { t } from '@svadmin/core/i18n';
|
|
5
6
|
import * as Breadcrumb from './ui/breadcrumb/index.js';
|
|
6
7
|
|
|
7
8
|
const resources = getResources();
|
|
8
|
-
let path = $state(currentPath());
|
|
9
|
-
|
|
10
|
-
function onHashChange() { path = currentPath(); }
|
|
11
9
|
|
|
12
10
|
interface Crumb { label: string; href: string; }
|
|
13
11
|
|
|
14
12
|
const crumbs = $derived.by(() => {
|
|
15
|
-
const result: Crumb[] = [{ label: t('common.home'), href: '
|
|
16
|
-
|
|
13
|
+
const result: Crumb[] = [{ label: t('common.home') ?? 'Home', href: formatLink('/') }];
|
|
14
|
+
const path = getPath().replace(/^\//, '');
|
|
15
|
+
if (path === '') return result;
|
|
17
16
|
|
|
18
17
|
const segments = path.split('/').filter(Boolean);
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
18
|
+
const resourceNames = resources.map(r => r.name);
|
|
19
|
+
|
|
20
|
+
let currentPath = '';
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < segments.length; i++) {
|
|
23
|
+
const seg = segments[i];
|
|
24
|
+
currentPath += `/${seg}`;
|
|
25
|
+
|
|
26
|
+
if (resourceNames.includes(seg)) {
|
|
27
|
+
const res = resources.find(r => r.name === seg)!;
|
|
28
|
+
result.push({ label: res.label, href: formatLink(currentPath) });
|
|
29
|
+
} else if (i > 0 && resourceNames.includes(segments[i-1])) {
|
|
30
|
+
// This is a parent ID (e.g. /teams/123/users)
|
|
31
|
+
result.push({ label: `#${seg}`, href: formatLink(currentPath) });
|
|
32
|
+
} else if (seg === 'create') {
|
|
33
|
+
result.push({ label: t('common.create') ?? 'Create', href: formatLink(currentPath) });
|
|
34
|
+
} else if (['edit', 'show', 'clone'].includes(seg) && segments[i+1]) {
|
|
35
|
+
const actionLabel = seg === 'edit' ? (t('common.edit') ?? 'Edit') :
|
|
36
|
+
seg === 'show' ? (t('common.detail') ?? 'Details') :
|
|
37
|
+
(t('common.clone') ?? 'Clone');
|
|
38
|
+
const id = segments[i+1];
|
|
39
|
+
currentPath += `/${id}`; // advance path by id
|
|
40
|
+
result.push({ label: `${actionLabel} #${id}`, href: formatLink(currentPath) });
|
|
41
|
+
i++; // skip next segment since we consumed the id
|
|
42
|
+
}
|
|
32
43
|
}
|
|
44
|
+
|
|
33
45
|
return result;
|
|
34
46
|
});
|
|
35
47
|
</script>
|
|
36
48
|
|
|
37
|
-
<svelte:window onhashchange={onHashChange} />
|
|
38
|
-
|
|
39
49
|
{#if crumbs.length > 1}
|
|
40
50
|
<Breadcrumb.Root class="mb-4">
|
|
41
51
|
<Breadcrumb.List>
|
|
@@ -29,10 +29,13 @@
|
|
|
29
29
|
let dragIndex = $state<number | null>(null);
|
|
30
30
|
let dropIndex = $state<number | null>(null);
|
|
31
31
|
|
|
32
|
-
// Restore saved order from localStorage
|
|
32
|
+
// Restore saved order from localStorage (once on mount)
|
|
33
|
+
let _restored = false;
|
|
33
34
|
$effect(() => {
|
|
35
|
+
if (_restored) return;
|
|
34
36
|
const storageKey = `svadmin-colorder-${resourceName}`;
|
|
35
37
|
if (typeof window === 'undefined') return;
|
|
38
|
+
_restored = true;
|
|
36
39
|
try {
|
|
37
40
|
const saved = localStorage.getItem(storageKey);
|
|
38
41
|
if (saved) {
|
|
@@ -35,22 +35,20 @@
|
|
|
35
35
|
let identity = $state<Identity | null>(null);
|
|
36
36
|
|
|
37
37
|
$effect(() => {
|
|
38
|
+
let cancelled = false;
|
|
38
39
|
if (!auth) {
|
|
39
|
-
loading = false;
|
|
40
|
+
if (!cancelled) loading = false;
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
|
-
(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (!authenticated) {
|
|
46
|
-
navigate(redirectTo ?? '/login');
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
identity = await auth.getIdentity();
|
|
50
|
-
} finally {
|
|
43
|
+
auth.getIdentity().then(id => {
|
|
44
|
+
if (!cancelled) {
|
|
45
|
+
identity = id;
|
|
51
46
|
loading = false;
|
|
52
47
|
}
|
|
53
|
-
})()
|
|
48
|
+
}).catch(() => {
|
|
49
|
+
if (!cancelled) loading = false;
|
|
50
|
+
});
|
|
51
|
+
return () => { cancelled = true; };
|
|
54
52
|
});
|
|
55
53
|
|
|
56
54
|
async function handleLogout() {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Shield, AlertCircle, Check } from '@lucide/svelte';
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
4
|
import type { RoleInfo, ResourceInfo, ActionInfo } from '../types.js';
|
|
5
|
+
import { t } from '@svadmin/core/i18n';
|
|
5
6
|
|
|
6
7
|
let {
|
|
7
8
|
// Basic metadata lists
|
|
@@ -51,9 +52,9 @@
|
|
|
51
52
|
<div>
|
|
52
53
|
<h1 class="text-2xl font-bold text-foreground flex items-center gap-2">
|
|
53
54
|
<Shield class="w-6 h-6 text-primary" />
|
|
54
|
-
|
|
55
|
+
{t('permissions.title') || 'Permission Matrix'}
|
|
55
56
|
</h1>
|
|
56
|
-
<p class="text-muted-foreground mt-1 text-sm"
|
|
57
|
+
<p class="text-muted-foreground mt-1 text-sm">{t('permissions.description') || 'Configure authorization rules for system modules and roles.'}</p>
|
|
57
58
|
</div>
|
|
58
59
|
|
|
59
60
|
<!-- Toast Feedback -->
|
|
@@ -72,7 +73,7 @@
|
|
|
72
73
|
<div class="flex flex-col md:flex-row gap-6 flex-1 min-h-[500px]">
|
|
73
74
|
<!-- Left: Role Selector Sidebar -->
|
|
74
75
|
<div class="w-full md:w-64 flex-shrink-0 bg-card rounded-xl shadow-sm border border-border p-4 flex flex-col">
|
|
75
|
-
<h2 class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-4 px-2"
|
|
76
|
+
<h2 class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-4 px-2">{t('permissions.roles') || 'Roles'}</h2>
|
|
76
77
|
<div class="space-y-1 flex-1 overflow-y-auto">
|
|
77
78
|
{#each roles as role}
|
|
78
79
|
<button
|
|
@@ -84,7 +85,7 @@
|
|
|
84
85
|
</button>
|
|
85
86
|
{/each}
|
|
86
87
|
{#if roles.length === 0}
|
|
87
|
-
<p class="text-center text-muted-foreground text-sm p-4"
|
|
88
|
+
<p class="text-center text-muted-foreground text-sm p-4">{t('permissions.noRoles') || 'No roles available'}</p>
|
|
88
89
|
{/if}
|
|
89
90
|
</div>
|
|
90
91
|
|
|
@@ -102,10 +103,10 @@
|
|
|
102
103
|
<div class="p-5 border-b border-border bg-muted/30 flex items-center justify-between">
|
|
103
104
|
<div class="flex items-center gap-4">
|
|
104
105
|
<div>
|
|
105
|
-
<h2 class="text-lg font-bold text-foreground"
|
|
106
|
-
<span class="text-primary">{roles.find(r => r.code === selectedRole)?.name || '
|
|
106
|
+
<h2 class="text-lg font-bold text-foreground">{t('permissions.currentRole') || 'Current role'}:
|
|
107
|
+
<span class="text-primary">{roles.find(r => r.code === selectedRole)?.name || (t('permissions.noneSelected') || 'None selected')}</span>
|
|
107
108
|
</h2>
|
|
108
|
-
<p class="text-xs text-muted-foreground mt-1"
|
|
109
|
+
<p class="text-xs text-muted-foreground mt-1">{t('permissions.toggleHint') || 'Toggle checkboxes to grant or revoke permissions'}</p>
|
|
109
110
|
</div>
|
|
110
111
|
|
|
111
112
|
<!-- Slot for header extra logic -->
|
|
@@ -116,7 +117,7 @@
|
|
|
116
117
|
|
|
117
118
|
{#if conflictCount > 0}
|
|
118
119
|
<div class="px-3 py-1.5 bg-destructive/10 text-destructive rounded-lg text-xs font-medium ring-1 ring-destructive/20 flex items-center gap-1.5">
|
|
119
|
-
<AlertCircle class="w-4 h-4" /> {conflictCount}
|
|
120
|
+
<AlertCircle class="w-4 h-4" /> {conflictCount} {t('permissions.conflicts') || 'conflicting rules'}
|
|
120
121
|
</div>
|
|
121
122
|
{/if}
|
|
122
123
|
</div>
|
|
@@ -126,7 +127,7 @@
|
|
|
126
127
|
<table class="min-w-full rounded-lg overflow-hidden shadow-sm ring-1 ring-border/10">
|
|
127
128
|
<thead class="bg-muted/50">
|
|
128
129
|
<tr>
|
|
129
|
-
<th scope="col" class="py-3 pr-3 pl-4 text-left text-sm font-semibold text-foreground w-48"
|
|
130
|
+
<th scope="col" class="py-3 pr-3 pl-4 text-left text-sm font-semibold text-foreground w-48">{t('permissions.resources') || 'Resources'}</th>
|
|
130
131
|
{#each actions as action}
|
|
131
132
|
<th scope="col" class="px-2 py-3 text-center text-sm font-semibold text-foreground">
|
|
132
133
|
<div class="flex flex-col items-center">
|
|
@@ -140,7 +141,7 @@
|
|
|
140
141
|
<tbody class="bg-card">
|
|
141
142
|
{#if resources.length === 0}
|
|
142
143
|
<tr>
|
|
143
|
-
<td colspan={actions.length + 1} class="py-10 text-center text-muted-foreground"
|
|
144
|
+
<td colspan={actions.length + 1} class="py-10 text-center text-muted-foreground">{t('permissions.noResources') || 'No resources available'}</td>
|
|
144
145
|
</tr>
|
|
145
146
|
{/if}
|
|
146
147
|
{#each resources as resource, i}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { toast } from '@svadmin/core/toast';
|
|
8
8
|
import { AlertCircle } from '@lucide/svelte';
|
|
9
9
|
|
|
10
|
-
const authProvider = getAuthProvider();
|
|
10
|
+
const authProvider = getAuthProvider({ optional: true });
|
|
11
11
|
const rawResources = getResources();
|
|
12
12
|
|
|
13
13
|
let roles = $state<RoleInfo[]>([]);
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
import AboutSettings from './AboutSettings.svelte';
|
|
9
9
|
import RolesSettings from './RolesSettings.svelte';
|
|
10
10
|
import AuditLogViewer from './AuditLogViewer.svelte';
|
|
11
|
+
import { getAuthProvider } from '@svadmin/core';
|
|
12
|
+
|
|
13
|
+
const authProvider = getAuthProvider({ optional: true });
|
|
11
14
|
|
|
12
15
|
const sections = [
|
|
13
16
|
{
|
|
@@ -20,26 +23,29 @@
|
|
|
20
23
|
{
|
|
21
24
|
group: 'settings.system',
|
|
22
25
|
items: [
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
...(authProvider ? [
|
|
27
|
+
{ key: 'roles', path: '/settings/roles', icon: Shield, label: 'settings.rolesAndPermissions' },
|
|
28
|
+
{ key: 'audit', path: '/settings/audit', icon: FileSearch, label: 'settings.auditLogs' },
|
|
29
|
+
] : []),
|
|
25
30
|
{ key: 'about', path: '/settings/about', icon: Info, label: 'settings.about' },
|
|
26
31
|
],
|
|
27
32
|
},
|
|
28
33
|
];
|
|
29
34
|
|
|
35
|
+
import { getParams, getRoute } from '../router-state.svelte.js';
|
|
36
|
+
|
|
30
37
|
let activeKey = $derived.by(() => {
|
|
31
|
-
const
|
|
32
|
-
if (
|
|
33
|
-
if (
|
|
34
|
-
if (
|
|
35
|
-
if (
|
|
38
|
+
const tab = getParams().tab;
|
|
39
|
+
if (tab === 'appearance') return 'appearance';
|
|
40
|
+
if (tab === 'roles') return 'roles';
|
|
41
|
+
if (tab === 'audit') return 'audit';
|
|
42
|
+
if (tab === 'about') return 'about';
|
|
36
43
|
return 'profile';
|
|
37
44
|
});
|
|
38
45
|
|
|
39
46
|
// Default redirect to profile
|
|
40
47
|
$effect(() => {
|
|
41
|
-
|
|
42
|
-
if (path === '/settings') {
|
|
48
|
+
if (getRoute() === '/settings') {
|
|
43
49
|
navigate('/settings/profile');
|
|
44
50
|
}
|
|
45
51
|
});
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import ListButton from './buttons/ListButton.svelte';
|
|
12
12
|
import EditButton from './buttons/EditButton.svelte';
|
|
13
13
|
import DeleteButton from './buttons/DeleteButton.svelte';
|
|
14
|
+
import CloneButton from './buttons/CloneButton.svelte';
|
|
14
15
|
import RefreshButton from './buttons/RefreshButton.svelte';
|
|
15
16
|
|
|
16
17
|
let { resourceName, id } = $props<{ resourceName: string; id: string | number }>();
|
|
@@ -28,6 +29,9 @@
|
|
|
28
29
|
{#if resource.canEdit !== false}
|
|
29
30
|
<EditButton resource={resourceName} recordItemId={id} hideText />
|
|
30
31
|
{/if}
|
|
32
|
+
{#if resource.canCreate !== false}
|
|
33
|
+
<CloneButton resource={resourceName} recordItemId={id} hideText />
|
|
34
|
+
{/if}
|
|
31
35
|
{#if resource.canDelete !== false}
|
|
32
36
|
<DeleteButton resource={resourceName} recordItemId={id} hideText onSuccess={() => navigate(`/${resourceName}`)} />
|
|
33
37
|
{/if}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getResources, canAccessAsync } from '@svadmin/core';
|
|
3
3
|
import type { Identity, MenuItem } from '@svadmin/core';
|
|
4
|
-
import {
|
|
4
|
+
import { navigate } from '@svadmin/core/router';
|
|
5
|
+
import { getPath } from '../router-state.svelte.js';
|
|
5
6
|
import { t, getLocale, setLocale, getAvailableLocales } from '@svadmin/core/i18n';
|
|
6
7
|
import { toggleTheme, getResolvedTheme, colorThemes, getColorTheme, setColorTheme } from '@svadmin/core';
|
|
7
8
|
import { Button } from './ui/button/index.js';
|
|
@@ -118,9 +119,7 @@
|
|
|
118
119
|
const localeLabel = $derived(getLocale() === 'zh-CN' ? '中' : 'EN');
|
|
119
120
|
|
|
120
121
|
// Track current hash for active state
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
function onHashChange() { path = currentPath(); }
|
|
122
|
+
const path = $derived(getPath());
|
|
124
123
|
|
|
125
124
|
function isActive(itemPath: string): boolean {
|
|
126
125
|
if (itemPath === '/') return path === '/';
|
|
@@ -159,6 +158,26 @@
|
|
|
159
158
|
let colorPickerRef = $state<HTMLDivElement | null>(null);
|
|
160
159
|
let colorPickerOpenedAt = 0;
|
|
161
160
|
|
|
161
|
+
// Density state
|
|
162
|
+
let density = $state<'compact' | 'standard'>('standard');
|
|
163
|
+
|
|
164
|
+
$effect(() => {
|
|
165
|
+
if (typeof window !== 'undefined') {
|
|
166
|
+
const stored = localStorage.getItem('svadmin-sidebar-density');
|
|
167
|
+
if (stored === 'compact' || stored === 'standard') density = stored;
|
|
168
|
+
|
|
169
|
+
const onDensityChange = (e: Event) => {
|
|
170
|
+
const customEvent = e as CustomEvent<'compact' | 'standard'>;
|
|
171
|
+
density = customEvent.detail;
|
|
172
|
+
};
|
|
173
|
+
window.addEventListener('svadmin-density-change', onDensityChange);
|
|
174
|
+
return () => window.removeEventListener('svadmin-density-change', onDensityChange);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const pyClass = $derived(density === 'compact' ? 'py-1.5' : 'py-2.5');
|
|
179
|
+
const pyClassGroupItem = $derived(density === 'compact' ? 'py-1.5' : 'py-2');
|
|
180
|
+
|
|
162
181
|
// Click-outside to close color picker
|
|
163
182
|
$effect(() => {
|
|
164
183
|
if (!colorPickerOpen) return;
|
|
@@ -176,7 +195,7 @@
|
|
|
176
195
|
});
|
|
177
196
|
</script>
|
|
178
197
|
|
|
179
|
-
|
|
198
|
+
|
|
180
199
|
|
|
181
200
|
<aside
|
|
182
201
|
aria-label="Sidebar navigation"
|
|
@@ -235,7 +254,7 @@
|
|
|
235
254
|
{@const active = isActive(item.path)}
|
|
236
255
|
<a
|
|
237
256
|
href={effectiveRouteMode === 'hash' ? `#${item.path}` : item.path}
|
|
238
|
-
class="flex items-center gap-3 rounded-lg px-3
|
|
257
|
+
class="flex items-center gap-3 rounded-lg px-3 {pyClassGroupItem} text-sm font-medium transition-all duration-200
|
|
239
258
|
{active
|
|
240
259
|
? 'bg-sidebar-accent text-sidebar-accent-foreground sidebar-nav-active'
|
|
241
260
|
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
|
|
@@ -258,7 +277,7 @@
|
|
|
258
277
|
<a
|
|
259
278
|
{...props}
|
|
260
279
|
href={effectiveRouteMode === 'hash' ? `#${item.path}` : item.path}
|
|
261
|
-
class="flex items-center justify-center gap-3 rounded-lg px-3
|
|
280
|
+
class="flex items-center justify-center gap-3 rounded-lg px-3 {pyClass} text-sm font-medium transition-all duration-200
|
|
262
281
|
{active
|
|
263
282
|
? 'bg-sidebar-accent text-sidebar-accent-foreground'
|
|
264
283
|
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
|
|
@@ -274,7 +293,7 @@
|
|
|
274
293
|
{:else}
|
|
275
294
|
<a
|
|
276
295
|
href={effectiveRouteMode === 'hash' ? `#${item.path}` : item.path}
|
|
277
|
-
class="flex items-center gap-3 rounded-lg px-3
|
|
296
|
+
class="flex items-center gap-3 rounded-lg px-3 {pyClass} text-sm font-medium transition-all duration-200
|
|
278
297
|
{active
|
|
279
298
|
? 'bg-sidebar-accent text-sidebar-accent-foreground sidebar-nav-active'
|
|
280
299
|
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
ChevronDown, Folder, ExternalLink, type Icon as LucideIcon,
|
|
10
10
|
} from '@lucide/svelte';
|
|
11
11
|
|
|
12
|
+
import { formatLink } from '@svadmin/core/router';
|
|
13
|
+
|
|
12
14
|
let { item, currentPath, collapsed = false, depth = 0 }: {
|
|
13
15
|
item: MenuItem;
|
|
14
16
|
currentPath: string;
|
|
@@ -37,6 +39,7 @@
|
|
|
37
39
|
function isActive(itemHref: string | undefined): boolean {
|
|
38
40
|
if (!itemHref) return false;
|
|
39
41
|
if (itemHref === '/') return currentPath === '/';
|
|
42
|
+
// Remove hash/leading slash for comparison if necessary, but currentPath usually starts with /
|
|
40
43
|
return currentPath.startsWith(itemHref);
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -49,7 +52,9 @@
|
|
|
49
52
|
const hasChildren = $derived(item.children && item.children.length > 0);
|
|
50
53
|
const active = $derived(isActive(item.href));
|
|
51
54
|
const childActive = $derived(hasActiveChild(item));
|
|
52
|
-
const isExternal = $derived(item.target === '_blank');
|
|
55
|
+
const isExternal = $derived(item.target === '_blank' || item.href?.startsWith('http'));
|
|
56
|
+
|
|
57
|
+
const finalHref = $derived(isExternal ? item.href : (item.href ? formatLink(item.href) : undefined));
|
|
53
58
|
|
|
54
59
|
// Track open state — auto-open if a child is active
|
|
55
60
|
let isOpen = $state(false);
|
|
@@ -92,7 +97,7 @@
|
|
|
92
97
|
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
93
98
|
<a
|
|
94
99
|
{...props}
|
|
95
|
-
href={
|
|
100
|
+
href={finalHref}
|
|
96
101
|
target={isExternal ? '_blank' : undefined}
|
|
97
102
|
rel={isExternal ? 'noopener noreferrer' : undefined}
|
|
98
103
|
class="flex items-center justify-center rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200
|
|
@@ -111,7 +116,7 @@
|
|
|
111
116
|
{:else}
|
|
112
117
|
<!-- Leaf node: render as a link -->
|
|
113
118
|
<a
|
|
114
|
-
href={
|
|
119
|
+
href={finalHref}
|
|
115
120
|
target={isExternal ? '_blank' : undefined}
|
|
116
121
|
rel={isExternal ? 'noopener noreferrer' : undefined}
|
|
117
122
|
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-all duration-200
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RouterProvider } from '@svadmin/core';
|
|
2
2
|
export declare function initRouter(provider?: RouterProvider): void;
|
|
3
3
|
export declare function getRoute(): string;
|
|
4
|
+
export declare function getPath(): string;
|
|
4
5
|
export declare function getParams(): Record<string, string>;
|
|
5
6
|
export declare function getRouterProviderInstance(): RouterProvider | undefined;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { matchRoute, setActiveRouterProvider } from '@svadmin/core/router';
|
|
3
3
|
let _route = $state('/');
|
|
4
4
|
let _params = $state({});
|
|
5
|
+
let _path = $state('/');
|
|
5
6
|
let _initialized = false;
|
|
6
7
|
let _provider;
|
|
7
8
|
const ROUTES = [
|
|
@@ -9,6 +10,8 @@ const ROUTES = [
|
|
|
9
10
|
'/register',
|
|
10
11
|
'/forgot-password',
|
|
11
12
|
'/update-password',
|
|
13
|
+
'/settings',
|
|
14
|
+
'/settings/:tab',
|
|
12
15
|
'/',
|
|
13
16
|
'/:resource',
|
|
14
17
|
'/:resource/create',
|
|
@@ -28,12 +31,14 @@ function sync() {
|
|
|
28
31
|
path = parsed.pathname || '/';
|
|
29
32
|
const m = matchRoute(path, ROUTES);
|
|
30
33
|
_route = m?.route ?? '/';
|
|
34
|
+
_path = path;
|
|
31
35
|
_params = { ...(m?.params ?? {}), ...parsed.params };
|
|
32
36
|
}
|
|
33
37
|
else {
|
|
34
38
|
path = typeof window !== 'undefined' ? window.location.hash.replace(/^#/, '') || '/' : '/';
|
|
35
39
|
const m = matchRoute(path, ROUTES);
|
|
36
40
|
_route = m?.route ?? '/';
|
|
41
|
+
_path = path;
|
|
37
42
|
_params = m?.params ?? {};
|
|
38
43
|
}
|
|
39
44
|
}
|
|
@@ -52,6 +57,9 @@ export function initRouter(provider) {
|
|
|
52
57
|
export function getRoute() {
|
|
53
58
|
return _route;
|
|
54
59
|
}
|
|
60
|
+
export function getPath() {
|
|
61
|
+
return _path;
|
|
62
|
+
}
|
|
55
63
|
export function getParams() {
|
|
56
64
|
return _params;
|
|
57
65
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/ui",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.2",
|
|
4
4
|
"description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -9,23 +9,22 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
|
-
"main": "
|
|
13
|
-
"types": "
|
|
12
|
+
"main": "src/index.ts",
|
|
13
|
+
"types": "src/index.ts",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
16
|
-
"types": "./
|
|
17
|
-
"
|
|
18
|
-
"
|
|
16
|
+
"types": "./src/index.ts",
|
|
17
|
+
"svelte": "./src/index.ts",
|
|
18
|
+
"default": "./src/index.ts"
|
|
19
19
|
},
|
|
20
20
|
"./app.css": {
|
|
21
|
-
"style": "./
|
|
22
|
-
"default": "./
|
|
23
|
-
"svelte": "./dist/app.css"
|
|
21
|
+
"style": "./src/app.css",
|
|
22
|
+
"default": "./src/app.css"
|
|
24
23
|
},
|
|
25
24
|
"./components/*": "./dist/components/*"
|
|
26
25
|
},
|
|
27
26
|
"peerDependencies": {
|
|
28
|
-
"@svadmin/core": "^0.21.
|
|
27
|
+
"@svadmin/core": "^0.21.2",
|
|
29
28
|
"@tanstack/svelte-query": "^6.0.0",
|
|
30
29
|
"highlight.js": "^11.11.1",
|
|
31
30
|
"isomorphic-dompurify": "^3.7.1",
|
|
@@ -52,8 +51,8 @@
|
|
|
52
51
|
},
|
|
53
52
|
"dependencies": {
|
|
54
53
|
"@lucide/svelte": "^1.7.0",
|
|
55
|
-
"@tanstack/svelte-table": "
|
|
56
|
-
"@tanstack/table-core": "
|
|
54
|
+
"@tanstack/svelte-table": "9.0.0-alpha.32",
|
|
55
|
+
"@tanstack/table-core": "9.0.0-alpha.32",
|
|
57
56
|
"bits-ui": "^2.17.1",
|
|
58
57
|
"clsx": "^2.1.1",
|
|
59
58
|
"cmdk-sv": "^0.0.19",
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// @svadmin/ui — Pre-built admin UI components
|
|
2
|
+
|
|
3
|
+
// Entry component
|
|
4
|
+
export { default as AdminApp } from './components/AdminApp.svelte';
|
|
5
|
+
|
|
6
|
+
// Admin components
|
|
7
|
+
export { default as AutoTable } from './components/AutoTable.svelte';
|
|
8
|
+
export { default as AutoForm } from './components/AutoForm.svelte';
|
|
9
|
+
export { default as ShowPage } from './components/ShowPage.svelte';
|
|
10
|
+
export { default as ConfirmDialog } from './components/ConfirmDialog.svelte';
|
|
11
|
+
export { default as Sidebar } from './components/Sidebar.svelte';
|
|
12
|
+
export { default as Layout } from './components/Layout.svelte';
|
|
13
|
+
export { default as Header } from './components/Header.svelte';
|
|
14
|
+
export { default as ErrorBoundary } from './components/ErrorBoundary.svelte';
|
|
15
|
+
export { default as Toast } from './components/Toast.svelte';
|
|
16
|
+
export { default as Breadcrumbs } from './components/Breadcrumbs.svelte';
|
|
17
|
+
export { default as FieldRenderer } from './components/FieldRenderer.svelte';
|
|
18
|
+
export { default as EmptyState } from './components/EmptyState.svelte';
|
|
19
|
+
export { default as StatsCard } from './components/StatsCard.svelte';
|
|
20
|
+
export { default as PageHeader } from './components/PageHeader.svelte';
|
|
21
|
+
export { default as LoginPage } from './components/LoginPage.svelte';
|
|
22
|
+
export { default as RegisterPage } from './components/RegisterPage.svelte';
|
|
23
|
+
export { default as ForgotPasswordPage } from './components/ForgotPasswordPage.svelte';
|
|
24
|
+
export { default as CanAccess } from './components/CanAccess.svelte';
|
|
25
|
+
export { default as Can } from './components/CanAccess.svelte';
|
|
26
|
+
export { default as UndoableNotification } from './components/UndoableNotification.svelte';
|
|
27
|
+
export { default as ModalForm } from './components/ModalForm.svelte';
|
|
28
|
+
export { default as DrawerForm } from './components/DrawerForm.svelte';
|
|
29
|
+
export { default as DevTools } from './components/DevTools.svelte';
|
|
30
|
+
export { default as Authenticated } from './components/Authenticated.svelte';
|
|
31
|
+
export { default as UpdatePasswordPage } from './components/UpdatePasswordPage.svelte';
|
|
32
|
+
export { default as ProfilePage } from './components/ProfilePage.svelte';
|
|
33
|
+
export { default as ConfigErrorScreen } from './components/ConfigErrorScreen.svelte';
|
|
34
|
+
export { default as InferencerPanel } from './components/InferencerPanel.svelte';
|
|
35
|
+
export { default as LiveIndicator } from './components/LiveIndicator.svelte';
|
|
36
|
+
export { default as ListPage } from './components/ListPage.svelte';
|
|
37
|
+
export { default as CreatePage } from './components/CreatePage.svelte';
|
|
38
|
+
export { default as EditPage } from './components/EditPage.svelte';
|
|
39
|
+
export { default as AutoSaveIndicator } from './components/AutoSaveIndicator.svelte';
|
|
40
|
+
export { default as ErrorComponent } from './components/ErrorComponent.svelte';
|
|
41
|
+
export { default as PageSkeleton } from './components/PageSkeleton.svelte';
|
|
42
|
+
export { default as NavigateToResource } from './components/NavigateToResource.svelte';
|
|
43
|
+
export { default as CatchAllNavigate } from './components/CatchAllNavigate.svelte';
|
|
44
|
+
export { default as ThemedTitle } from './components/ThemedTitle.svelte';
|
|
45
|
+
export { default as CommandPalette } from './components/CommandPalette.svelte';
|
|
46
|
+
export { default as StepsForm } from './components/StepsForm.svelte';
|
|
47
|
+
export { default as InfiniteList } from './components/InfiniteList.svelte';
|
|
48
|
+
export { default as ComboboxField } from './components/ComboboxField.svelte';
|
|
49
|
+
export { default as PasswordInput } from './components/PasswordInput.svelte';
|
|
50
|
+
export { default as ChatDialog } from "./components/ChatDialog.svelte";
|
|
51
|
+
export { default as MarkdownRenderer } from "./components/MarkdownRenderer.svelte";
|
|
52
|
+
export { default as SmartSuggest } from "./components/SmartSuggest.svelte";
|
|
53
|
+
export { default as AICommandBar } from "./components/AICommandBar.svelte";
|
|
54
|
+
export { default as CopilotPanel } from "./components/CopilotPanel.svelte";
|
|
55
|
+
export { default as InsightCard } from "./components/InsightCard.svelte";
|
|
56
|
+
export { default as AnomalyBadge } from "./components/AnomalyBadge.svelte";
|
|
57
|
+
export { default as VoiceInput } from "./components/VoiceInput.svelte";
|
|
58
|
+
export { default as AnimatedCounter } from "./components/AnimatedCounter.svelte";
|
|
59
|
+
export { default as KeyboardShortcuts } from "./components/KeyboardShortcuts.svelte";
|
|
60
|
+
export { default as InlineEdit } from "./components/InlineEdit.svelte";
|
|
61
|
+
export { default as VirtualTable } from "./components/VirtualTable.svelte";
|
|
62
|
+
export { default as DraggableHeader } from "./components/DraggableHeader.svelte";
|
|
63
|
+
export { default as SettingsPage } from "./components/SettingsPage.svelte";
|
|
64
|
+
|
|
65
|
+
// Editor Registry
|
|
66
|
+
export { setRichTextEditor, getRichTextEditor } from "./editor-config.svelte.js";
|
|
67
|
+
|
|
68
|
+
// Settings & Preferences
|
|
69
|
+
export { default as AppearanceSettings } from "./components/AppearanceSettings.svelte";
|
|
70
|
+
export { default as AboutSettings } from "./components/AboutSettings.svelte";
|
|
71
|
+
export { default as PermissionMatrix } from "./components/PermissionMatrix.svelte";
|
|
72
|
+
export type { RoleInfo, ResourceInfo, ActionInfo } from "./types.js";
|
|
73
|
+
export { default as RolesSettings } from "./components/RolesSettings.svelte";
|
|
74
|
+
export { default as AuditLogViewer } from "./components/AuditLogViewer.svelte";
|
|
75
|
+
export { default as TenantSwitcher } from "./components/TenantSwitcher.svelte";
|
|
76
|
+
export type { Tenant } from "./types.js";
|
|
77
|
+
export { default as TaskQueueDrawer } from "./components/TaskQueueDrawer.svelte";
|
|
78
|
+
export type { BackgroundTask, TaskStatus } from "./types.js";
|
|
79
|
+
export { default as DraggableGrid } from "./components/DraggableGrid.svelte";
|
|
80
|
+
export type { GridModule } from "./types.js";
|
|
81
|
+
|
|
82
|
+
// Dashboard Charts (zero-dependency SVG)
|
|
83
|
+
export { BarChart, LineChart, PieChart } from './components/charts/index.js';
|
|
84
|
+
// Field display components
|
|
85
|
+
export { default as NumberField } from './components/fields/NumberField.svelte';
|
|
86
|
+
export { default as DateField } from './components/fields/DateField.svelte';
|
|
87
|
+
export { default as EmailField } from './components/fields/EmailField.svelte';
|
|
88
|
+
export { default as UrlField } from './components/fields/UrlField.svelte';
|
|
89
|
+
export { default as BooleanField } from './components/fields/BooleanField.svelte';
|
|
90
|
+
export { default as TagField } from './components/fields/TagField.svelte';
|
|
91
|
+
export { default as FileField } from './components/fields/FileField.svelte';
|
|
92
|
+
export { default as MarkdownField } from './components/fields/MarkdownField.svelte';
|
|
93
|
+
export { default as TextField } from './components/fields/TextField.svelte';
|
|
94
|
+
export { default as ImageField } from './components/fields/ImageField.svelte';
|
|
95
|
+
export { default as SelectField } from './components/fields/SelectField.svelte';
|
|
96
|
+
export { default as MultiSelectField } from './components/fields/MultiSelectField.svelte';
|
|
97
|
+
export { default as RelationField } from './components/fields/RelationField.svelte';
|
|
98
|
+
export { default as JsonField } from './components/fields/JsonField.svelte';
|
|
99
|
+
export { default as RichTextField } from './components/fields/RichTextField.svelte';
|
|
100
|
+
|
|
101
|
+
// Rich text editor (requires @svadmin/editor) as a direct import in consumer app
|
|
102
|
+
|
|
103
|
+
// CRUD Buttons
|
|
104
|
+
export {
|
|
105
|
+
CreateButton, EditButton, DeleteButton, ShowButton, ListButton,
|
|
106
|
+
RefreshButton, ExportButton, ImportButton, SaveButton, CloneButton,
|
|
107
|
+
} from './components/buttons/index.js';
|
|
108
|
+
|
|
109
|
+
// Base UI components (shadcn-svelte)
|
|
110
|
+
export { Button, buttonVariants } from './components/ui/button/index.js';
|
|
111
|
+
export { Input } from './components/ui/input/index.js';
|
|
112
|
+
export { Textarea } from './components/ui/textarea/index.js';
|
|
113
|
+
export { Select } from './components/ui/select/index.js';
|
|
114
|
+
export { Switch } from './components/ui/switch/index.js';
|
|
115
|
+
export { Checkbox } from './components/ui/checkbox/index.js';
|
|
116
|
+
export { Badge } from './components/ui/badge/index.js';
|
|
117
|
+
export { Separator } from './components/ui/separator/index.js';
|
|
118
|
+
export { Avatar } from './components/ui/avatar/index.js';
|
|
119
|
+
export { Skeleton } from './components/ui/skeleton/index.js';
|
|
120
|
+
export * as Sheet from './components/ui/sheet/index.js';
|
|
121
|
+
export * as Alert from './components/ui/alert/index.js';
|
|
122
|
+
export * as Card from './components/ui/card/index.js';
|
|
123
|
+
export * as Dialog from './components/ui/dialog/index.js';
|
|
124
|
+
export * as Table from './components/ui/table/index.js';
|
|
125
|
+
export * as Tabs from './components/ui/tabs/index.js';
|
|
126
|
+
export * as Tooltip from './components/ui/tooltip/index.js';
|
|
127
|
+
export * as DropdownMenu from './components/ui/dropdown-menu/index.js';
|
|
128
|
+
|
|
129
|
+
// Additional shadcn components
|
|
130
|
+
export * as Breadcrumb from './components/ui/breadcrumb/index.js';
|
|
131
|
+
export * as Pagination from './components/ui/pagination/index.js';
|
|
132
|
+
export * as ContextMenu from './components/ui/context-menu/index.js';
|
|
133
|
+
|
|
134
|
+
export * as Collapsible from './components/ui/collapsible/index.js';
|
|
135
|
+
export { Label } from './components/ui/label/index.js';
|
|
136
|
+
export { Command } from './components/ui/command/index.js';
|
|
137
|
+
|
|
138
|
+
// Utils
|
|
139
|
+
export { cn } from './utils';
|
|
140
|
+
|
|
141
|
+
// Component Registry (DI)
|
|
142
|
+
export {
|
|
143
|
+
setComponentRegistry, getComponentRegistry, useComponent,
|
|
144
|
+
type ComponentRegistry,
|
|
145
|
+
} from './component-registry.svelte';
|
|
146
|
+
|
|
147
|
+
// Field component registry
|
|
148
|
+
export {
|
|
149
|
+
builtinDisplayComponents, registerDisplayComponent,
|
|
150
|
+
getDisplayComponent, hasDisplayComponent,
|
|
151
|
+
} from './components/fieldComponentMap';
|
|
152
|
+
export type { FieldComponentMap } from './components/fieldComponentMap';
|
|
153
|
+
|
|
154
|
+
// Svelte Actions
|
|
155
|
+
export { clickOutside, shortcut, intersect, copyOnClick } from './actions';
|