@svadmin/lite 0.25.7 → 0.27.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/README.md +11 -5
- package/dist/components/LiteAnnouncementBanner.svelte +88 -0
- package/dist/components/LiteAnnouncementBanner.svelte.d.ts +12 -0
- package/dist/components/LiteAsyncExportModal.svelte +113 -0
- package/dist/components/LiteAsyncExportModal.svelte.d.ts +14 -0
- package/dist/components/LiteAuditLog.svelte +102 -36
- package/dist/components/LiteAuditLog.svelte.d.ts +6 -3
- package/dist/components/LiteConcurrentConflictModal.svelte +110 -0
- package/dist/components/LiteConcurrentConflictModal.svelte.d.ts +21 -0
- package/dist/components/LiteContextMenu.svelte +114 -0
- package/dist/components/LiteContextMenu.svelte.d.ts +18 -0
- package/dist/components/LiteCronExpressionBuilder.svelte +78 -0
- package/dist/components/LiteCronExpressionBuilder.svelte.d.ts +17 -0
- package/dist/components/LiteDualApprovalGate.svelte +58 -0
- package/dist/components/LiteDualApprovalGate.svelte.d.ts +11 -0
- package/dist/components/LiteJsonViewer.svelte +88 -0
- package/dist/components/LiteJsonViewer.svelte.d.ts +9 -0
- package/dist/components/LiteKeyboardShortcutsModal.svelte +56 -0
- package/dist/components/LiteKeyboardShortcutsModal.svelte.d.ts +13 -0
- package/dist/components/LiteKpiCard.svelte +169 -0
- package/dist/components/LiteKpiCard.svelte.d.ts +25 -0
- package/dist/components/LiteMetricGrid.svelte +143 -0
- package/dist/components/LiteMetricGrid.svelte.d.ts +31 -0
- package/dist/components/LiteMicroAppFrame.svelte +57 -0
- package/dist/components/LiteMicroAppFrame.svelte.d.ts +15 -0
- package/dist/components/LiteOrgChart.svelte +78 -0
- package/dist/components/LiteOrgChart.svelte.d.ts +17 -0
- package/dist/components/LitePresenceAvatarGroup.svelte +118 -15
- package/dist/components/LitePresenceAvatarGroup.svelte.d.ts +8 -3
- package/dist/components/LiteResultPage.svelte +135 -0
- package/dist/components/LiteResultPage.svelte.d.ts +17 -0
- package/dist/components/LiteSafeDeleteConfirm.svelte +86 -0
- package/dist/components/LiteSafeDeleteConfirm.svelte.d.ts +21 -0
- package/dist/components/LiteSensitiveDataMask.svelte +34 -2
- package/dist/components/LiteSensitiveDataMask.svelte.d.ts +1 -2
- package/dist/components/LiteSignaturePad.svelte +3 -1
- package/dist/components/LiteSignaturePad.svelte.d.ts +1 -0
- package/dist/components/LiteStepWizard.svelte +98 -0
- package/dist/components/LiteStepWizard.svelte.d.ts +16 -0
- package/dist/components/LiteTaskCenter.svelte +133 -0
- package/dist/components/LiteTaskCenter.svelte.d.ts +24 -0
- package/dist/components/LiteTextDiffViewer.svelte +111 -0
- package/dist/components/LiteTextDiffViewer.svelte.d.ts +10 -0
- package/dist/components/LiteTour.svelte +4 -0
- package/dist/components/LiteTour.svelte.d.ts +4 -0
- package/dist/components/LiteTrendIndicator.svelte +119 -0
- package/dist/components/LiteTrendIndicator.svelte.d.ts +19 -0
- package/dist/components/LiteUnsavedChangesGuard.svelte +53 -0
- package/dist/components/LiteUnsavedChangesGuard.svelte.d.ts +10 -0
- package/dist/components/LiteVirtualTreeSelect.svelte +92 -0
- package/dist/components/LiteVirtualTreeSelect.svelte.d.ts +20 -0
- package/dist/components/LiteWatermarkContainer.svelte +58 -0
- package/dist/components/LiteWatermarkContainer.svelte.d.ts +9 -0
- package/dist/components/LiteWorkflowStepper.svelte +56 -0
- package/dist/components/LiteWorkflowStepper.svelte.d.ts +14 -0
- package/dist/components/widgets/LiteGaugeChart.svelte +50 -3
- package/dist/components/widgets/LiteGaugeChart.svelte.d.ts +12 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +24 -0
- package/dist/rich-content.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export interface LiteDeleteImpact {
|
|
3
|
+
label: string;
|
|
4
|
+
count?: number;
|
|
5
|
+
description?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
title?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
impactItems?: LiteDeleteImpact[];
|
|
12
|
+
action: string;
|
|
13
|
+
method?: 'POST' | 'post';
|
|
14
|
+
confirmLabel?: string;
|
|
15
|
+
cancelHref?: string;
|
|
16
|
+
cancelLabel?: string;
|
|
17
|
+
confirmationText?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
class?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
title = 'Delete this record?',
|
|
24
|
+
description = 'This action cannot be undone. Review the impact before continuing.',
|
|
25
|
+
impactItems = [],
|
|
26
|
+
action,
|
|
27
|
+
method = 'POST',
|
|
28
|
+
confirmLabel = 'Delete permanently',
|
|
29
|
+
cancelHref,
|
|
30
|
+
cancelLabel = 'Cancel',
|
|
31
|
+
confirmationText,
|
|
32
|
+
name = 'confirm',
|
|
33
|
+
class: className = '',
|
|
34
|
+
}: Props = $props();
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<section class="lite-safe-delete lite-card {className}" aria-label={title}>
|
|
38
|
+
<header>
|
|
39
|
+
<h2>{title}</h2>
|
|
40
|
+
<p>{description}</p>
|
|
41
|
+
</header>
|
|
42
|
+
|
|
43
|
+
{#if impactItems.length > 0}
|
|
44
|
+
<ul class="lite-safe-delete-impact">
|
|
45
|
+
{#each impactItems as item (item.label)}
|
|
46
|
+
<li>
|
|
47
|
+
<strong>{item.label}</strong>
|
|
48
|
+
{#if typeof item.count === 'number'}<span class="lite-safe-delete-count">{item.count}</span>{/if}
|
|
49
|
+
{#if item.description}<span class="lite-safe-delete-desc">{item.description}</span>{/if}
|
|
50
|
+
</li>
|
|
51
|
+
{/each}
|
|
52
|
+
</ul>
|
|
53
|
+
{/if}
|
|
54
|
+
|
|
55
|
+
<form method={method} action={action}>
|
|
56
|
+
{#if confirmationText}
|
|
57
|
+
<label for="lite-delete-confirmation">
|
|
58
|
+
Type <strong>{confirmationText}</strong> to confirm
|
|
59
|
+
</label>
|
|
60
|
+
<input id="lite-delete-confirmation" name={name} autocomplete="off" required />
|
|
61
|
+
{/if}
|
|
62
|
+
<div class="lite-safe-delete-actions">
|
|
63
|
+
<button type="submit" class="lite-safe-delete-danger">{confirmLabel}</button>
|
|
64
|
+
{#if cancelHref}<a href={cancelHref}>{cancelLabel}</a>{/if}
|
|
65
|
+
</div>
|
|
66
|
+
</form>
|
|
67
|
+
</section>
|
|
68
|
+
|
|
69
|
+
<style>
|
|
70
|
+
.lite-safe-delete { padding: 16px; border-color: #ffd8d2; }
|
|
71
|
+
.lite-safe-delete header h2 { margin: 0; color: #1d2129; font-size: 16px; }
|
|
72
|
+
.lite-safe-delete header p { margin: 6px 0 12px; color: #4e5969; font-size: 12px; line-height: 1.5; }
|
|
73
|
+
.lite-safe-delete-impact { margin: 0 0 12px; padding: 0; list-style: none; }
|
|
74
|
+
.lite-safe-delete-impact li { display: flex; align-items: baseline; padding: 7px 9px; border: 1px solid #ffe4e0; border-radius: 4px; background: #fff7f6; color: #4e5969; font-size: 12px; }
|
|
75
|
+
.lite-safe-delete-impact li > * + * { margin-left: 6px; }
|
|
76
|
+
.lite-safe-delete-impact li + li { margin-top: 6px; }
|
|
77
|
+
.lite-safe-delete-impact strong { color: #1d2129; }
|
|
78
|
+
.lite-safe-delete-count { padding: 1px 5px; border-radius: 999px; background: #f53f3f; color: #fff; font-size: 11px; }
|
|
79
|
+
.lite-safe-delete-desc { color: #86909c; }
|
|
80
|
+
.lite-safe-delete label { display: block; margin-bottom: 5px; color: #4e5969; font-size: 12px; }
|
|
81
|
+
.lite-safe-delete input { width: 100%; box-sizing: border-box; padding: 7px 9px; border: 1px solid #e5e6eb; border-radius: 4px; font: inherit; font-size: 13px; }
|
|
82
|
+
.lite-safe-delete-actions { display: flex; align-items: center; margin-top: 12px; }
|
|
83
|
+
.lite-safe-delete-actions > * + * { margin-left: 12px; }
|
|
84
|
+
.lite-safe-delete-danger { padding: 7px 12px; border: 0; border-radius: 4px; background: #f53f3f; color: #fff; font-size: 12px; cursor: pointer; }
|
|
85
|
+
.lite-safe-delete-actions a { color: #4e5969; font-size: 12px; }
|
|
86
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface LiteDeleteImpact {
|
|
2
|
+
label: string;
|
|
3
|
+
count?: number;
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
6
|
+
interface Props {
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
impactItems?: LiteDeleteImpact[];
|
|
10
|
+
action: string;
|
|
11
|
+
method?: 'POST' | 'post';
|
|
12
|
+
confirmLabel?: string;
|
|
13
|
+
cancelHref?: string;
|
|
14
|
+
cancelLabel?: string;
|
|
15
|
+
confirmationText?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
class?: string;
|
|
18
|
+
}
|
|
19
|
+
declare const LiteSafeDeleteConfirm: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type LiteSafeDeleteConfirm = ReturnType<typeof LiteSafeDeleteConfirm>;
|
|
21
|
+
export default LiteSafeDeleteConfirm;
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
interface Props {
|
|
3
3
|
value?: string | number | null;
|
|
4
|
-
type?:
|
|
5
|
-
|
|
4
|
+
type?:
|
|
5
|
+
| 'phone'
|
|
6
|
+
| 'mobile'
|
|
7
|
+
| 'id-card'
|
|
8
|
+
| 'idCard'
|
|
9
|
+
| 'email'
|
|
10
|
+
| 'bank-card'
|
|
11
|
+
| 'bankCard'
|
|
12
|
+
| 'name'
|
|
13
|
+
| 'secret'
|
|
14
|
+
| 'custom';
|
|
6
15
|
class?: string;
|
|
7
16
|
}
|
|
8
17
|
|
|
@@ -14,13 +23,36 @@
|
|
|
14
23
|
|
|
15
24
|
const rawString = $derived(String(value ?? ''));
|
|
16
25
|
|
|
26
|
+
function maskName(val: string): string {
|
|
27
|
+
const trimmed = val.trim();
|
|
28
|
+
if (!trimmed) return '—';
|
|
29
|
+
if (trimmed.includes(' ')) {
|
|
30
|
+
return trimmed
|
|
31
|
+
.split(/\s+/)
|
|
32
|
+
.map((part) => (part.length <= 1 ? '*' : `${part[0]}${'*'.repeat(Math.max(1, part.length - 2))}${part.length > 2 ? part.slice(-1) : '*'}`))
|
|
33
|
+
.join(' ');
|
|
34
|
+
}
|
|
35
|
+
const len = trimmed.length;
|
|
36
|
+
if (len <= 1) return '*';
|
|
37
|
+
if (len === 2) return `${trimmed[0]}*`;
|
|
38
|
+
if (len === 3) return `${trimmed[0]}*${trimmed[2]}`;
|
|
39
|
+
return `${trimmed[0]}${'*'.repeat(len - 2)}${trimmed.slice(-1)}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
17
42
|
function mask(val: string, maskType: string): string {
|
|
18
43
|
if (!val) return '—';
|
|
19
44
|
switch (maskType) {
|
|
20
45
|
case 'phone':
|
|
46
|
+
case 'mobile':
|
|
21
47
|
return val.length >= 7 ? `${val.slice(0, 3)}****${val.slice(-4)}` : '****';
|
|
22
48
|
case 'id-card':
|
|
49
|
+
case 'idCard':
|
|
23
50
|
return val.length >= 10 ? `${val.slice(0, 6)}********${val.slice(-4)}` : '********';
|
|
51
|
+
case 'bank-card':
|
|
52
|
+
case 'bankCard':
|
|
53
|
+
return val.length >= 8 ? `${val.slice(0, 4)} **** **** ${val.slice(-4)}` : '**** ****';
|
|
54
|
+
case 'name':
|
|
55
|
+
return maskName(val);
|
|
24
56
|
case 'email': {
|
|
25
57
|
const parts = val.split('@');
|
|
26
58
|
const [name, domain] = parts;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
interface Props {
|
|
2
2
|
value?: string | number | null;
|
|
3
|
-
type?: 'phone' | 'id-card' | 'email' | 'bank-card' | 'secret' | 'custom';
|
|
4
|
-
/** Caller class merged with the built-in class list. */
|
|
3
|
+
type?: 'phone' | 'mobile' | 'id-card' | 'idCard' | 'email' | 'bank-card' | 'bankCard' | 'name' | 'secret' | 'custom';
|
|
5
4
|
class?: string;
|
|
6
5
|
}
|
|
7
6
|
declare const LiteSensitiveDataMask: import("svelte").Component<Props, {}, "">;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
interface Props {
|
|
5
5
|
name?: string;
|
|
6
6
|
value?: string;
|
|
7
|
+
alt?: string;
|
|
7
8
|
label?: string;
|
|
8
9
|
formAction?: string;
|
|
9
10
|
disabled?: boolean;
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
let {
|
|
14
15
|
name = 'signature',
|
|
15
16
|
value = '',
|
|
17
|
+
alt = 'Signature preview',
|
|
16
18
|
label = 'Electronic Signature',
|
|
17
19
|
formAction = '',
|
|
18
20
|
disabled = false,
|
|
@@ -28,7 +30,7 @@
|
|
|
28
30
|
|
|
29
31
|
{#if value}
|
|
30
32
|
<div class="sv-lite-sig-preview">
|
|
31
|
-
<img src={value} alt=
|
|
33
|
+
<img src={value} alt={alt} class="sv-lite-sig-img" />
|
|
32
34
|
</div>
|
|
33
35
|
{:else}
|
|
34
36
|
<div class="sv-lite-sig-empty">
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
export interface LiteStepItem {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
steps: LiteStepItem[];
|
|
12
|
+
currentStep?: number;
|
|
13
|
+
action?: string;
|
|
14
|
+
class?: string;
|
|
15
|
+
children?: Snippet;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
steps = [],
|
|
20
|
+
currentStep = 0,
|
|
21
|
+
action,
|
|
22
|
+
class: className = '',
|
|
23
|
+
children,
|
|
24
|
+
}: Props = $props();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<div
|
|
28
|
+
class="lite-step-wizard {className}"
|
|
29
|
+
data-svadmin-component="LiteStepWizard"
|
|
30
|
+
data-current-step={currentStep}
|
|
31
|
+
>
|
|
32
|
+
<nav class="lite-steps-nav" aria-label="Wizard Steps">
|
|
33
|
+
<ol class="lite-steps-list">
|
|
34
|
+
{#each steps as step, idx (step.id)}
|
|
35
|
+
{@const isDone = idx < currentStep}
|
|
36
|
+
{@const isCurrent = idx === currentStep}
|
|
37
|
+
<li
|
|
38
|
+
class="lite-step-item"
|
|
39
|
+
class:lite-step-done={isDone}
|
|
40
|
+
class:lite-step-active={isCurrent}
|
|
41
|
+
>
|
|
42
|
+
<span class="lite-step-badge">
|
|
43
|
+
{#if isDone}✓{:else}{idx + 1}{/if}
|
|
44
|
+
</span>
|
|
45
|
+
<span class="lite-step-info">
|
|
46
|
+
<span class="lite-step-title">{step.title}</span>
|
|
47
|
+
{#if step.description}
|
|
48
|
+
<span class="lite-step-desc">{step.description}</span>
|
|
49
|
+
{/if}
|
|
50
|
+
</span>
|
|
51
|
+
{#if idx < steps.length - 1}
|
|
52
|
+
<span class="lite-step-divider" aria-hidden="true"></span>
|
|
53
|
+
{/if}
|
|
54
|
+
</li>
|
|
55
|
+
{/each}
|
|
56
|
+
</ol>
|
|
57
|
+
</nav>
|
|
58
|
+
|
|
59
|
+
<div class="lite-step-body">
|
|
60
|
+
{#if children}{@render children()}{/if}
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
{#if action}
|
|
64
|
+
<form {action} method="POST" class="lite-step-actions">
|
|
65
|
+
<input type="hidden" name="currentStep" value={currentStep} />
|
|
66
|
+
{#if currentStep > 0}
|
|
67
|
+
<button type="submit" name="direction" value="prev" class="lite-btn-secondary">上一步</button>
|
|
68
|
+
{/if}
|
|
69
|
+
<button type="submit" name="direction" value="next" class="lite-btn-primary">
|
|
70
|
+
{currentStep >= steps.length - 1 ? '提交完成' : '下一步'}
|
|
71
|
+
</button>
|
|
72
|
+
</form>
|
|
73
|
+
{/if}
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<style>
|
|
77
|
+
.lite-step-wizard { border: 1px solid #e5e6eb; border-radius: 8px; background: #ffffff; padding: 20px; }
|
|
78
|
+
.lite-steps-nav { margin-bottom: 24px; border-bottom: 1px solid #f2f3f5; padding-bottom: 16px; }
|
|
79
|
+
.lite-steps-list { display: flex; align-items: center; list-style: none; margin: 0; padding: 0; }
|
|
80
|
+
.lite-step-item { display: flex; align-items: center; flex: 1; }
|
|
81
|
+
.lite-step-item > * + * { margin-left: 8px; }
|
|
82
|
+
.lite-step-badge {
|
|
83
|
+
width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center;
|
|
84
|
+
justify-content: center; font-size: 12px; font-weight: 700; background: #f2f3f5; color: #4e5969;
|
|
85
|
+
}
|
|
86
|
+
.lite-step-done .lite-step-badge { background: #00b42a; color: #fff; }
|
|
87
|
+
.lite-step-active .lite-step-badge { background: #165dff; color: #fff; }
|
|
88
|
+
.lite-step-info { display: flex; flex-direction: column; }
|
|
89
|
+
.lite-step-title { font-size: 13px; font-weight: 600; color: #1d2129; }
|
|
90
|
+
.lite-step-desc { font-size: 11px; color: #86909c; }
|
|
91
|
+
.lite-step-divider { flex: 1; height: 1px; background: #e5e6eb; margin: 0 12px; }
|
|
92
|
+
.lite-step-done .lite-step-divider { background: #00b42a; }
|
|
93
|
+
.lite-step-body { min-height: 120px; }
|
|
94
|
+
.lite-step-actions { display: flex; justify-content: flex-end; margin-top: 20px; border-top: 1px solid #f2f3f5; padding-top: 16px; }
|
|
95
|
+
.lite-step-actions > * + * { margin-left: 12px; }
|
|
96
|
+
.lite-btn-primary { padding: 6px 16px; background: #165dff; color: #fff; border: none; border-radius: 4px; font-size: 12px; cursor: pointer; }
|
|
97
|
+
.lite-btn-secondary { padding: 6px 16px; background: #f2f3f5; color: #4e5969; border: 1px solid #e5e6eb; border-radius: 4px; font-size: 12px; cursor: pointer; }
|
|
98
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export interface LiteStepItem {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
}
|
|
7
|
+
interface Props {
|
|
8
|
+
steps: LiteStepItem[];
|
|
9
|
+
currentStep?: number;
|
|
10
|
+
action?: string;
|
|
11
|
+
class?: string;
|
|
12
|
+
children?: Snippet;
|
|
13
|
+
}
|
|
14
|
+
declare const LiteStepWizard: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type LiteStepWizard = ReturnType<typeof LiteStepWizard>;
|
|
16
|
+
export default LiteStepWizard;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export type LiteTaskStatus = 'running' | 'pending' | 'completed' | 'failed' | 'cancelled';
|
|
3
|
+
|
|
4
|
+
export interface LiteBackgroundTask {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
status: LiteTaskStatus;
|
|
8
|
+
progress?: number;
|
|
9
|
+
processed?: number;
|
|
10
|
+
total?: number;
|
|
11
|
+
error?: string;
|
|
12
|
+
downloadHref?: string;
|
|
13
|
+
cancelAction?: string;
|
|
14
|
+
retryAction?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
tasks?: LiteBackgroundTask[];
|
|
19
|
+
title?: string;
|
|
20
|
+
emptyText?: string;
|
|
21
|
+
clearAction?: string;
|
|
22
|
+
clearLabel?: string;
|
|
23
|
+
class?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
tasks = [],
|
|
28
|
+
title = 'Background tasks',
|
|
29
|
+
emptyText = 'No background tasks',
|
|
30
|
+
clearAction,
|
|
31
|
+
clearLabel = 'Clear completed',
|
|
32
|
+
class: className = '',
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
const completedCount = $derived(tasks.filter((task) => task.status === 'completed').length);
|
|
36
|
+
|
|
37
|
+
function statusLabel(status: LiteTaskStatus): string {
|
|
38
|
+
switch (status) {
|
|
39
|
+
case 'running': return 'Running';
|
|
40
|
+
case 'pending': return 'Pending';
|
|
41
|
+
case 'completed': return 'Completed';
|
|
42
|
+
case 'failed': return 'Failed';
|
|
43
|
+
case 'cancelled': return 'Cancelled';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function boundedProgress(task: LiteBackgroundTask): number {
|
|
48
|
+
if (typeof task.progress === 'number') return Math.max(0, Math.min(100, task.progress));
|
|
49
|
+
if (task.total && task.total > 0 && typeof task.processed === 'number') {
|
|
50
|
+
return Math.max(0, Math.min(100, Math.round((task.processed / task.total) * 100)));
|
|
51
|
+
}
|
|
52
|
+
return task.status === 'completed' ? 100 : 0;
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<section class="lite-task-center lite-card {className}" aria-label={title}>
|
|
57
|
+
<header>
|
|
58
|
+
<div>
|
|
59
|
+
<h2>{title}</h2>
|
|
60
|
+
<p>{tasks.length} task{tasks.length === 1 ? '' : 's'}{completedCount ? ` · ${completedCount} completed` : ''}</p>
|
|
61
|
+
</div>
|
|
62
|
+
{#if clearAction && completedCount > 0}
|
|
63
|
+
<form method="POST" action={clearAction}>
|
|
64
|
+
<button type="submit" class="lite-task-secondary">{clearLabel}</button>
|
|
65
|
+
</form>
|
|
66
|
+
{/if}
|
|
67
|
+
</header>
|
|
68
|
+
|
|
69
|
+
{#if tasks.length === 0}
|
|
70
|
+
<p class="lite-task-empty" role="status">{emptyText}</p>
|
|
71
|
+
{:else}
|
|
72
|
+
<table class="lite-table">
|
|
73
|
+
<thead>
|
|
74
|
+
<tr>
|
|
75
|
+
<th>Task</th>
|
|
76
|
+
<th>Status</th>
|
|
77
|
+
<th>Progress</th>
|
|
78
|
+
<th>Actions</th>
|
|
79
|
+
</tr>
|
|
80
|
+
</thead>
|
|
81
|
+
<tbody>
|
|
82
|
+
{#each tasks as task (task.id)}
|
|
83
|
+
<tr data-task-id={task.id} data-status={task.status}>
|
|
84
|
+
<td>
|
|
85
|
+
<strong>{task.title}</strong>
|
|
86
|
+
{#if task.error}<span class="lite-task-error">{task.error}</span>{/if}
|
|
87
|
+
</td>
|
|
88
|
+
<td><span class="lite-task-status" data-status={task.status}>{statusLabel(task.status)}</span></td>
|
|
89
|
+
<td>
|
|
90
|
+
<progress max="100" value={boundedProgress(task)} aria-label={`${task.title} progress`}></progress>
|
|
91
|
+
<span class="lite-task-progress-text">
|
|
92
|
+
{boundedProgress(task)}%{typeof task.processed === 'number' && typeof task.total === 'number'
|
|
93
|
+
? ` · ${task.processed} / ${task.total}`
|
|
94
|
+
: ''}
|
|
95
|
+
</span>
|
|
96
|
+
</td>
|
|
97
|
+
<td class="lite-task-actions">
|
|
98
|
+
{#if task.downloadHref}<a href={task.downloadHref}>Download</a>{/if}
|
|
99
|
+
{#if task.retryAction}
|
|
100
|
+
<form method="POST" action={task.retryAction}><button type="submit" class="lite-task-link">Retry</button></form>
|
|
101
|
+
{/if}
|
|
102
|
+
{#if task.cancelAction && (task.status === 'running' || task.status === 'pending')}
|
|
103
|
+
<form method="POST" action={task.cancelAction}><button type="submit" class="lite-task-link">Cancel</button></form>
|
|
104
|
+
{/if}
|
|
105
|
+
</td>
|
|
106
|
+
</tr>
|
|
107
|
+
{/each}
|
|
108
|
+
</tbody>
|
|
109
|
+
</table>
|
|
110
|
+
{/if}
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
<style>
|
|
114
|
+
.lite-task-center { padding: 16px; }
|
|
115
|
+
.lite-task-center header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 12px; }
|
|
116
|
+
.lite-task-center header > form { margin-left: 12px; }
|
|
117
|
+
.lite-task-center header h2 { margin: 0; color: #1d2129; font-size: 16px; }
|
|
118
|
+
.lite-task-center header p { margin: 4px 0 0; color: #4e5969; font-size: 12px; }
|
|
119
|
+
.lite-task-empty { margin: 0; padding: 20px; color: #86909c; text-align: center; }
|
|
120
|
+
.lite-task-error { display: block; margin-top: 3px; color: #f53f3f; font-size: 11px; }
|
|
121
|
+
.lite-task-status { display: inline-block; padding: 2px 6px; border-radius: 999px; background: #f2f3f5; color: #4e5969; font-size: 11px; }
|
|
122
|
+
.lite-task-status[data-status='running'] { background: #e8f3ff; color: #165dff; }
|
|
123
|
+
.lite-task-status[data-status='pending'] { background: #fff7e8; color: #d25f00; }
|
|
124
|
+
.lite-task-status[data-status='completed'] { background: #e8ffea; color: #00b42a; }
|
|
125
|
+
.lite-task-status[data-status='failed'] { background: #ffece8; color: #f53f3f; }
|
|
126
|
+
.lite-task-status[data-status='cancelled'] { background: #f2f3f5; color: #86909c; }
|
|
127
|
+
.lite-task-center progress { width: 100%; height: 6px; }
|
|
128
|
+
.lite-task-progress-text { display: block; margin-top: 3px; color: #86909c; font-size: 11px; }
|
|
129
|
+
.lite-task-actions { white-space: nowrap; }
|
|
130
|
+
.lite-task-actions form { display: inline; }
|
|
131
|
+
.lite-task-link { padding: 0; border: 0; background: none; color: #165dff; font: inherit; font-size: 12px; cursor: pointer; }
|
|
132
|
+
.lite-task-secondary { padding: 5px 9px; border: 1px solid #e5e6eb; border-radius: 4px; background: #fff; color: #4e5969; font-size: 11px; cursor: pointer; }
|
|
133
|
+
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type LiteTaskStatus = 'running' | 'pending' | 'completed' | 'failed' | 'cancelled';
|
|
2
|
+
export interface LiteBackgroundTask {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
status: LiteTaskStatus;
|
|
6
|
+
progress?: number;
|
|
7
|
+
processed?: number;
|
|
8
|
+
total?: number;
|
|
9
|
+
error?: string;
|
|
10
|
+
downloadHref?: string;
|
|
11
|
+
cancelAction?: string;
|
|
12
|
+
retryAction?: string;
|
|
13
|
+
}
|
|
14
|
+
interface Props {
|
|
15
|
+
tasks?: LiteBackgroundTask[];
|
|
16
|
+
title?: string;
|
|
17
|
+
emptyText?: string;
|
|
18
|
+
clearAction?: string;
|
|
19
|
+
clearLabel?: string;
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
declare const LiteTaskCenter: import("svelte").Component<Props, {}, "">;
|
|
23
|
+
type LiteTaskCenter = ReturnType<typeof LiteTaskCenter>;
|
|
24
|
+
export default LiteTaskCenter;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
oldText?: string;
|
|
4
|
+
newText?: string;
|
|
5
|
+
oldTitle?: string;
|
|
6
|
+
newTitle?: string;
|
|
7
|
+
class?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
oldText = '',
|
|
12
|
+
newText = '',
|
|
13
|
+
oldTitle = 'Baseline',
|
|
14
|
+
newTitle = 'Current',
|
|
15
|
+
class: className = '',
|
|
16
|
+
}: Props = $props();
|
|
17
|
+
|
|
18
|
+
interface DiffEntry {
|
|
19
|
+
type: 'same' | 'add' | 'del';
|
|
20
|
+
lineNo: number;
|
|
21
|
+
text: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const diffEntries = $derived.by<DiffEntry[]>(() => {
|
|
25
|
+
const oldLines = oldText.split(/\r?\n/u);
|
|
26
|
+
const newLines = newText.split(/\r?\n/u);
|
|
27
|
+
const result: DiffEntry[] = [];
|
|
28
|
+
|
|
29
|
+
let lineCounter = 1;
|
|
30
|
+
let i = 0;
|
|
31
|
+
let j = 0;
|
|
32
|
+
|
|
33
|
+
while (i < oldLines.length && j < newLines.length) {
|
|
34
|
+
if (oldLines[i] === newLines[j]) {
|
|
35
|
+
result.push({ type: 'same', lineNo: lineCounter++, text: oldLines[i] ?? '' });
|
|
36
|
+
i++;
|
|
37
|
+
j++;
|
|
38
|
+
} else {
|
|
39
|
+
result.push({ type: 'del', lineNo: lineCounter++, text: oldLines[i] ?? '' });
|
|
40
|
+
result.push({ type: 'add', lineNo: lineCounter++, text: newLines[j] ?? '' });
|
|
41
|
+
i++;
|
|
42
|
+
j++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
while (i < oldLines.length) {
|
|
46
|
+
result.push({ type: 'del', lineNo: lineCounter++, text: oldLines[i] ?? '' });
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
while (j < newLines.length) {
|
|
50
|
+
result.push({ type: 'add', lineNo: lineCounter++, text: newLines[j] ?? '' });
|
|
51
|
+
j++;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
});
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<div
|
|
59
|
+
class="lite-text-diff {className}"
|
|
60
|
+
data-svadmin-component="LiteTextDiffViewer"
|
|
61
|
+
role="region"
|
|
62
|
+
aria-label="Text Diff"
|
|
63
|
+
>
|
|
64
|
+
<header class="lite-diff-header">
|
|
65
|
+
<span class="lite-diff-title">{oldTitle} ↔ {newTitle}</span>
|
|
66
|
+
</header>
|
|
67
|
+
|
|
68
|
+
<div class="lite-diff-body">
|
|
69
|
+
<table class="lite-diff-table">
|
|
70
|
+
<tbody>
|
|
71
|
+
{#each diffEntries as entry (entry.lineNo)}
|
|
72
|
+
<tr class="lite-diff-row lite-diff-{entry.type}">
|
|
73
|
+
<td class="lite-diff-line-no">{entry.lineNo}</td>
|
|
74
|
+
<td class="lite-diff-sign">
|
|
75
|
+
{#if entry.type === 'add'}+{/if}
|
|
76
|
+
{#if entry.type === 'del'}-{/if}
|
|
77
|
+
</td>
|
|
78
|
+
<td class="lite-diff-content">{entry.text}</td>
|
|
79
|
+
</tr>
|
|
80
|
+
{/each}
|
|
81
|
+
</tbody>
|
|
82
|
+
</table>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<style>
|
|
87
|
+
.lite-text-diff {
|
|
88
|
+
border: 1px solid #e5e6eb;
|
|
89
|
+
border-radius: 6px;
|
|
90
|
+
background: #ffffff;
|
|
91
|
+
font-family: monospace;
|
|
92
|
+
font-size: 12px;
|
|
93
|
+
overflow: hidden;
|
|
94
|
+
}
|
|
95
|
+
.lite-diff-header {
|
|
96
|
+
padding: 8px 12px;
|
|
97
|
+
background: #f7f8fa;
|
|
98
|
+
border-bottom: 1px solid #e5e6eb;
|
|
99
|
+
font-weight: 600;
|
|
100
|
+
color: #4e5969;
|
|
101
|
+
}
|
|
102
|
+
.lite-diff-body { max-height: 400px; overflow: auto; }
|
|
103
|
+
.lite-diff-table { width: 100%; border-collapse: collapse; }
|
|
104
|
+
.lite-diff-row td { padding: 2px 6px; }
|
|
105
|
+
.lite-diff-line-no { width: 40px; text-align: right; color: #86909c; user-select: none; border-right: 1px solid #f2f3f5; }
|
|
106
|
+
.lite-diff-sign { width: 20px; text-align: center; font-weight: 700; user-select: none; }
|
|
107
|
+
.lite-diff-content { white-space: pre-wrap; word-break: break-all; }
|
|
108
|
+
.lite-diff-add { background: #e8ffea; color: #00b42a; }
|
|
109
|
+
.lite-diff-del { background: #ffece8; color: #f53f3f; }
|
|
110
|
+
.lite-diff-same { color: #1d2129; }
|
|
111
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
oldText?: string;
|
|
3
|
+
newText?: string;
|
|
4
|
+
oldTitle?: string;
|
|
5
|
+
newTitle?: string;
|
|
6
|
+
class?: string;
|
|
7
|
+
}
|
|
8
|
+
declare const LiteTextDiffViewer: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type LiteTextDiffViewer = ReturnType<typeof LiteTextDiffViewer>;
|
|
10
|
+
export default LiteTextDiffViewer;
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
title: string;
|
|
4
4
|
description?: string;
|
|
5
5
|
href?: string;
|
|
6
|
+
/** SPA target selector; ignored by the server-rendered Lite fallback. */
|
|
7
|
+
target?: string;
|
|
8
|
+
/** SPA placement; ignored by the server-rendered Lite fallback. */
|
|
9
|
+
placement?: 'top' | 'bottom' | 'left' | 'right';
|
|
6
10
|
}
|
|
7
11
|
|
|
8
12
|
interface Props {
|
|
@@ -2,6 +2,10 @@ interface TourStep {
|
|
|
2
2
|
title: string;
|
|
3
3
|
description?: string;
|
|
4
4
|
href?: string;
|
|
5
|
+
/** SPA target selector; ignored by the server-rendered Lite fallback. */
|
|
6
|
+
target?: string;
|
|
7
|
+
/** SPA placement; ignored by the server-rendered Lite fallback. */
|
|
8
|
+
placement?: 'top' | 'bottom' | 'left' | 'right';
|
|
5
9
|
}
|
|
6
10
|
interface Props {
|
|
7
11
|
steps: TourStep[];
|