runeforge 0.0.61 → 0.0.63
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/LICENSE +21 -21
- package/README.md +1410 -1407
- package/dist/components/Avatar.svelte +31 -31
- package/dist/components/IconRenderer.svelte +73 -73
- package/dist/components/Modal.svelte +75 -75
- package/dist/components/common/Header.svelte +40 -40
- package/dist/components/crud/EmbeddedField.svelte +203 -203
- package/dist/components/crud/Field.svelte +122 -122
- package/dist/components/crud/GenericCRUD.svelte +426 -426
- package/dist/components/crud/SearchInput.svelte +53 -53
- package/dist/components/crud/columns/Avatar.svelte +15 -15
- package/dist/components/crud/columns/Icon.svelte +8 -8
- package/dist/components/crud/fields/BooleanField.svelte +38 -38
- package/dist/components/crud/fields/DatetimeField.svelte +250 -250
- package/dist/components/crud/fields/DefaultField.svelte +67 -67
- package/dist/components/crud/fields/FileField.svelte +70 -70
- package/dist/components/crud/fields/MultiSelectField.svelte +70 -70
- package/dist/components/crud/fields/SelectField.svelte +67 -67
- package/dist/components/crud/fields/TextareaField.svelte +55 -55
- package/dist/components/crud/fields/TreeField.svelte +1 -0
- package/dist/components/crud/utils/resolution.js +1 -0
- package/dist/components/crud/utils/tree.d.ts +5 -0
- package/dist/components/crud/utils/tree.js +29 -0
- package/dist/components/crud/views/Create.svelte +239 -239
- package/dist/components/crud/views/Read.svelte +131 -131
- package/dist/components/crud/views/Update.svelte +215 -215
- package/dist/components/crud/views/list/List.svelte +291 -291
- package/dist/components/crud/views/list/Modals.svelte +56 -56
- package/dist/components/crud/views/list/Table.svelte +173 -173
- package/dist/components/crud/views/list/Toolbar.svelte +340 -340
- package/dist/components/form/Button.svelte +27 -27
- package/dist/components/form/Label.svelte +37 -37
- package/dist/components/form/MultiSelect.svelte +248 -248
- package/dist/components/form/PasswordInput.svelte +68 -68
- package/dist/components/form/Required.svelte +1 -1
- package/dist/components/form/Select.svelte +209 -209
- package/dist/components/form/Tree.svelte +43 -4
- package/dist/components/form/Tree.svelte.d.ts +1 -0
- package/dist/components/form/TreeNode.svelte +31 -2
- package/dist/components/form/TreeNode.svelte.d.ts +2 -0
- package/dist/components/navigation/Breadcrumbs.svelte +111 -111
- package/dist/components/table/ColumnFilter.svelte +168 -168
- package/dist/components/table/PaginatedTable.svelte +542 -542
- package/dist/components/table/Paginator.svelte +113 -113
- package/dist/components/table/SortHeader.svelte +43 -43
- package/dist/components/table/TableBody.svelte +150 -150
- package/dist/components/table/TableHeader.svelte +88 -88
- package/dist/components/table/state.svelte.js +7 -2
- package/dist/icons/defaults/Clear.svelte +6 -6
- package/dist/icons/defaults/Create.svelte +6 -6
- package/dist/icons/defaults/Delete.svelte +6 -6
- package/dist/icons/defaults/Download.svelte +7 -7
- package/dist/icons/defaults/Edit.svelte +7 -7
- package/dist/icons/defaults/Filter.svelte +6 -6
- package/dist/icons/defaults/FilterActive.svelte +6 -6
- package/dist/icons/defaults/Folder.svelte +6 -6
- package/dist/icons/defaults/Grip.svelte +7 -7
- package/dist/icons/defaults/Home.svelte +6 -6
- package/dist/icons/defaults/PasswordHide.svelte +9 -9
- package/dist/icons/defaults/PasswordShow.svelte +7 -7
- package/dist/icons/defaults/SortAsc.svelte +6 -6
- package/dist/icons/defaults/SortDesc.svelte +6 -6
- package/dist/icons/defaults/SortNone.svelte +6 -6
- package/dist/icons/defaults/View.svelte +7 -7
- package/dist/types/attribute.d.ts +3 -0
- package/dist/types/crud/fields.d.ts +3 -0
- package/package.json +1 -1
|
@@ -1,131 +1,131 @@
|
|
|
1
|
-
<script lang="ts" generics="T extends object = Record<string, unknown>">
|
|
2
|
-
import { untrack } from 'svelte';
|
|
3
|
-
import { deserialize } from '$app/forms';
|
|
4
|
-
import Field from '../Field.svelte';
|
|
5
|
-
import Button from '../../form/Button.svelte';
|
|
6
|
-
import Header from '../../common/Header.svelte';
|
|
7
|
-
import { getIconSet } from '../../../icons/context.js';
|
|
8
|
-
import { defaultIconSet } from '../../../icons/sets/default.js';
|
|
9
|
-
import { groupFields } from '../utils/grouping.js';
|
|
10
|
-
import type { ActionConfiguration } from '../../../types/crud/actions.js';
|
|
11
|
-
import type { FieldDefinition } from '../../../types/crud/fields.js';
|
|
12
|
-
import { getStrings } from '../../../i18n/context.js';
|
|
13
|
-
|
|
14
|
-
const strings = getStrings();
|
|
15
|
-
|
|
16
|
-
let {
|
|
17
|
-
labelOne = '',
|
|
18
|
-
labelMany = '',
|
|
19
|
-
icon,
|
|
20
|
-
idKey = '_id',
|
|
21
|
-
fields = [] as FieldDefinition<T>[],
|
|
22
|
-
instance = {} as T,
|
|
23
|
-
read = {} as ActionConfiguration<T>,
|
|
24
|
-
onCancel,
|
|
25
|
-
}: {
|
|
26
|
-
labelOne?: string;
|
|
27
|
-
labelMany?: string;
|
|
28
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
-
icon?: any;
|
|
30
|
-
idKey?: string;
|
|
31
|
-
fields?: FieldDefinition<T>[];
|
|
32
|
-
instance?: T;
|
|
33
|
-
read?: ActionConfiguration<T>;
|
|
34
|
-
onCancel?: () => void;
|
|
35
|
-
} = $props();
|
|
36
|
-
|
|
37
|
-
const icons = $derived(getIconSet() ?? defaultIconSet);
|
|
38
|
-
const entityIcon = $derived(icon ?? icons.folder);
|
|
39
|
-
|
|
40
|
-
let record = $state<Record<string, unknown>>(untrack(() => instance as Record<string, unknown>));
|
|
41
|
-
|
|
42
|
-
$effect(() => {
|
|
43
|
-
const endpoint = read.endpoint;
|
|
44
|
-
const id = (instance as Record<string, unknown>)[idKey];
|
|
45
|
-
if (!endpoint || id == null) return;
|
|
46
|
-
|
|
47
|
-
let cancelled = false;
|
|
48
|
-
(async () => {
|
|
49
|
-
const fd = new FormData();
|
|
50
|
-
fd.set('id', String(id));
|
|
51
|
-
const res = await fetch(endpoint, { method: 'POST', body: fd });
|
|
52
|
-
const result = deserialize(await res.text());
|
|
53
|
-
if (!cancelled && result.type === 'success') {
|
|
54
|
-
const data = result.data as Record<string, unknown> | undefined;
|
|
55
|
-
const fetched = data
|
|
56
|
-
? Object.values(data).find(
|
|
57
|
-
(v) => v !== null && typeof v === 'object' && !Array.isArray(v) && idKey in (v as object)
|
|
58
|
-
)
|
|
59
|
-
: undefined;
|
|
60
|
-
if (fetched && typeof fetched === 'object') record = fetched as Record<string, unknown>;
|
|
61
|
-
}
|
|
62
|
-
})();
|
|
63
|
-
return () => { cancelled = true; };
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const groups = $derived(groupFields(fields));
|
|
67
|
-
</script>
|
|
68
|
-
|
|
69
|
-
<div class="flex flex-col gap-6">
|
|
70
|
-
|
|
71
|
-
<div class="sticky-header sticky z-10 bg-base-100 pb-2">
|
|
72
|
-
<Header
|
|
73
|
-
title={labelMany}
|
|
74
|
-
breadcrumbs={[
|
|
75
|
-
{ label: labelMany, icon: entityIcon, link: { href: '#', onclick: (e) => { e.preventDefault(); onCancel?.(); } }, prominent: true },
|
|
76
|
-
{ label: read.label ?? labelOne, icon: icons.view },
|
|
77
|
-
]}
|
|
78
|
-
/>
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
<div class="fields-panel mx-auto flex w-full flex-col gap-4 px-4">
|
|
82
|
-
{#each groups as group, i (group.title ?? `_ungrouped_${i}`)}
|
|
83
|
-
{#if group.title}
|
|
84
|
-
<fieldset class="fieldset border border-base-300 rounded-box p-4">
|
|
85
|
-
<legend class="fieldset-legend px-2">{group.title}</legend>
|
|
86
|
-
<div class="flex flex-col gap-4">
|
|
87
|
-
{#each group.rows as row (row.map((f) => f.attribute).join('|'))}
|
|
88
|
-
{#if row.length > 1}
|
|
89
|
-
<div class="flex flex-col gap-4 md:flex-row">
|
|
90
|
-
{#each row as field (field.attribute)}
|
|
91
|
-
<Field {field} bind:record readonly class="md:min-w-0 md:flex-1" />
|
|
92
|
-
{/each}
|
|
93
|
-
</div>
|
|
94
|
-
{:else}
|
|
95
|
-
<Field field={row[0]} bind:record readonly />
|
|
96
|
-
{/if}
|
|
97
|
-
{/each}
|
|
98
|
-
</div>
|
|
99
|
-
</fieldset>
|
|
100
|
-
{:else}
|
|
101
|
-
{#each group.rows as row (row.map((f) => f.attribute).join('|'))}
|
|
102
|
-
{#if row.length > 1}
|
|
103
|
-
<div class="flex flex-col gap-4 md:flex-row">
|
|
104
|
-
{#each row as field (field.attribute)}
|
|
105
|
-
<Field {field} bind:record readonly class="md:min-w-0 md:flex-1" />
|
|
106
|
-
{/each}
|
|
107
|
-
</div>
|
|
108
|
-
{:else}
|
|
109
|
-
<Field field={row[0]} bind:record readonly />
|
|
110
|
-
{/if}
|
|
111
|
-
{/each}
|
|
112
|
-
{/if}
|
|
113
|
-
{/each}
|
|
114
|
-
|
|
115
|
-
<div class="flex flex-col-reverse gap-2 pt-2 sm:flex-row sm:justify-end">
|
|
116
|
-
<Button variant="ghost" onclick={() => onCancel?.()}>
|
|
117
|
-
{strings.back}
|
|
118
|
-
</Button>
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
</div>
|
|
122
|
-
|
|
123
|
-
<style>
|
|
124
|
-
.fields-panel {
|
|
125
|
-
max-width: var(--runeforge-form-max-width, 32rem);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.sticky-header {
|
|
129
|
-
top: var(--runeforge-sticky-header-top, 0);
|
|
130
|
-
}
|
|
131
|
-
</style>
|
|
1
|
+
<script lang="ts" generics="T extends object = Record<string, unknown>">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
3
|
+
import { deserialize } from '$app/forms';
|
|
4
|
+
import Field from '../Field.svelte';
|
|
5
|
+
import Button from '../../form/Button.svelte';
|
|
6
|
+
import Header from '../../common/Header.svelte';
|
|
7
|
+
import { getIconSet } from '../../../icons/context.js';
|
|
8
|
+
import { defaultIconSet } from '../../../icons/sets/default.js';
|
|
9
|
+
import { groupFields } from '../utils/grouping.js';
|
|
10
|
+
import type { ActionConfiguration } from '../../../types/crud/actions.js';
|
|
11
|
+
import type { FieldDefinition } from '../../../types/crud/fields.js';
|
|
12
|
+
import { getStrings } from '../../../i18n/context.js';
|
|
13
|
+
|
|
14
|
+
const strings = getStrings();
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
labelOne = '',
|
|
18
|
+
labelMany = '',
|
|
19
|
+
icon,
|
|
20
|
+
idKey = '_id',
|
|
21
|
+
fields = [] as FieldDefinition<T>[],
|
|
22
|
+
instance = {} as T,
|
|
23
|
+
read = {} as ActionConfiguration<T>,
|
|
24
|
+
onCancel,
|
|
25
|
+
}: {
|
|
26
|
+
labelOne?: string;
|
|
27
|
+
labelMany?: string;
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
icon?: any;
|
|
30
|
+
idKey?: string;
|
|
31
|
+
fields?: FieldDefinition<T>[];
|
|
32
|
+
instance?: T;
|
|
33
|
+
read?: ActionConfiguration<T>;
|
|
34
|
+
onCancel?: () => void;
|
|
35
|
+
} = $props();
|
|
36
|
+
|
|
37
|
+
const icons = $derived(getIconSet() ?? defaultIconSet);
|
|
38
|
+
const entityIcon = $derived(icon ?? icons.folder);
|
|
39
|
+
|
|
40
|
+
let record = $state<Record<string, unknown>>(untrack(() => instance as Record<string, unknown>));
|
|
41
|
+
|
|
42
|
+
$effect(() => {
|
|
43
|
+
const endpoint = read.endpoint;
|
|
44
|
+
const id = (instance as Record<string, unknown>)[idKey];
|
|
45
|
+
if (!endpoint || id == null) return;
|
|
46
|
+
|
|
47
|
+
let cancelled = false;
|
|
48
|
+
(async () => {
|
|
49
|
+
const fd = new FormData();
|
|
50
|
+
fd.set('id', String(id));
|
|
51
|
+
const res = await fetch(endpoint, { method: 'POST', body: fd });
|
|
52
|
+
const result = deserialize(await res.text());
|
|
53
|
+
if (!cancelled && result.type === 'success') {
|
|
54
|
+
const data = result.data as Record<string, unknown> | undefined;
|
|
55
|
+
const fetched = data
|
|
56
|
+
? Object.values(data).find(
|
|
57
|
+
(v) => v !== null && typeof v === 'object' && !Array.isArray(v) && idKey in (v as object)
|
|
58
|
+
)
|
|
59
|
+
: undefined;
|
|
60
|
+
if (fetched && typeof fetched === 'object') record = fetched as Record<string, unknown>;
|
|
61
|
+
}
|
|
62
|
+
})();
|
|
63
|
+
return () => { cancelled = true; };
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const groups = $derived(groupFields(fields));
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<div class="flex flex-col gap-6">
|
|
70
|
+
|
|
71
|
+
<div class="sticky-header sticky z-10 bg-base-100 pb-2">
|
|
72
|
+
<Header
|
|
73
|
+
title={labelMany}
|
|
74
|
+
breadcrumbs={[
|
|
75
|
+
{ label: labelMany, icon: entityIcon, link: { href: '#', onclick: (e) => { e.preventDefault(); onCancel?.(); } }, prominent: true },
|
|
76
|
+
{ label: read.label ?? labelOne, icon: icons.view },
|
|
77
|
+
]}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div class="fields-panel mx-auto flex w-full flex-col gap-4 px-4">
|
|
82
|
+
{#each groups as group, i (group.title ?? `_ungrouped_${i}`)}
|
|
83
|
+
{#if group.title}
|
|
84
|
+
<fieldset class="fieldset border border-base-300 rounded-box p-4">
|
|
85
|
+
<legend class="fieldset-legend px-2">{group.title}</legend>
|
|
86
|
+
<div class="flex flex-col gap-4">
|
|
87
|
+
{#each group.rows as row (row.map((f) => f.attribute).join('|'))}
|
|
88
|
+
{#if row.length > 1}
|
|
89
|
+
<div class="flex flex-col gap-4 md:flex-row">
|
|
90
|
+
{#each row as field (field.attribute)}
|
|
91
|
+
<Field {field} bind:record readonly class="md:min-w-0 md:flex-1" />
|
|
92
|
+
{/each}
|
|
93
|
+
</div>
|
|
94
|
+
{:else}
|
|
95
|
+
<Field field={row[0]} bind:record readonly />
|
|
96
|
+
{/if}
|
|
97
|
+
{/each}
|
|
98
|
+
</div>
|
|
99
|
+
</fieldset>
|
|
100
|
+
{:else}
|
|
101
|
+
{#each group.rows as row (row.map((f) => f.attribute).join('|'))}
|
|
102
|
+
{#if row.length > 1}
|
|
103
|
+
<div class="flex flex-col gap-4 md:flex-row">
|
|
104
|
+
{#each row as field (field.attribute)}
|
|
105
|
+
<Field {field} bind:record readonly class="md:min-w-0 md:flex-1" />
|
|
106
|
+
{/each}
|
|
107
|
+
</div>
|
|
108
|
+
{:else}
|
|
109
|
+
<Field field={row[0]} bind:record readonly />
|
|
110
|
+
{/if}
|
|
111
|
+
{/each}
|
|
112
|
+
{/if}
|
|
113
|
+
{/each}
|
|
114
|
+
|
|
115
|
+
<div class="flex flex-col-reverse gap-2 pt-2 sm:flex-row sm:justify-end">
|
|
116
|
+
<Button variant="ghost" onclick={() => onCancel?.()}>
|
|
117
|
+
{strings.back}
|
|
118
|
+
</Button>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<style>
|
|
124
|
+
.fields-panel {
|
|
125
|
+
max-width: var(--runeforge-form-max-width, 32rem);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.sticky-header {
|
|
129
|
+
top: var(--runeforge-sticky-header-top, 0);
|
|
130
|
+
}
|
|
131
|
+
</style>
|