@svadmin/ui 0.0.3 → 0.0.6
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/package.json +2 -2
- package/src/components/AdminApp.svelte +27 -18
- package/src/components/Authenticated.svelte +26 -0
- package/src/components/ConfigErrorScreen.svelte +224 -0
- package/src/components/DevTools.svelte +83 -34
- package/src/components/ForgotPasswordPage.svelte +12 -22
- package/src/components/InferencerPanel.svelte +166 -0
- package/src/components/LoginPage.svelte +13 -22
- package/src/components/RegisterPage.svelte +12 -23
- package/src/components/StatsCard.svelte +23 -2
- package/src/components/UpdatePasswordPage.svelte +217 -0
- package/src/components/buttons/CloneButton.svelte +30 -0
- package/src/components/buttons/CreateButton.svelte +29 -0
- package/src/components/buttons/DeleteButton.svelte +60 -0
- package/src/components/buttons/EditButton.svelte +30 -0
- package/src/components/buttons/ExportButton.svelte +24 -0
- package/src/components/buttons/ImportButton.svelte +28 -0
- package/src/components/buttons/ListButton.svelte +23 -0
- package/src/components/buttons/RefreshButton.svelte +30 -0
- package/src/components/buttons/SaveButton.svelte +27 -0
- package/src/components/buttons/ShowButton.svelte +24 -0
- package/src/components/buttons/index.ts +10 -0
- package/src/index.ts +10 -0
- package/src/router-state.svelte.ts +24 -5
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useDelete, useCan, t } from '@svadmin/core';
|
|
3
|
+
import { Button } from '../ui/button/index.js';
|
|
4
|
+
import { Trash2 } from 'lucide-svelte';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
resource, recordItemId, hideText = false,
|
|
8
|
+
accessControl, onSuccess, undoable = false, class: className = '',
|
|
9
|
+
} = $props<{
|
|
10
|
+
resource: string;
|
|
11
|
+
recordItemId: string | number;
|
|
12
|
+
hideText?: boolean;
|
|
13
|
+
accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
|
|
14
|
+
onSuccess?: () => void;
|
|
15
|
+
undoable?: boolean;
|
|
16
|
+
class?: string;
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
const deleteMut = useDelete(resource, { mutationMode: undoable ? 'undoable' : 'pessimistic' });
|
|
20
|
+
const can = accessControl?.enabled ? useCan({ resource, action: 'delete' }) : null;
|
|
21
|
+
const hidden = $derived(accessControl?.hideIfUnauthorized && can && !can.allowed);
|
|
22
|
+
let confirming = $state(false);
|
|
23
|
+
|
|
24
|
+
async function handleDelete() {
|
|
25
|
+
if (!confirming) {
|
|
26
|
+
confirming = true;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
confirming = false;
|
|
30
|
+
// @ts-expect-error $ rune prefix
|
|
31
|
+
await $deleteMut.mutateAsync(recordItemId);
|
|
32
|
+
onSuccess?.();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function cancel() { confirming = false; }
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
{#if !hidden}
|
|
39
|
+
{#if confirming}
|
|
40
|
+
<div class="inline-flex items-center gap-1">
|
|
41
|
+
<Button variant="destructive" size="sm" onclick={handleDelete}>
|
|
42
|
+
{t('common.confirm')}
|
|
43
|
+
</Button>
|
|
44
|
+
<Button variant="ghost" size="sm" onclick={cancel}>
|
|
45
|
+
{t('common.cancel')}
|
|
46
|
+
</Button>
|
|
47
|
+
</div>
|
|
48
|
+
{:else}
|
|
49
|
+
<Button
|
|
50
|
+
variant="ghost"
|
|
51
|
+
size={hideText ? 'icon' : 'sm'}
|
|
52
|
+
class="text-destructive hover:text-destructive {className}"
|
|
53
|
+
disabled={can ? !can.allowed : false}
|
|
54
|
+
onclick={handleDelete}
|
|
55
|
+
>
|
|
56
|
+
<Trash2 class="h-4 w-4" />
|
|
57
|
+
{#if !hideText}<span class="ml-1">{t('common.delete')}</span>{/if}
|
|
58
|
+
</Button>
|
|
59
|
+
{/if}
|
|
60
|
+
{/if}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useNavigation, useCan, t } from '@svadmin/core';
|
|
3
|
+
import { Button } from '../ui/button/index.js';
|
|
4
|
+
import { Pencil } from 'lucide-svelte';
|
|
5
|
+
|
|
6
|
+
let { resource, recordItemId, hideText = false, accessControl, class: className = '' } = $props<{
|
|
7
|
+
resource: string;
|
|
8
|
+
recordItemId: string | number;
|
|
9
|
+
hideText?: boolean;
|
|
10
|
+
accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
|
|
11
|
+
class?: string;
|
|
12
|
+
}>();
|
|
13
|
+
|
|
14
|
+
const nav = useNavigation();
|
|
15
|
+
const can = accessControl?.enabled ? useCan({ resource, action: 'edit' }) : null;
|
|
16
|
+
const hidden = $derived(accessControl?.hideIfUnauthorized && can && !can.allowed);
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
{#if !hidden}
|
|
20
|
+
<Button
|
|
21
|
+
variant="outline"
|
|
22
|
+
size={hideText ? 'icon' : 'sm'}
|
|
23
|
+
class={className}
|
|
24
|
+
disabled={can ? !can.allowed : false}
|
|
25
|
+
onclick={() => nav.edit(resource, recordItemId)}
|
|
26
|
+
>
|
|
27
|
+
<Pencil class="h-4 w-4" />
|
|
28
|
+
{#if !hideText}<span class="ml-1">{t('common.edit')}</span>{/if}
|
|
29
|
+
</Button>
|
|
30
|
+
{/if}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useExport, t } from '@svadmin/core';
|
|
3
|
+
import { Button } from '../ui/button/index.js';
|
|
4
|
+
import { Download } from 'lucide-svelte';
|
|
5
|
+
|
|
6
|
+
let { resource, hideText = false, class: className = '' } = $props<{
|
|
7
|
+
resource: string;
|
|
8
|
+
hideText?: boolean;
|
|
9
|
+
class?: string;
|
|
10
|
+
}>();
|
|
11
|
+
|
|
12
|
+
const { triggerExport, isLoading } = useExport({ resource });
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<Button
|
|
16
|
+
variant="outline"
|
|
17
|
+
size={hideText ? 'icon' : 'sm'}
|
|
18
|
+
class={className}
|
|
19
|
+
disabled={isLoading}
|
|
20
|
+
onclick={triggerExport}
|
|
21
|
+
>
|
|
22
|
+
<Download class="h-4 w-4" />
|
|
23
|
+
{#if !hideText}<span class="ml-1">{t('common.export')}</span>{/if}
|
|
24
|
+
</Button>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useImport, t } from '@svadmin/core';
|
|
3
|
+
import { Button } from '../ui/button/index.js';
|
|
4
|
+
import { Upload } from 'lucide-svelte';
|
|
5
|
+
|
|
6
|
+
let { resource, hideText = false, onComplete, class: className = '' } = $props<{
|
|
7
|
+
resource: string;
|
|
8
|
+
hideText?: boolean;
|
|
9
|
+
onComplete?: (result: { success: number; failed: number }) => void;
|
|
10
|
+
class?: string;
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
const { triggerImport, isLoading } = useImport({
|
|
14
|
+
resource,
|
|
15
|
+
onComplete: (result) => onComplete?.(result),
|
|
16
|
+
});
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<Button
|
|
20
|
+
variant="outline"
|
|
21
|
+
size={hideText ? 'icon' : 'sm'}
|
|
22
|
+
class={className}
|
|
23
|
+
disabled={isLoading}
|
|
24
|
+
onclick={triggerImport}
|
|
25
|
+
>
|
|
26
|
+
<Upload class="h-4 w-4" />
|
|
27
|
+
{#if !hideText}<span class="ml-1">{t('common.import')}</span>{/if}
|
|
28
|
+
</Button>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useNavigation, t } from '@svadmin/core';
|
|
3
|
+
import { Button } from '../ui/button/index.js';
|
|
4
|
+
import { List } from 'lucide-svelte';
|
|
5
|
+
|
|
6
|
+
let { resource, hideText = false, class: className = '' } = $props<{
|
|
7
|
+
resource: string;
|
|
8
|
+
hideText?: boolean;
|
|
9
|
+
class?: string;
|
|
10
|
+
}>();
|
|
11
|
+
|
|
12
|
+
const nav = useNavigation();
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<Button
|
|
16
|
+
variant="outline"
|
|
17
|
+
size={hideText ? 'icon' : 'sm'}
|
|
18
|
+
class={className}
|
|
19
|
+
onclick={() => nav.list(resource)}
|
|
20
|
+
>
|
|
21
|
+
<List class="h-4 w-4" />
|
|
22
|
+
{#if !hideText}<span class="ml-1">{resource}</span>{/if}
|
|
23
|
+
</Button>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useQueryClient } from '@tanstack/svelte-query';
|
|
3
|
+
import { Button } from '../ui/button/index.js';
|
|
4
|
+
import { RefreshCw } from 'lucide-svelte';
|
|
5
|
+
|
|
6
|
+
let { resource, hideText = false, class: className = '' } = $props<{
|
|
7
|
+
resource: string;
|
|
8
|
+
hideText?: boolean;
|
|
9
|
+
class?: string;
|
|
10
|
+
}>();
|
|
11
|
+
|
|
12
|
+
const queryClient = useQueryClient();
|
|
13
|
+
let spinning = $state(false);
|
|
14
|
+
|
|
15
|
+
function refresh() {
|
|
16
|
+
spinning = true;
|
|
17
|
+
queryClient.invalidateQueries({ queryKey: [resource] });
|
|
18
|
+
setTimeout(() => { spinning = false; }, 600);
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<Button
|
|
23
|
+
variant="ghost"
|
|
24
|
+
size={hideText ? 'icon' : 'sm'}
|
|
25
|
+
class={className}
|
|
26
|
+
onclick={refresh}
|
|
27
|
+
>
|
|
28
|
+
<RefreshCw class="h-4 w-4 {spinning ? 'animate-spin' : ''}" />
|
|
29
|
+
{#if !hideText}<span class="ml-1">Refresh</span>{/if}
|
|
30
|
+
</Button>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core';
|
|
3
|
+
import { Button } from '../ui/button/index.js';
|
|
4
|
+
import { Save, Loader2 } from 'lucide-svelte';
|
|
5
|
+
|
|
6
|
+
let { loading = false, hideText = false, type = 'submit', class: className = '' } = $props<{
|
|
7
|
+
loading?: boolean;
|
|
8
|
+
hideText?: boolean;
|
|
9
|
+
type?: 'submit' | 'button';
|
|
10
|
+
class?: string;
|
|
11
|
+
}>();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<Button
|
|
15
|
+
{type}
|
|
16
|
+
variant="default"
|
|
17
|
+
size={hideText ? 'icon' : 'default'}
|
|
18
|
+
class={className}
|
|
19
|
+
disabled={loading}
|
|
20
|
+
>
|
|
21
|
+
{#if loading}
|
|
22
|
+
<Loader2 class="h-4 w-4 animate-spin" />
|
|
23
|
+
{:else}
|
|
24
|
+
<Save class="h-4 w-4" />
|
|
25
|
+
{/if}
|
|
26
|
+
{#if !hideText}<span class="ml-1">{t('common.save')}</span>{/if}
|
|
27
|
+
</Button>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useNavigation, t } from '@svadmin/core';
|
|
3
|
+
import { Button } from '../ui/button/index.js';
|
|
4
|
+
import { Eye } from 'lucide-svelte';
|
|
5
|
+
|
|
6
|
+
let { resource, recordItemId, hideText = false, class: className = '' } = $props<{
|
|
7
|
+
resource: string;
|
|
8
|
+
recordItemId: string | number;
|
|
9
|
+
hideText?: boolean;
|
|
10
|
+
class?: string;
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
const nav = useNavigation();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<Button
|
|
17
|
+
variant="ghost"
|
|
18
|
+
size={hideText ? 'icon' : 'sm'}
|
|
19
|
+
class={className}
|
|
20
|
+
onclick={() => nav.show(resource, recordItemId)}
|
|
21
|
+
>
|
|
22
|
+
<Eye class="h-4 w-4" />
|
|
23
|
+
{#if !hideText}<span class="ml-1">{t('common.detail')}</span>{/if}
|
|
24
|
+
</Button>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { default as CreateButton } from './CreateButton.svelte';
|
|
2
|
+
export { default as EditButton } from './EditButton.svelte';
|
|
3
|
+
export { default as DeleteButton } from './DeleteButton.svelte';
|
|
4
|
+
export { default as ShowButton } from './ShowButton.svelte';
|
|
5
|
+
export { default as ListButton } from './ListButton.svelte';
|
|
6
|
+
export { default as RefreshButton } from './RefreshButton.svelte';
|
|
7
|
+
export { default as ExportButton } from './ExportButton.svelte';
|
|
8
|
+
export { default as ImportButton } from './ImportButton.svelte';
|
|
9
|
+
export { default as SaveButton } from './SaveButton.svelte';
|
|
10
|
+
export { default as CloneButton } from './CloneButton.svelte';
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,16 @@ export { default as UndoableNotification } from './components/UndoableNotificati
|
|
|
26
26
|
export { default as ModalForm } from './components/ModalForm.svelte';
|
|
27
27
|
export { default as DrawerForm } from './components/DrawerForm.svelte';
|
|
28
28
|
export { default as DevTools } from './components/DevTools.svelte';
|
|
29
|
+
export { default as Authenticated } from './components/Authenticated.svelte';
|
|
30
|
+
export { default as UpdatePasswordPage } from './components/UpdatePasswordPage.svelte';
|
|
31
|
+
export { default as ConfigErrorScreen } from './components/ConfigErrorScreen.svelte';
|
|
32
|
+
export { default as InferencerPanel } from './components/InferencerPanel.svelte';
|
|
33
|
+
|
|
34
|
+
// CRUD Buttons
|
|
35
|
+
export {
|
|
36
|
+
CreateButton, EditButton, DeleteButton, ShowButton, ListButton,
|
|
37
|
+
RefreshButton, ExportButton, ImportButton, SaveButton, CloneButton,
|
|
38
|
+
} from './components/buttons/index.js';
|
|
29
39
|
|
|
30
40
|
// Base UI components (shadcn-svelte)
|
|
31
41
|
export { Button, buttonVariants } from './components/ui/button/index.js';
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
// Shared reactive hash router state — .svelte.ts enables $state at module level
|
|
2
2
|
import { matchRoute } from '@svadmin/core/router';
|
|
3
|
+
import type { RouterProvider } from '@svadmin/core';
|
|
3
4
|
|
|
4
5
|
let _route = $state('/');
|
|
5
6
|
let _params: Record<string, string> = $state({});
|
|
6
7
|
let _initialized = false;
|
|
8
|
+
let _provider: RouterProvider | undefined;
|
|
7
9
|
|
|
8
10
|
const ROUTES = [
|
|
9
11
|
'/login',
|
|
10
12
|
'/register',
|
|
11
13
|
'/forgot-password',
|
|
14
|
+
'/update-password',
|
|
12
15
|
'/',
|
|
13
16
|
'/:resource',
|
|
14
17
|
'/:resource/create',
|
|
@@ -17,17 +20,29 @@ const ROUTES = [
|
|
|
17
20
|
];
|
|
18
21
|
|
|
19
22
|
function sync() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
if (_provider) {
|
|
24
|
+
// Use RouterProvider for parsing
|
|
25
|
+
const parsed = _provider.parse();
|
|
26
|
+
const path = parsed.pathname || '/';
|
|
27
|
+
const m = matchRoute(path.startsWith('#') ? path : `#${path}`, ROUTES);
|
|
28
|
+
_route = m?.route ?? '/';
|
|
29
|
+
_params = { ...(m?.params ?? {}), ...parsed.params };
|
|
30
|
+
} else {
|
|
31
|
+
// Fallback: direct hash parsing
|
|
32
|
+
const hash = window.location.hash;
|
|
33
|
+
const m = matchRoute(hash, ROUTES);
|
|
34
|
+
_route = m?.route ?? '/';
|
|
35
|
+
_params = m?.params ?? {};
|
|
36
|
+
}
|
|
24
37
|
}
|
|
25
38
|
|
|
26
|
-
export function initRouter() {
|
|
39
|
+
export function initRouter(provider?: RouterProvider) {
|
|
27
40
|
if (_initialized) return;
|
|
28
41
|
_initialized = true;
|
|
42
|
+
_provider = provider;
|
|
29
43
|
sync();
|
|
30
44
|
window.addEventListener('hashchange', sync);
|
|
45
|
+
window.addEventListener('popstate', sync);
|
|
31
46
|
}
|
|
32
47
|
|
|
33
48
|
export function getRoute(): string {
|
|
@@ -37,3 +52,7 @@ export function getRoute(): string {
|
|
|
37
52
|
export function getParams(): Record<string, string> {
|
|
38
53
|
return _params;
|
|
39
54
|
}
|
|
55
|
+
|
|
56
|
+
export function getRouterProviderInstance(): RouterProvider | undefined {
|
|
57
|
+
return _provider;
|
|
58
|
+
}
|