@svadmin/ui 0.30.1 → 0.31.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/ambient.d.ts +4 -0
- package/dist/components/AdminApp.svelte +34 -10
- 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/Header.svelte +1 -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/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +11 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.svelte +4 -0
- package/dist/router-state.svelte.d.ts +1 -0
- package/dist/router-state.svelte.js +8 -0
- package/package.json +6 -5
|
@@ -87,6 +87,15 @@
|
|
|
87
87
|
const resolvedRouter = $derived(routerProvider ?? createHashRouterProvider());
|
|
88
88
|
const resolvedRouteMode = $derived(routeMode ?? (routerProvider ? 'auto' : 'hash'));
|
|
89
89
|
|
|
90
|
+
// Set up initial context synchronously so children can access resources immediately during first render
|
|
91
|
+
setDataProvider(dataProvider);
|
|
92
|
+
if (authProvider) setAuthProvider(authProvider);
|
|
93
|
+
setResources(resources);
|
|
94
|
+
setRouterProvider(resolvedRouter);
|
|
95
|
+
if (locale) setLocale(locale);
|
|
96
|
+
if (userThemeConfig) configureTheme(userThemeConfig);
|
|
97
|
+
if (defaultTheme) setTheme(defaultTheme);
|
|
98
|
+
|
|
90
99
|
// Set up context — use $effect so prop changes are tracked
|
|
91
100
|
$effect.pre(() => {
|
|
92
101
|
setDataProvider(dataProvider);
|
|
@@ -104,7 +113,8 @@
|
|
|
104
113
|
},
|
|
105
114
|
});
|
|
106
115
|
|
|
107
|
-
// Initialize router with provider
|
|
116
|
+
// Initialize router with provider synchronously
|
|
117
|
+
initRouter(resolvedRouter);
|
|
108
118
|
$effect.pre(() => {
|
|
109
119
|
initRouter(resolvedRouter);
|
|
110
120
|
});
|
|
@@ -118,21 +128,35 @@
|
|
|
118
128
|
let authChecked = $state(false);
|
|
119
129
|
|
|
120
130
|
$effect(() => {
|
|
131
|
+
let cancelled = false;
|
|
121
132
|
// Explicitly track route so auth is re-checked on navigation
|
|
122
133
|
const _route = route;
|
|
123
134
|
|
|
124
135
|
if (!authProvider) {
|
|
125
|
-
|
|
126
|
-
|
|
136
|
+
if (!cancelled) {
|
|
137
|
+
isAuthenticated = true;
|
|
138
|
+
authChecked = true;
|
|
139
|
+
}
|
|
127
140
|
return;
|
|
128
141
|
}
|
|
129
142
|
authProvider.check().then(result => {
|
|
143
|
+
if (cancelled) return;
|
|
130
144
|
isAuthenticated = result.authenticated;
|
|
131
145
|
authChecked = true;
|
|
132
146
|
if (!result.authenticated && _route !== '/login' && _route !== '/register' && _route !== '/forgot-password' && _route !== '/update-password') {
|
|
133
147
|
navigate(result.redirectTo ?? '/login');
|
|
134
148
|
}
|
|
149
|
+
}).catch(err => {
|
|
150
|
+
if (cancelled) return;
|
|
151
|
+
console.warn('Auth check failed:', err);
|
|
152
|
+
isAuthenticated = false;
|
|
153
|
+
authChecked = true;
|
|
154
|
+
if (_route !== '/login' && _route !== '/register' && _route !== '/forgot-password' && _route !== '/update-password') {
|
|
155
|
+
navigate('/login');
|
|
156
|
+
}
|
|
135
157
|
});
|
|
158
|
+
|
|
159
|
+
return () => { cancelled = true; };
|
|
136
160
|
});
|
|
137
161
|
</script>
|
|
138
162
|
|
|
@@ -160,7 +184,7 @@
|
|
|
160
184
|
<Layout {title} {menu} {siteUrl} routeMode={resolvedRouteMode}>
|
|
161
185
|
{#key route + (params.resource ?? '') + (params.id ?? '')}
|
|
162
186
|
<div class="svadmin-page-enter">
|
|
163
|
-
{#if
|
|
187
|
+
{#if route.startsWith('/settings')}
|
|
164
188
|
<SettingsPage />
|
|
165
189
|
{:else if route === '/' || route === ''}
|
|
166
190
|
{#if dashboard}
|
|
@@ -171,30 +195,30 @@
|
|
|
171
195
|
<p class="text-muted-foreground">{t('common.dashboardHint')}</p>
|
|
172
196
|
</div>
|
|
173
197
|
{/if}
|
|
174
|
-
{:else if route === '/:resource'}
|
|
198
|
+
{:else if route === '/:resource' || route === '/:parent/:parentId/:resource'}
|
|
175
199
|
{#key params.resource}
|
|
176
200
|
{@const Comp = getComponentRegistry().AutoTable}
|
|
177
201
|
<Comp resourceName={params.resource} />
|
|
178
202
|
{/key}
|
|
179
|
-
{:else if route === '/:resource/create'}
|
|
203
|
+
{:else if route === '/:resource/create' || route === '/:parent/:parentId/:resource/create'}
|
|
180
204
|
{#key params.resource}
|
|
181
205
|
{@const Comp = getComponentRegistry().AutoForm}
|
|
182
206
|
<Comp resourceName={params.resource} mode="create" />
|
|
183
207
|
{/key}
|
|
184
|
-
{:else if route === '/:resource/edit/:id'}
|
|
208
|
+
{:else if route === '/:resource/edit/:id' || route === '/:parent/:parentId/:resource/edit/:id'}
|
|
185
209
|
{#key `${params.resource}-${params.id}`}
|
|
186
210
|
{@const Comp = getComponentRegistry().AutoForm}
|
|
187
211
|
<Comp resourceName={params.resource} mode="edit" id={params.id} />
|
|
188
212
|
{/key}
|
|
189
|
-
{:else if route === '/:resource/show/:id'}
|
|
213
|
+
{:else if route === '/:resource/show/:id' || route === '/:parent/:parentId/:resource/show/:id'}
|
|
190
214
|
{#key `${params.resource}-${params.id}`}
|
|
191
215
|
{@const Comp = getComponentRegistry().ShowPage}
|
|
192
216
|
<Comp resourceName={params.resource} id={params.id} />
|
|
193
217
|
{/key}
|
|
194
|
-
{:else if route === '/:resource/clone/:id'}
|
|
218
|
+
{:else if route === '/:resource/clone/:id' || route === '/:parent/:parentId/:resource/clone/:id'}
|
|
195
219
|
{#key `${params.resource}-clone-${params.id}`}
|
|
196
220
|
{@const Comp = getComponentRegistry().AutoForm}
|
|
197
|
-
<Comp resourceName={params.resource} mode="
|
|
221
|
+
<Comp resourceName={params.resource} mode="clone" id={params.id} />
|
|
198
222
|
{/key}
|
|
199
223
|
{/if}
|
|
200
224
|
</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) {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
}>();
|
|
28
28
|
|
|
29
29
|
// Retrieve optional component overrides from registry
|
|
30
|
-
const registry = getComponentRegistry();
|
|
30
|
+
const registry = getComponentRegistry() || {} as any;
|
|
31
31
|
const CustomBreadcrumbs = registry.Breadcrumbs;
|
|
32
32
|
const CustomThemeToggle = registry.ThemeToggle;
|
|
33
33
|
const CustomUserMenu = registry.UserMenu;
|
|
@@ -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,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { cn, type WithElementRef } from "../../../utils.js";
|
|
3
3
|
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
4
|
+
import { getContext } from "svelte";
|
|
4
5
|
|
|
5
6
|
type Props = WithElementRef<HTMLButtonAttributes, HTMLButtonElement>;
|
|
6
7
|
|
|
@@ -11,16 +12,25 @@
|
|
|
11
12
|
child,
|
|
12
13
|
...restProps
|
|
13
14
|
}: Props & { child?: import("svelte").Snippet<[{ props: Record<string, unknown> }]> } = $props();
|
|
15
|
+
|
|
16
|
+
const toggle = getContext<() => void>("svadmin-dropdown-toggle");
|
|
17
|
+
|
|
18
|
+
function handleClick(e: MouseEvent) {
|
|
19
|
+
e.stopPropagation();
|
|
20
|
+
toggle?.();
|
|
21
|
+
restProps.onclick?.(e as any);
|
|
22
|
+
}
|
|
14
23
|
</script>
|
|
15
24
|
|
|
16
25
|
{#if child}
|
|
17
|
-
{@render child({ props: { ...restProps, class: cn(className), "data-slot": "dropdown-menu-trigger" } })}
|
|
26
|
+
{@render child({ props: { ...restProps, class: cn(className), "data-slot": "dropdown-menu-trigger", onclick: handleClick } })}
|
|
18
27
|
{:else}
|
|
19
28
|
<button
|
|
20
29
|
bind:this={ref}
|
|
21
30
|
data-slot="dropdown-menu-trigger"
|
|
22
31
|
class={cn(className)}
|
|
23
32
|
{...restProps}
|
|
33
|
+
onclick={handleClick}
|
|
24
34
|
>
|
|
25
35
|
{@render children?.()}
|
|
26
36
|
</button>
|
|
@@ -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.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
12
|
"main": "dist/index.js",
|
|
13
|
-
"types": "dist/index.d.
|
|
13
|
+
"types": "dist/index.d.ts",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
16
16
|
"types": "./dist/index.d.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"./components/*": "./dist/components/*"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@svadmin/core": "^0.
|
|
28
|
+
"@svadmin/core": "^0.22.0",
|
|
29
29
|
"@tanstack/svelte-query": "^6.0.0",
|
|
30
30
|
"highlight.js": "^11.11.1",
|
|
31
31
|
"isomorphic-dompurify": "^3.7.1",
|
|
@@ -53,7 +53,6 @@
|
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@lucide/svelte": "^1.7.0",
|
|
55
55
|
"@tanstack/svelte-table": "^9.0.0-alpha.10",
|
|
56
|
-
"@tanstack/table-core": "^9.0.0-alpha.10",
|
|
57
56
|
"bits-ui": "^2.17.1",
|
|
58
57
|
"clsx": "^2.1.1",
|
|
59
58
|
"cmdk-sv": "^0.0.19",
|
|
@@ -72,7 +71,9 @@
|
|
|
72
71
|
"bugs": {
|
|
73
72
|
"url": "https://github.com/zuohuadong/svadmin/issues"
|
|
74
73
|
},
|
|
75
|
-
"devDependencies": {
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@tanstack/table-core": "8.21.3"
|
|
76
|
+
},
|
|
76
77
|
"scripts": {
|
|
77
78
|
"build": "svelte-package -i src -o dist"
|
|
78
79
|
}
|