@svadmin/ui 0.34.9 → 0.34.10
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/component-registry.svelte.js +1 -0
- package/dist/components/AICommandBar.svelte +3 -3
- package/dist/components/AboutSettings.svelte +2 -2
- package/dist/components/AdminApp.svelte +2 -3
- package/dist/components/AnomalyBadge.svelte +1 -1
- package/dist/components/AppearanceSettings.svelte +5 -6
- package/dist/components/AuditLogDrawer.svelte +1 -1
- package/dist/components/AuditLogViewer.svelte +1 -1
- package/dist/components/AutoForm.svelte +5 -4
- package/dist/components/AutoTable.svelte +17 -17
- package/dist/components/Breadcrumbs.svelte +1 -0
- package/dist/components/ChatDialog.svelte +5 -4
- package/dist/components/ComboboxField.svelte +3 -3
- package/dist/components/CommandPalette.svelte +3 -3
- package/dist/components/CopilotPanel.svelte +2 -2
- package/dist/components/CreatePage.svelte +0 -1
- package/dist/components/DevTools.svelte +4 -3
- package/dist/components/DraggableHeader.svelte +2 -1
- package/dist/components/EditPage.svelte +1 -1
- package/dist/components/EmptyState.svelte +1 -1
- package/dist/components/FieldRenderer.svelte +7 -6
- package/dist/components/Header.svelte +1 -0
- package/dist/components/InferencerPanel.svelte +4 -3
- package/dist/components/InfiniteList.svelte +3 -4
- package/dist/components/KeyboardShortcuts.svelte +3 -3
- package/dist/components/Layout.svelte +5 -5
- package/dist/components/MarkdownRenderer.svelte +3 -5
- package/dist/components/ModalForm.svelte +1 -3
- package/dist/components/PageSkeleton.svelte +4 -4
- package/dist/components/PasswordInput.svelte +1 -2
- package/dist/components/PermissionMatrix.svelte +5 -5
- package/dist/components/ProfilePage.svelte +1 -1
- package/dist/components/RolesSettings.svelte +1 -2
- package/dist/components/SettingsPage.svelte +4 -5
- package/dist/components/ShowPage.svelte +4 -6
- package/dist/components/Sidebar.svelte +12 -11
- package/dist/components/SidebarItem.svelte +1 -2
- package/dist/components/StepsForm.svelte +1 -1
- package/dist/components/TaskList.svelte +1 -1
- package/dist/components/TaskQueueDrawer.svelte +2 -1
- package/dist/components/TenantSwitcher.svelte +1 -2
- package/dist/components/Toast.svelte +1 -0
- package/dist/components/TooltipButton.svelte +1 -0
- package/dist/components/VirtualTable.svelte +1 -1
- package/dist/components/VoiceInput.svelte +1 -0
- package/dist/components/buttons/ImportButton.svelte +1 -1
- package/dist/components/buttons/ListButton.svelte +1 -1
- package/dist/components/charts/BarChart.svelte +1 -1
- package/dist/components/charts/LineChart.svelte +3 -3
- package/dist/components/charts/PieChart.svelte +2 -2
- package/dist/components/fieldComponentMap.js +2 -1
- package/dist/components/fields/ArrayField.svelte +1 -1
- package/dist/components/fields/MarkdownField.svelte +1 -0
- package/dist/components/fields/MultiSelectField.svelte +1 -1
- package/dist/components/fields/TagField.svelte +1 -1
- package/dist/components/ui/alert/index.js +1 -0
- package/dist/components/ui/badge/index.js +1 -0
- package/dist/components/ui/button/button.svelte +1 -0
- package/dist/components/ui/button/index.js +1 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +1 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.svelte +1 -1
- package/dist/components/ui/sheet/sheet.svelte +1 -0
- package/dist/components/ui/tabs/tabs.svelte +1 -0
- package/package.json +13 -13
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/* eslint-disable svelte/no-useless-children-snippet */
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
import { fade, fly } from 'svelte/transition';
|
|
4
5
|
import Sidebar from './Sidebar.svelte';
|
|
@@ -13,7 +14,6 @@
|
|
|
13
14
|
import { navigate } from '@svadmin/core/router';
|
|
14
15
|
import { getPath } from '../router-state.svelte.js';
|
|
15
16
|
import { Skeleton } from './ui/skeleton/index.js';
|
|
16
|
-
import { Button } from './ui/button/index.js';
|
|
17
17
|
import * as Sheet from './ui/sheet/index.js';
|
|
18
18
|
import { Menu } from '@lucide/svelte';
|
|
19
19
|
import { getComponentRegistry } from '../component-registry.svelte.js';
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
|
|
25
25
|
let { children, title = 'Admin', menu, siteUrl, routeMode = 'auto' }: { children: Snippet; title?: string; menu?: MenuItem[]; siteUrl?: string; routeMode?: 'hash' | 'path' | 'auto' } = $props();
|
|
26
26
|
|
|
27
|
-
let
|
|
27
|
+
let _hasAuth = true;
|
|
28
28
|
let auth: ReturnType<typeof getAuthProvider> | null = null;
|
|
29
29
|
try {
|
|
30
30
|
auth = getAuthProvider();
|
|
31
31
|
} catch {
|
|
32
|
-
|
|
32
|
+
_hasAuth = false;
|
|
33
33
|
}
|
|
34
34
|
let loading = $state(true);
|
|
35
35
|
let identity = $state<Identity | null>(null);
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
<div class="hidden md:block w-64 bg-sidebar/80 p-4 space-y-4">
|
|
109
109
|
<Skeleton class="h-8 w-32" />
|
|
110
110
|
<div class="space-y-2 mt-6">
|
|
111
|
-
{#each Array(5) as _}
|
|
111
|
+
{#each Array(5) as _, _i (_i)}
|
|
112
112
|
<Skeleton class="h-9 w-full rounded-lg" />
|
|
113
113
|
{/each}
|
|
114
114
|
</div>
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
<div class="flex-1 p-8 space-y-6">
|
|
117
117
|
<Skeleton class="h-8 w-48" />
|
|
118
118
|
<div class="space-y-3">
|
|
119
|
-
{#each Array(4) as _}
|
|
119
|
+
{#each Array(4) as _, _i (_i)}
|
|
120
120
|
<Skeleton class="h-12 w-full" />
|
|
121
121
|
{/each}
|
|
122
122
|
</div>
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/* eslint-disable svelte/no-at-html-tags */
|
|
2
3
|
import * as markedPkg from "marked";
|
|
3
4
|
import * as markedHighlightPkg from "marked-highlight";
|
|
4
5
|
import * as hljsPkg from "highlight.js";
|
|
5
6
|
import * as DOMPurifyPkg from "isomorphic-dompurify";
|
|
6
|
-
import { Check, Copy } from '@lucide/svelte';
|
|
7
|
-
import { Button } from "./ui/button/index.js";
|
|
8
7
|
|
|
9
8
|
const Marked = markedPkg.Marked;
|
|
10
9
|
const markedHighlight = markedHighlightPkg.markedHighlight;
|
|
@@ -13,7 +12,6 @@
|
|
|
13
12
|
|
|
14
13
|
// Only safely import css if hljs exists
|
|
15
14
|
if (hljsModule && Object.keys(hljsModule).length > 0) {
|
|
16
|
-
// @ts-ignore
|
|
17
15
|
import("highlight.js/styles/github-dark.css").catch(() => {});
|
|
18
16
|
}
|
|
19
17
|
|
|
@@ -44,7 +42,7 @@
|
|
|
44
42
|
// Render HTML safely
|
|
45
43
|
const html = $derived(
|
|
46
44
|
hasMarkdownDeps
|
|
47
|
-
? DOMPurify.sanitize(markedObj
|
|
45
|
+
? DOMPurify.sanitize(markedObj?.parse(content || "") as string)
|
|
48
46
|
: `<div style="white-space: pre-wrap">${String(content || "").replace(/</g, "<").replace(/>/g, ">")}</div>`
|
|
49
47
|
);
|
|
50
48
|
|
|
@@ -75,7 +73,7 @@
|
|
|
75
73
|
// We do this via action instead of raw string manipulation for safety
|
|
76
74
|
function enhanceCodeBlocks(node: HTMLElement, _: string) {
|
|
77
75
|
return {
|
|
78
|
-
update(
|
|
76
|
+
update(_newHtml: string) {
|
|
79
77
|
// Find all pre > code blocks that don't have wrappers yet
|
|
80
78
|
const pres = node.querySelectorAll("pre:not(.enhanced)");
|
|
81
79
|
pres.forEach((pre) => {
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import
|
|
3
|
-
import type { FieldDefinition } from '@svadmin/core';
|
|
4
|
-
import { getResource, useForm } from '@svadmin/core';
|
|
2
|
+
import { getResource } from '@svadmin/core';
|
|
5
3
|
import { t } from '@svadmin/core/i18n';
|
|
6
4
|
import * as Dialog from './ui/dialog/index.js';
|
|
7
5
|
import AutoForm from './AutoForm.svelte';
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
{#if type === 'list'}
|
|
28
28
|
<!-- Table Header -->
|
|
29
29
|
<div class="flex gap-4 border-b pb-4">
|
|
30
|
-
{#each Array(4) as _}
|
|
30
|
+
{#each Array(4) as _, _i (_i)}
|
|
31
31
|
<Skeleton class="h-6 flex-1" />
|
|
32
32
|
{/each}
|
|
33
33
|
</div>
|
|
34
34
|
<!-- Table Rows -->
|
|
35
35
|
<div class="space-y-4">
|
|
36
|
-
{#each Array(rows) as _}
|
|
36
|
+
{#each Array(rows) as _, _i (_i)}
|
|
37
37
|
<div class="flex gap-4">
|
|
38
38
|
<Skeleton class="h-6 flex-1" />
|
|
39
39
|
<Skeleton class="h-6 flex-1" />
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
{:else if type === 'show'}
|
|
46
46
|
<!-- Key-Value pairs -->
|
|
47
47
|
<div class="space-y-6">
|
|
48
|
-
{#each Array(rows) as _}
|
|
48
|
+
{#each Array(rows) as _, _i (_i)}
|
|
49
49
|
<div class="space-y-2">
|
|
50
50
|
<Skeleton class="h-4 w-[100px]" />
|
|
51
51
|
<Skeleton class="h-6 w-[250px]" />
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
{:else if type === 'form'}
|
|
56
56
|
<!-- Form Inputs -->
|
|
57
57
|
<div class="space-y-6">
|
|
58
|
-
{#each Array(rows) as _}
|
|
58
|
+
{#each Array(rows) as _, _i (_i)}
|
|
59
59
|
<div class="space-y-2">
|
|
60
60
|
<Skeleton class="h-4 w-[100px]" />
|
|
61
61
|
<Skeleton class="h-10 w-full max-w-md" />
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
3
|
import { Input } from './ui/input/index.js';
|
|
3
|
-
import { Button } from './ui/button/index.js';
|
|
4
4
|
import TooltipButton from './TooltipButton.svelte';
|
|
5
5
|
import { Label } from './ui/label/index.js';
|
|
6
|
-
import { Progress } from './ui/progress/index.js';
|
|
7
6
|
import { Lock, Eye, EyeOff } from '@lucide/svelte';
|
|
8
7
|
import { t } from '@svadmin/core/i18n';
|
|
9
8
|
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
<div class="w-full md:w-64 flex-shrink-0 bg-card rounded-xl shadow-sm border border-border p-4 flex flex-col">
|
|
81
81
|
<h2 class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-4 px-2">{t('permissions.roles') || 'Roles'}</h2>
|
|
82
82
|
<div class="space-y-1 flex-1 overflow-y-auto">
|
|
83
|
-
{#each roles as role}
|
|
83
|
+
{#each roles as role, _i (_i)}
|
|
84
84
|
<button
|
|
85
85
|
onclick={() => selectedRole = role.code}
|
|
86
86
|
class="w-full text-left px-3 py-2.5 rounded-lg text-sm transition-all duration-200 {selectedRole === role.code ? 'bg-primary/10 text-primary font-medium shadow-sm ring-1 ring-primary/20' : 'text-foreground hover:bg-muted'}"
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
<thead class="bg-muted/50">
|
|
134
134
|
<tr>
|
|
135
135
|
<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>
|
|
136
|
-
{#each actions as action}
|
|
136
|
+
{#each actions as action, _i (_i)}
|
|
137
137
|
<th scope="col" class="px-2 py-3 text-center text-sm font-semibold text-foreground">
|
|
138
138
|
<div class="flex flex-col items-center">
|
|
139
139
|
<span>{action.name}</span>
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
<td colspan={actions.length + 1} class="py-10 text-center text-muted-foreground">{t('permissions.noResources') || 'No resources available'}</td>
|
|
150
150
|
</tr>
|
|
151
151
|
{/if}
|
|
152
|
-
{#each resources as resource, i}
|
|
152
|
+
{#each resources as resource, i (i)}
|
|
153
153
|
<!-- Section Grouping Header -->
|
|
154
154
|
{#if resource.section && (i === 0 || resource.section !== resources[i-1].section)}
|
|
155
155
|
<tr class="bg-primary/5">
|
|
@@ -164,14 +164,14 @@
|
|
|
164
164
|
</td>
|
|
165
165
|
|
|
166
166
|
<!-- Action Checkboxes -->
|
|
167
|
-
{#each actions as action}
|
|
167
|
+
{#each actions as action, _i (_i)}
|
|
168
168
|
{@const granted = selectedRole ? isGranted(selectedRole, resource.code, action.code) : false}
|
|
169
169
|
<td class="whitespace-nowrap px-3 py-3 text-center">
|
|
170
170
|
<div class="flex items-center justify-center">
|
|
171
171
|
<button
|
|
172
172
|
disabled={loading || !selectedRole}
|
|
173
173
|
aria-label="Toggle {action.name}"
|
|
174
|
-
onclick={() => onToggle(selectedRole
|
|
174
|
+
onclick={() => onToggle(selectedRole as string, resource.code, action.code, !granted)}
|
|
175
175
|
class="w-6 h-6 rounded border flex items-center justify-center transition-all focus:outline-none focus:ring-2 focus:ring-primary/50 {granted ? 'bg-primary border-primary text-primary-foreground' : 'border-input bg-background hover:bg-accent'} {loading || !selectedRole ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}"
|
|
176
176
|
>
|
|
177
177
|
<svg class="w-3.5 h-3.5 {granted ? 'block' : 'hidden'}" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
|
|
54
54
|
// Avatar upload
|
|
55
55
|
let avatarUploading = $state(false);
|
|
56
|
-
let fileInput = $state<HTMLInputElement>(null!);
|
|
56
|
+
let fileInput = $state<HTMLInputElement>(null!); // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
57
57
|
|
|
58
58
|
async function handleAvatarChange(e: Event) {
|
|
59
59
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { t } from '@svadmin/core/i18n';
|
|
3
3
|
import { getAuthProvider, getResources } from '@svadmin/core';
|
|
4
|
-
import type { Role } from '@svadmin/core';
|
|
5
4
|
import PermissionMatrix from './PermissionMatrix.svelte';
|
|
6
5
|
import type { RoleInfo, ResourceInfo, ActionInfo } from '../types.js';
|
|
7
6
|
import { toast } from '@svadmin/core/toast';
|
|
@@ -117,7 +116,7 @@
|
|
|
117
116
|
} catch (e) {
|
|
118
117
|
toast.error((e as Error).message);
|
|
119
118
|
// Rollback on fail
|
|
120
|
-
delete permissionsCache[roleCode];
|
|
119
|
+
delete permissionsCache[roleCode]; // eslint-disable-line @typescript-eslint/no-dynamic-delete
|
|
121
120
|
loadPermissionsForRole(roleCode);
|
|
122
121
|
} finally {
|
|
123
122
|
saving = false;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { t } from '@svadmin/core/i18n';
|
|
3
3
|
import { navigate } from '@svadmin/core/router';
|
|
4
|
-
import { currentPath } from '@svadmin/core/router';
|
|
5
4
|
import { User, Palette, Info, Shield, FileSearch } from '@lucide/svelte';
|
|
6
5
|
import ProfilePage from './ProfilePage.svelte';
|
|
7
6
|
import AppearanceSettings from './AppearanceSettings.svelte';
|
|
@@ -56,8 +55,8 @@
|
|
|
56
55
|
<nav class="w-full lg:w-56 shrink-0 bg-muted/10 shadow-[inset_0_-1px_0_rgba(0,0,0,0.05)] lg:shadow-[inset_-1px_0_0_rgba(0,0,0,0.05)] dark:shadow-[inset_-1px_0_0_rgba(255,255,255,0.05)]">
|
|
57
56
|
<!-- Mobile: horizontal scroll tabs -->
|
|
58
57
|
<div class="flex lg:hidden overflow-x-auto px-4 py-2 gap-1">
|
|
59
|
-
{#each sections as section}
|
|
60
|
-
{#each section.items as item}
|
|
58
|
+
{#each sections as section, _i (_i)}
|
|
59
|
+
{#each section.items as item, _i (_i)}
|
|
61
60
|
{@const active = activeKey === item.key}
|
|
62
61
|
<button
|
|
63
62
|
class="flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md whitespace-nowrap transition-colors
|
|
@@ -77,13 +76,13 @@
|
|
|
77
76
|
<h2 class="text-lg font-semibold text-foreground">{t('settings.title')}</h2>
|
|
78
77
|
<p class="text-xs text-muted-foreground mt-1">{t('settings.settingsDescription')}</p>
|
|
79
78
|
</div>
|
|
80
|
-
{#each sections as section}
|
|
79
|
+
{#each sections as section, _i (_i)}
|
|
81
80
|
<div>
|
|
82
81
|
<h3 class="px-3 mb-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60">
|
|
83
82
|
{t(section.group)}
|
|
84
83
|
</h3>
|
|
85
84
|
<div class="space-y-0.5">
|
|
86
|
-
{#each section.items as item}
|
|
85
|
+
{#each section.items as item, _i (_i)}
|
|
87
86
|
{@const active = activeKey === item.key}
|
|
88
87
|
<button
|
|
89
88
|
class="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm transition-all
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { useShow, getResource } from '@svadmin/core';
|
|
3
|
-
import type { FieldDefinition } from '@svadmin/core';
|
|
4
3
|
import { navigate } from '@svadmin/core/router';
|
|
5
|
-
import { t
|
|
6
|
-
import { Button } from './ui/button/index.js';
|
|
4
|
+
import { t } from '@svadmin/core/i18n';
|
|
7
5
|
import * as Card from './ui/card/index.js';
|
|
8
6
|
import { Skeleton } from './ui/skeleton/index.js';
|
|
9
7
|
import PageHeader from './PageHeader.svelte';
|
|
@@ -42,7 +40,7 @@
|
|
|
42
40
|
{#if query.isLoading}
|
|
43
41
|
<Card.Root class="overflow-hidden border-border/40 shadow-sm">
|
|
44
42
|
<Card.Content class="p-0">
|
|
45
|
-
{#each showFields.slice(0, 6) as _, i}
|
|
43
|
+
{#each showFields.slice(0, 6) as _, i (i)}
|
|
46
44
|
<div class="flex flex-col sm:flex-row px-4 sm:px-6 py-3 sm:py-4 {i % 2 === 1 ? 'bg-muted/20' : ''}">
|
|
47
45
|
<Skeleton class="h-4 w-1/2 sm:w-1/4" />
|
|
48
46
|
<Skeleton class="h-4 w-3/4 sm:w-2/5 mt-1 sm:mt-0 sm:ml-auto" />
|
|
@@ -53,8 +51,8 @@
|
|
|
53
51
|
{:else if query.data?.data}
|
|
54
52
|
<Card.Root class="overflow-hidden border-border/40 shadow-sm">
|
|
55
53
|
<Card.Content class="p-0">
|
|
56
|
-
{#each showFields as field, i}
|
|
57
|
-
{@const value = (query.data
|
|
54
|
+
{#each showFields as field, i (i)}
|
|
55
|
+
{@const value = ((query.data as { data: Record<string, unknown> }).data as Record<string, unknown>)[field.key]}
|
|
58
56
|
{@const DisplayComponent = getDisplayComponent(field.type)}
|
|
59
57
|
<div class="flex flex-col sm:flex-row px-4 sm:px-6 py-3 sm:py-4 {i % 2 === 1 ? 'bg-muted/20' : ''}">
|
|
60
58
|
<div class="sm:w-1/3 text-xs sm:text-sm font-medium text-muted-foreground mb-1 sm:mb-0">{field.label}</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/* eslint-disable svelte/prefer-svelte-reactivity */
|
|
2
3
|
import { getResources, canAccessAsync } from '@svadmin/core';
|
|
3
4
|
import type { Identity, MenuItem } from '@svadmin/core';
|
|
4
5
|
import { navigate } from '@svadmin/core/router';
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
: routeMode
|
|
38
39
|
);
|
|
39
40
|
|
|
40
|
-
const
|
|
41
|
+
const _resources = getResources();
|
|
41
42
|
|
|
42
43
|
const iconMap: Record<string, typeof LayoutDashboard> = {
|
|
43
44
|
'dashboard': LayoutDashboard,
|
|
@@ -80,7 +81,7 @@
|
|
|
80
81
|
let navItems = $state.raw<NavItem[]>([]);
|
|
81
82
|
|
|
82
83
|
$effect(() => {
|
|
83
|
-
const
|
|
84
|
+
const _localeVal = getLocale();
|
|
84
85
|
const currentResources = getResources();
|
|
85
86
|
let cancelled = false;
|
|
86
87
|
|
|
@@ -132,7 +133,7 @@
|
|
|
132
133
|
|
|
133
134
|
/** Prefetch resource data on hover for instant navigation */
|
|
134
135
|
const prefetchedResources = new Set<string>();
|
|
135
|
-
function
|
|
136
|
+
function _prefetchResource(resourceName: string) {
|
|
136
137
|
if (resourceName === '/' || prefetchedResources.has(resourceName)) return;
|
|
137
138
|
prefetchedResources.add(resourceName);
|
|
138
139
|
// Trigger a background fetch by navigating the browser cache
|
|
@@ -148,9 +149,9 @@
|
|
|
148
149
|
const key = item.group ?? null;
|
|
149
150
|
if (!groupMap.has(key)) {
|
|
150
151
|
groupMap.set(key, []);
|
|
151
|
-
groups.push({ name: key, items: groupMap.get(key)
|
|
152
|
+
groups.push({ name: key, items: groupMap.get(key) ?? [] });
|
|
152
153
|
}
|
|
153
|
-
groupMap.get(key)
|
|
154
|
+
(groupMap.get(key) ?? []).push(item);
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
return groups;
|
|
@@ -233,17 +234,17 @@
|
|
|
233
234
|
<nav aria-label="Main menu" class="py-4 px-3 space-y-1">
|
|
234
235
|
{#if menu && menu.length > 0}
|
|
235
236
|
<!-- Custom multi-level menu -->
|
|
236
|
-
{#each menu as item}
|
|
237
|
+
{#each menu as item, _i (_i)}
|
|
237
238
|
<SidebarItem {item} currentPath={path} {collapsed} depth={0} />
|
|
238
239
|
{/each}
|
|
239
240
|
{:else}
|
|
240
241
|
<!-- Auto-generated menu from resources (fallback) -->
|
|
241
|
-
{#each navGroups as group}
|
|
242
|
+
{#each navGroups as group, _i (_i)}
|
|
242
243
|
{#if group.name && !collapsed}
|
|
243
244
|
<!-- Grouped section with Collapsible -->
|
|
244
245
|
<Collapsible.Root open={openGroups.has(group.name)} onOpenChange={(isOpen) => {
|
|
245
246
|
const next = new Set(openGroups);
|
|
246
|
-
if (isOpen) next.add(group.name
|
|
247
|
+
if (isOpen) next.add(group.name as string); else next.delete(group.name as string);
|
|
247
248
|
openGroups = next;
|
|
248
249
|
}}>
|
|
249
250
|
<Collapsible.Trigger
|
|
@@ -254,7 +255,7 @@
|
|
|
254
255
|
</Collapsible.Trigger>
|
|
255
256
|
<Collapsible.Content>
|
|
256
257
|
<div class="mt-1 space-y-1">
|
|
257
|
-
{#each group.items as item}
|
|
258
|
+
{#each group.items as item, _i (_i)}
|
|
258
259
|
{@const active = isActive(item.path)}
|
|
259
260
|
<a
|
|
260
261
|
href={effectiveRouteMode === 'hash' ? `#${item.path}` : item.path}
|
|
@@ -273,7 +274,7 @@
|
|
|
273
274
|
</Collapsible.Root>
|
|
274
275
|
{:else}
|
|
275
276
|
<!-- Ungrouped items (flat) -->
|
|
276
|
-
{#each group.items as item}
|
|
277
|
+
{#each group.items as item, _i (_i)}
|
|
277
278
|
{@const active = isActive(item.path)}
|
|
278
279
|
{#if collapsed}
|
|
279
280
|
<Tooltip.Root>
|
|
@@ -336,7 +337,7 @@
|
|
|
336
337
|
</Button>
|
|
337
338
|
{#if colorPickerOpen}
|
|
338
339
|
<div class="absolute bottom-full left-0 mb-1 z-50 w-40 rounded-lg border bg-popover p-1 text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95">
|
|
339
|
-
{#each getColorThemes() as ct}
|
|
340
|
+
{#each getColorThemes() as ct, _i (_i)}
|
|
340
341
|
<button
|
|
341
342
|
class="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground transition-colors"
|
|
342
343
|
onclick={() => { setColorTheme(ct.id as typeof ct.id & import('@svadmin/core').ColorTheme); colorPickerOpen = false; }}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { MenuItem } from '@svadmin/core';
|
|
3
|
-
import { t } from '@svadmin/core/i18n';
|
|
4
3
|
import * as Collapsible from './ui/collapsible/index.js';
|
|
5
4
|
import * as Tooltip from './ui/tooltip/index.js';
|
|
6
5
|
import SidebarItem from './SidebarItem.svelte';
|
|
@@ -84,7 +83,7 @@
|
|
|
84
83
|
</Collapsible.Trigger>
|
|
85
84
|
<Collapsible.Content>
|
|
86
85
|
<div class="mt-0.5 space-y-0.5" style="padding-left: {depth > 0 ? 0 : 0}px">
|
|
87
|
-
{#each item.children as child}
|
|
86
|
+
{#each item.children as child, _i (_i)}
|
|
88
87
|
<SidebarItem item={child} {currentPath} {collapsed} depth={depth + 1} />
|
|
89
88
|
{/each}
|
|
90
89
|
</div>
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
</Table.Row>
|
|
111
111
|
</Table.Header>
|
|
112
112
|
<Table.Body>
|
|
113
|
-
{#each resolvedTasks as task}
|
|
113
|
+
{#each resolvedTasks as task, _i (_i)}
|
|
114
114
|
<Table.Row class="cursor-pointer hover:bg-muted/40" onclick={() => onSelect?.(task)}>
|
|
115
115
|
<Table.Cell class="min-w-[240px]">
|
|
116
116
|
<div class="space-y-1">
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
initialTab?: TaskTab;
|
|
37
37
|
} = $props();
|
|
38
38
|
|
|
39
|
+
// eslint-disable-next-line svelte/prefer-writable-derived
|
|
39
40
|
let activeTab = $state<TaskTab>('tasks');
|
|
40
41
|
let selectedTaskId = $state<string | null>(null);
|
|
41
42
|
let searchQuery = $state('');
|
|
@@ -283,7 +284,7 @@
|
|
|
283
284
|
</div>
|
|
284
285
|
<Select bind:value={statusFilter} aria-label={t('task.statusFilterLabel')}>
|
|
285
286
|
<option value="all">{t('task.statusAll')}</option>
|
|
286
|
-
{#each availableStatuses as status}
|
|
287
|
+
{#each availableStatuses as status, _i (_i)}
|
|
287
288
|
<option value={status}>{status}</option>
|
|
288
289
|
{/each}
|
|
289
290
|
</Select>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import { ChevronsUpDown, Building2, Check } from '@lucide/svelte';
|
|
4
|
-
import { Button } from './ui/button/index.js';
|
|
5
4
|
import * as DropdownMenu from './ui/dropdown-menu/index.js';
|
|
6
5
|
import type { Tenant } from '../types.js';
|
|
7
6
|
|
|
@@ -61,7 +60,7 @@
|
|
|
61
60
|
<DropdownMenu.Separator />
|
|
62
61
|
{/if}
|
|
63
62
|
<div class="px-2 py-1.5 text-xs text-muted-foreground">Workspaces</div>
|
|
64
|
-
{#each tenants as tenant}
|
|
63
|
+
{#each tenants as tenant, _i (_i)}
|
|
65
64
|
<DropdownMenu.Item
|
|
66
65
|
class="flex items-center gap-3 cursor-pointer"
|
|
67
66
|
onclick={() => selectTenant(tenant.id)}
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
let scrollTop = $state(0);
|
|
38
38
|
let containerRef = $state<HTMLDivElement | null>(null);
|
|
39
39
|
|
|
40
|
-
const
|
|
40
|
+
const _totalHeight = $derived(items.length * rowHeight);
|
|
41
41
|
|
|
42
42
|
const visibleRange = $derived.by(() => {
|
|
43
43
|
const start = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan);
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<div class="w-full overflow-x-auto {className}">
|
|
25
25
|
<svg viewBox="0 0 {totalWidth} {height + 30}" class="w-full" style="min-width: {Math.min(totalWidth, 300)}px;">
|
|
26
26
|
<!-- Bars -->
|
|
27
|
-
{#each data as point, i}
|
|
27
|
+
{#each data as point, i (i)}
|
|
28
28
|
{@const barHeight = (point.value / maxValue) * (height - 20)}
|
|
29
29
|
{@const x = 20 + i * (barWidth + gap)}
|
|
30
30
|
{@const y = height - barHeight}
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
<div class="w-full overflow-x-auto {className}">
|
|
47
47
|
<svg viewBox="0 0 {chartWidth} {height}" class="w-full" style="min-width: {Math.min(chartWidth, 300)}px;">
|
|
48
48
|
<!-- Grid lines -->
|
|
49
|
-
{#each [0, 0.25, 0.5, 0.75, 1] as ratio}
|
|
49
|
+
{#each [0, 0.25, 0.5, 0.75, 1] as ratio, _i (_i)}
|
|
50
50
|
{@const y = padding.top + chartHeight * (1 - ratio)}
|
|
51
51
|
<line
|
|
52
52
|
x1={padding.left}
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
|
|
89
89
|
<!-- Dots -->
|
|
90
90
|
{#if showDots}
|
|
91
|
-
{#each data as point, i}
|
|
91
|
+
{#each data as point, i (i)}
|
|
92
92
|
<circle
|
|
93
93
|
cx={getX(i)}
|
|
94
94
|
cy={getY(point.value)}
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
{/if}
|
|
103
103
|
|
|
104
104
|
<!-- X-axis labels -->
|
|
105
|
-
{#each data as point, i}
|
|
105
|
+
{#each data as point, i (i)}
|
|
106
106
|
<text
|
|
107
107
|
x={getX(i)}
|
|
108
108
|
y={height - 4}
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
|
|
60
60
|
<div class="inline-flex flex-col items-center gap-3 {className}">
|
|
61
61
|
<svg viewBox="0 0 {size} {size}" width={size} height={size}>
|
|
62
|
-
{#each slices as slice, i}
|
|
62
|
+
{#each slices as slice, i (i)}
|
|
63
63
|
<path
|
|
64
64
|
d={getArcPath(slice.startAngle, slice.endAngle, radius, innerRadius)}
|
|
65
65
|
fill={slice.color}
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
|
|
85
85
|
<!-- Legend -->
|
|
86
86
|
<div class="flex flex-wrap justify-center gap-x-4 gap-y-1">
|
|
87
|
-
{#each slices as slice}
|
|
87
|
+
{#each slices as slice, _i (_i)}
|
|
88
88
|
<div class="flex items-center gap-1.5 text-xs">
|
|
89
89
|
<span class="h-2.5 w-2.5 rounded-full" style="background-color: {slice.color};"></span>
|
|
90
90
|
<span class="text-muted-foreground">{slice.label}</span>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1
2
|
// Shared field type → display component mapping
|
|
2
3
|
// Used by both ShowPage and FieldRenderer to avoid duplicate if/else chains.
|
|
3
4
|
// Components are lazily resolved by field type at runtime.
|
|
4
5
|
// Default display component map (read-only field display)
|
|
5
6
|
// Dynamically imports components only when first used
|
|
6
|
-
const
|
|
7
|
+
const _displayCache = new Map();
|
|
7
8
|
// Pre-import all field display components (tree-shaken by bundler)
|
|
8
9
|
import BooleanField from './fields/BooleanField.svelte';
|
|
9
10
|
import ImageField from './fields/ImageField.svelte';
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
</div>
|
|
60
60
|
|
|
61
61
|
<div class="space-y-4 pt-2">
|
|
62
|
-
{#each arrayVal as item, i}
|
|
62
|
+
{#each arrayVal as item, i (i)}
|
|
63
63
|
<div class="relative rounded-md border bg-card p-4 pt-6 shadow-sm">
|
|
64
64
|
<div class="absolute right-2 top-2">
|
|
65
65
|
<TooltipButton tooltip={t('common.remove') || 'Remove'} variant="ghost" size="icon" class="h-8 w-8 text-muted-foreground hover:text-destructive transition-colors" onclick={() => handleRemove(i)}>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
{#if items.length > 0}
|
|
13
13
|
<div class="flex flex-wrap gap-1">
|
|
14
|
-
{#each items as v}
|
|
14
|
+
{#each items as v, _i (_i)}
|
|
15
15
|
{@const label = options?.find((o: { label: string; value: string | number }) => o.value === v)?.label ?? String(v)}
|
|
16
16
|
<Badge variant="secondary" class="font-normal">{label}</Badge>
|
|
17
17
|
{/each}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const tags = $derived(Array.isArray(value) ? value : value ? [value] : []);
|
|
6
6
|
</script>
|
|
7
7
|
<span class="inline-flex flex-wrap gap-1">
|
|
8
|
-
{#each tags as tag}
|
|
8
|
+
{#each tags as tag, _i (_i)}
|
|
9
9
|
<Badge variant="secondary">{tag}</Badge>
|
|
10
10
|
{/each}
|
|
11
11
|
{#if tags.length === 0}<span class="text-muted-foreground">—</span>{/if}
|