@lobb-js/studio 0.43.0 → 0.44.1
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/components/dataTable/dataTable.svelte +7 -1
- package/dist/components/dataTable/filter.svelte +30 -22
- package/dist/components/dataTable/filterButton.svelte +1 -0
- package/dist/components/dataTable/header.svelte +17 -13
- package/dist/components/dataTable/header.svelte.d.ts +1 -0
- package/dist/components/dataTable/table.svelte +17 -9
- package/dist/components/dataTable/table.svelte.d.ts +1 -0
- package/dist/components/foreingKeyInput.svelte +26 -149
- package/dist/components/polymorphicInput.svelte +19 -92
- package/package.json +2 -2
- package/src/app.css +2 -2
- package/src/lib/components/dataTable/dataTable.svelte +7 -1
- package/src/lib/components/dataTable/filter.svelte +30 -22
- package/src/lib/components/dataTable/filterButton.svelte +1 -0
- package/src/lib/components/dataTable/header.svelte +17 -13
- package/src/lib/components/dataTable/table.svelte +17 -9
- package/src/lib/components/foreingKeyInput.svelte +26 -149
- package/src/lib/components/polymorphicInput.svelte +19 -92
|
@@ -3,14 +3,9 @@
|
|
|
3
3
|
import Input from "./ui/input/input.svelte";
|
|
4
4
|
import SelectRecord from "./selectRecord.svelte";
|
|
5
5
|
import CreateDetailView from "./detailView/create/createDetailView.svelte";
|
|
6
|
-
import
|
|
7
|
-
import { getCollectionPrimaryField } from "./dataTable/utils";
|
|
8
|
-
import { getStudioContext } from "../context";
|
|
9
|
-
import { Plus, Link, Pencil, Unlink, Trash, RotateCcw, RefreshCw } from "lucide-svelte";
|
|
6
|
+
import { Plus } from "lucide-svelte";
|
|
10
7
|
import Button from "./ui/button/button.svelte";
|
|
11
8
|
|
|
12
|
-
const { lobb, ctx } = getStudioContext();
|
|
13
|
-
|
|
14
9
|
interface LocalProps {
|
|
15
10
|
parentCollectionName: string;
|
|
16
11
|
collectionName: string;
|
|
@@ -30,45 +25,23 @@
|
|
|
30
25
|
}: LocalProps = $props();
|
|
31
26
|
|
|
32
27
|
let createDrawerOpen = $state(false);
|
|
33
|
-
let editDrawerOpen = $state(false);
|
|
34
|
-
let editValues: Record<string, any> | undefined = $state(undefined);
|
|
35
|
-
let originalId = $state<number | null>(null);
|
|
36
28
|
let initialValue = $state<any>(undefined);
|
|
37
|
-
let unlinked = $state(false);
|
|
38
29
|
|
|
39
30
|
onMount(() => { initialValue = value; });
|
|
40
31
|
|
|
41
|
-
// Derived state from value
|
|
42
32
|
const isPendingCreate = $derived(value && typeof value === 'object' && value.create);
|
|
43
|
-
const isPendingEdit = $derived(value && typeof value === 'object' && value.id && value.update);
|
|
44
|
-
const isStagedDelete = $derived(value && typeof value === 'object' && value.delete === true);
|
|
45
|
-
const isStagedUnlink = $derived(unlinked);
|
|
46
33
|
const hasRealId = $derived(value != null && typeof value === 'number');
|
|
47
|
-
const
|
|
48
|
-
const isReplaced = $derived(hasRealId && initialValue !== undefined && initialValue != null && value !== initialValue);
|
|
49
|
-
const isEmpty = $derived((value == null || value === 0) && !unlinked);
|
|
34
|
+
const isEmpty = $derived(value == null || value === 0);
|
|
50
35
|
const isZeroPlaceholder = $derived(value === 0);
|
|
36
|
+
const isChanged = $derived(initialValue !== undefined && value !== initialValue && !isPendingCreate);
|
|
51
37
|
|
|
52
38
|
const bgClass = $derived(
|
|
53
|
-
isPendingCreate ? '!bg-green-500/5 border-green-500/40'
|
|
54
|
-
|
|
55
|
-
isReplaced ? '!bg-orange-500/5 border-orange-500/40' :
|
|
56
|
-
isPendingEdit ? '!bg-orange-500/5 border-orange-500/40' :
|
|
57
|
-
isStagedUnlink ? '!bg-slate-500/5 border-slate-500/40' :
|
|
58
|
-
isStagedDelete ? '!bg-red-500/5 border-red-500/40' :
|
|
39
|
+
isPendingCreate ? '!bg-green-500/5 border-green-500/40' :
|
|
40
|
+
isChanged ? '!bg-orange-500/5' :
|
|
59
41
|
''
|
|
60
42
|
);
|
|
61
43
|
|
|
62
|
-
const displayId = $derived(
|
|
63
|
-
isPendingEdit ? (value as any).id :
|
|
64
|
-
isStagedUnlink ? originalId :
|
|
65
|
-
isStagedDelete ? originalId :
|
|
66
|
-
hasRealId ? value :
|
|
67
|
-
null
|
|
68
|
-
);
|
|
69
|
-
|
|
70
44
|
async function handleCreated(record: any) {
|
|
71
|
-
// dry-run result has no id — store as pending create
|
|
72
45
|
if (!record.id) {
|
|
73
46
|
value = { create: record };
|
|
74
47
|
} else {
|
|
@@ -76,127 +49,41 @@
|
|
|
76
49
|
}
|
|
77
50
|
}
|
|
78
51
|
|
|
79
|
-
async function openEdit() {
|
|
80
|
-
const res = await lobb.findAll(collectionName, { filter: { id: value }, limit: 1 });
|
|
81
|
-
const result = await res.json();
|
|
82
|
-
editValues = result.data[0];
|
|
83
|
-
editDrawerOpen = true;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function handleEditChanges(changes: import('./detailView/utils').Changes) {
|
|
87
|
-
if (Object.keys(changes.data).length === 0) {
|
|
88
|
-
value = (editValues as any)?.id ?? value;
|
|
89
|
-
} else {
|
|
90
|
-
value = { id: (editValues as any)?.id ?? value, update: changes.data };
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
52
|
function handleSelect(selectedEntry: any) {
|
|
95
53
|
value = selectedEntry.id;
|
|
96
54
|
}
|
|
97
|
-
|
|
98
|
-
function handleUnlink() {
|
|
99
|
-
if (hasRealId) {
|
|
100
|
-
originalId = value as number;
|
|
101
|
-
unlinked = true;
|
|
102
|
-
value = null;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function handleDelete() {
|
|
107
|
-
if (hasRealId) {
|
|
108
|
-
originalId = value as number;
|
|
109
|
-
value = { delete: true };
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function handleRevert() {
|
|
114
|
-
if (originalId != null) {
|
|
115
|
-
value = originalId;
|
|
116
|
-
originalId = null;
|
|
117
|
-
} else {
|
|
118
|
-
value = null;
|
|
119
|
-
}
|
|
120
|
-
unlinked = false;
|
|
121
|
-
}
|
|
122
55
|
</script>
|
|
123
56
|
|
|
124
57
|
{#if !isZeroPlaceholder}
|
|
125
58
|
<div class="relative">
|
|
126
|
-
<!-- Action buttons overlay on the right -->
|
|
127
59
|
<div class="flex gap-1 absolute right-0 top-0 mr-9 h-full items-center text-xs">
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
variant="ghost"
|
|
147
|
-
Icon={Unlink}
|
|
148
|
-
onclick={handleUnlink}
|
|
149
|
-
title="Unlink"
|
|
150
|
-
></Button>
|
|
151
|
-
<Button
|
|
152
|
-
class="h-5 w-5 px-0 py-0 hover:bg-transparent text-muted-foreground"
|
|
153
|
-
variant="ghost"
|
|
154
|
-
Icon={Pencil}
|
|
155
|
-
onclick={openEdit}
|
|
156
|
-
title="Edit record"
|
|
157
|
-
></Button>
|
|
158
|
-
<SelectRecord
|
|
159
|
-
class="h-5 w-5 px-0 py-0 hover:bg-transparent text-muted-foreground"
|
|
160
|
-
variant="ghost"
|
|
161
|
-
{collectionName}
|
|
162
|
-
onSelect={handleSelect}
|
|
163
|
-
additionalFilter={{ id: { $ne: value } }}
|
|
164
|
-
title="Replace"
|
|
165
|
-
{entry}
|
|
166
|
-
>
|
|
167
|
-
{#snippet children()}<RefreshCw size="13" />{/snippet}
|
|
168
|
-
</SelectRecord>
|
|
169
|
-
{:else if isEmpty}
|
|
170
|
-
<Button
|
|
171
|
-
class="h-6 px-2 font-normal text-xs"
|
|
172
|
-
variant="outline"
|
|
173
|
-
Icon={Plus}
|
|
174
|
-
onclick={() => (createDrawerOpen = true)}
|
|
175
|
-
>
|
|
176
|
-
Create
|
|
177
|
-
</Button>
|
|
178
|
-
<SelectRecord
|
|
179
|
-
class="h-6 px-2 font-normal text-xs"
|
|
180
|
-
variant="outline"
|
|
181
|
-
{parentCollectionName}
|
|
182
|
-
{collectionName}
|
|
183
|
-
{fieldName}
|
|
184
|
-
onSelect={handleSelect}
|
|
185
|
-
text="Link"
|
|
186
|
-
{entry}
|
|
187
|
-
/>
|
|
188
|
-
{/if}
|
|
60
|
+
<Button
|
|
61
|
+
class="h-6 px-2 font-normal text-xs"
|
|
62
|
+
variant="outline"
|
|
63
|
+
Icon={Plus}
|
|
64
|
+
onclick={() => (createDrawerOpen = true)}
|
|
65
|
+
>
|
|
66
|
+
Create
|
|
67
|
+
</Button>
|
|
68
|
+
<SelectRecord
|
|
69
|
+
class="h-6 px-2 font-normal text-xs"
|
|
70
|
+
variant="outline"
|
|
71
|
+
{parentCollectionName}
|
|
72
|
+
{collectionName}
|
|
73
|
+
{fieldName}
|
|
74
|
+
onSelect={handleSelect}
|
|
75
|
+
text="Link"
|
|
76
|
+
{entry}
|
|
77
|
+
/>
|
|
189
78
|
</div>
|
|
190
79
|
|
|
191
|
-
<!-- Input field colored by state -->
|
|
192
80
|
<Input
|
|
193
|
-
placeholder={isEmpty ? "NULL" : ""}
|
|
81
|
+
placeholder={isPendingCreate ? "AUTO GENERATED" : isEmpty ? "NULL" : ""}
|
|
194
82
|
type="number"
|
|
195
83
|
class="bg-muted text-xs {bgClass} {destructive ? '!bg-destructive/10 border-destructive' : ''}"
|
|
196
|
-
disabled={isPendingCreate || isPendingEdit}
|
|
197
84
|
bind:value={
|
|
198
|
-
() =>
|
|
199
|
-
(v) => {
|
|
85
|
+
() => hasRealId ? value : "",
|
|
86
|
+
(v) => { value = (v === "" || v == null) ? null : Number(v); }
|
|
200
87
|
}
|
|
201
88
|
/>
|
|
202
89
|
</div>
|
|
@@ -214,13 +101,3 @@
|
|
|
214
101
|
onCancel={async () => { createDrawerOpen = false; }}
|
|
215
102
|
/>
|
|
216
103
|
{/if}
|
|
217
|
-
|
|
218
|
-
{#if editDrawerOpen && editValues}
|
|
219
|
-
<UpdateDetailView
|
|
220
|
-
collectionName={collectionName}
|
|
221
|
-
recordId={String(editValues.id)}
|
|
222
|
-
values={editValues}
|
|
223
|
-
onChanges={handleEditChanges}
|
|
224
|
-
onCancel={async () => { editDrawerOpen = false; editValues = undefined; }}
|
|
225
|
-
/>
|
|
226
|
-
{/if}
|
|
@@ -4,12 +4,8 @@
|
|
|
4
4
|
import DataTable from "./dataTable/dataTable.svelte";
|
|
5
5
|
import Drawer from "./drawer.svelte";
|
|
6
6
|
import * as Popover from "./ui/popover/index";
|
|
7
|
-
import {
|
|
8
|
-
import { ArrowLeft, Link, ChevronDown, Plus, Pencil, Unlink, Trash, RotateCcw, RefreshCw } from "lucide-svelte";
|
|
7
|
+
import { ArrowLeft, Link, ChevronDown, Plus } from "lucide-svelte";
|
|
9
8
|
import CreateDetailView from "./detailView/create/createDetailView.svelte";
|
|
10
|
-
import UpdateDetailView from "./detailView/update/updateDetailView.svelte";
|
|
11
|
-
|
|
12
|
-
const { ctx, lobb } = getStudioContext();
|
|
13
9
|
|
|
14
10
|
interface Props {
|
|
15
11
|
collectionField: string;
|
|
@@ -29,41 +25,33 @@
|
|
|
29
25
|
destructive,
|
|
30
26
|
}: Props = $props();
|
|
31
27
|
|
|
32
|
-
|
|
33
28
|
const selectedCollection = $derived(entry[collectionField] ?? null);
|
|
34
29
|
const selectedId = $derived(entry[idField] ?? null);
|
|
35
30
|
const virtualVal = $derived(entry[virtualField] ?? null);
|
|
36
31
|
|
|
37
32
|
let initialId = $state<number | null | undefined>(undefined);
|
|
38
|
-
let
|
|
39
|
-
onMount(() => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
33
|
+
let initialCollection = $state<string | null | undefined>(undefined);
|
|
34
|
+
onMount(() => {
|
|
35
|
+
initialId = entry[idField] ?? null;
|
|
36
|
+
initialCollection = entry[collectionField] ?? null;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const isPendingCreate = $derived(virtualVal && typeof virtualVal === 'object' && virtualVal.create);
|
|
40
|
+
const isChanged = $derived(
|
|
41
|
+
initialId !== undefined &&
|
|
42
|
+
!isPendingCreate &&
|
|
43
|
+
(selectedId !== initialId || selectedCollection !== initialCollection)
|
|
44
|
+
);
|
|
50
45
|
|
|
51
46
|
const bgClass = $derived(
|
|
52
|
-
isPendingCreate ? '!bg-green-500/5 border-green-500/40'
|
|
53
|
-
|
|
54
|
-
isReplaced ? '!bg-orange-500/5 border-orange-500/40' :
|
|
55
|
-
isPendingEdit ? '!bg-orange-500/5 border-orange-500/40' :
|
|
56
|
-
isStagedUnlink ? '!bg-slate-500/5 border-slate-500/40' :
|
|
57
|
-
isStagedDelete ? '!bg-red-500/5 border-red-500/40' :
|
|
47
|
+
isPendingCreate ? '!bg-green-500/5 border-green-500/40' :
|
|
48
|
+
isChanged ? '!bg-orange-500/5' :
|
|
58
49
|
''
|
|
59
50
|
);
|
|
60
51
|
|
|
61
52
|
let collectionPopoverOpen = $state(false);
|
|
62
53
|
let recordDrawerOpen = $state(false);
|
|
63
54
|
let createDrawerOpen = $state(false);
|
|
64
|
-
let editDrawerOpen = $state(false);
|
|
65
|
-
let editValues: Record<string, any> | undefined = $state(undefined);
|
|
66
|
-
let originalSnapshot: { collection: string; id: number } | null = $state(null);
|
|
67
55
|
|
|
68
56
|
function onCollectionChange(col: string) {
|
|
69
57
|
collectionPopoverOpen = false;
|
|
@@ -75,7 +63,7 @@
|
|
|
75
63
|
function onIdChange(e: Event) {
|
|
76
64
|
const raw = (e.target as HTMLInputElement).value;
|
|
77
65
|
const id = raw === "" ? null : Number(raw);
|
|
78
|
-
entry = { ...entry, [idField]: id };
|
|
66
|
+
entry = { ...entry, [idField]: id, [virtualField]: undefined };
|
|
79
67
|
}
|
|
80
68
|
|
|
81
69
|
function onRecordSelect(record: any) {
|
|
@@ -90,50 +78,9 @@
|
|
|
90
78
|
entry = { ...entry, [idField]: record.id, [virtualField]: undefined };
|
|
91
79
|
}
|
|
92
80
|
}
|
|
93
|
-
|
|
94
|
-
async function openEdit() {
|
|
95
|
-
const res = await lobb.findAll(selectedCollection!, { filter: { id: selectedId }, limit: 1 });
|
|
96
|
-
const result = await res.json();
|
|
97
|
-
editValues = result.data[0];
|
|
98
|
-
editDrawerOpen = true;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function handleEditChanges(changes: import('./detailView/utils').Changes) {
|
|
102
|
-
if (Object.keys(changes.data).length === 0) {
|
|
103
|
-
entry = { ...entry, [virtualField]: undefined };
|
|
104
|
-
} else {
|
|
105
|
-
entry = { ...entry, [virtualField]: { collection: selectedCollection, id: selectedId, update: changes.data } };
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function handleUnlink() {
|
|
110
|
-
if (hasRealValue) {
|
|
111
|
-
originalSnapshot = { collection: selectedCollection!, id: selectedId! };
|
|
112
|
-
unlinked = true;
|
|
113
|
-
entry = { ...entry, [collectionField]: null, [idField]: null, [virtualField]: undefined };
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function handleDelete() {
|
|
118
|
-
if (hasRealValue) {
|
|
119
|
-
originalSnapshot = { collection: selectedCollection!, id: selectedId! };
|
|
120
|
-
entry = { ...entry, [virtualField]: { delete: true } };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function handleRevert() {
|
|
125
|
-
if (originalSnapshot) {
|
|
126
|
-
entry = { ...entry, [collectionField]: originalSnapshot.collection, [idField]: originalSnapshot.id, [virtualField]: undefined };
|
|
127
|
-
originalSnapshot = null;
|
|
128
|
-
} else {
|
|
129
|
-
entry = { ...entry, [collectionField]: null, [idField]: null, [virtualField]: undefined };
|
|
130
|
-
}
|
|
131
|
-
unlinked = false;
|
|
132
|
-
}
|
|
133
81
|
</script>
|
|
134
82
|
|
|
135
83
|
<div class="flex h-9 w-full items-center gap-1.5 rounded-md border pl-1.5 pr-9 text-xs bg-muted {bgClass} {destructive ? '!bg-destructive/10 border-destructive' : ''}">
|
|
136
|
-
<!-- Collection picker -->
|
|
137
84
|
<Popover.Root bind:open={collectionPopoverOpen}>
|
|
138
85
|
<Popover.Trigger>
|
|
139
86
|
{#snippet child({ props })}
|
|
@@ -161,25 +108,15 @@
|
|
|
161
108
|
</Popover.Content>
|
|
162
109
|
</Popover.Root>
|
|
163
110
|
|
|
164
|
-
<!-- ID input (only editable when real value or empty) -->
|
|
165
111
|
<input
|
|
166
|
-
placeholder="NULL"
|
|
112
|
+
placeholder={isPendingCreate ? "AUTO GENERATED" : "NULL"}
|
|
167
113
|
type="number"
|
|
168
114
|
class="min-w-0 flex-1 bg-transparent outline-none text-xs placeholder:text-muted-foreground"
|
|
169
|
-
value={
|
|
170
|
-
disabled={isPendingCreate || isPendingEdit}
|
|
115
|
+
value={selectedId ?? ""}
|
|
171
116
|
oninput={onIdChange}
|
|
172
117
|
/>
|
|
173
118
|
|
|
174
|
-
|
|
175
|
-
{#if isStagedUnlink || isStagedDelete || isPendingCreate || isPendingEdit}
|
|
176
|
-
<Button class="h-5 w-5 px-0 shrink-0 hover:bg-transparent text-muted-foreground" variant="ghost" Icon={RotateCcw} onclick={handleRevert} title="Revert"></Button>
|
|
177
|
-
{:else if hasRealValue}
|
|
178
|
-
<Button class="h-5 w-5 px-0 shrink-0 hover:bg-transparent text-muted-foreground" variant="ghost" Icon={Trash} onclick={handleDelete} title="Delete record"></Button>
|
|
179
|
-
<Button class="h-5 w-5 px-0 shrink-0 hover:bg-transparent text-muted-foreground" variant="ghost" Icon={Unlink} onclick={handleUnlink} title="Unlink"></Button>
|
|
180
|
-
<Button class="h-5 w-5 px-0 shrink-0 hover:bg-transparent text-muted-foreground" variant="ghost" Icon={Pencil} onclick={openEdit} title="Edit record"></Button>
|
|
181
|
-
<Button class="h-5 w-5 px-0 shrink-0 hover:bg-transparent text-muted-foreground" variant="ghost" Icon={RefreshCw} onclick={() => (recordDrawerOpen = true)} title="Replace"></Button>
|
|
182
|
-
{:else if selectedCollection && !isPendingCreate}
|
|
119
|
+
{#if selectedCollection}
|
|
183
120
|
<Button class="h-6 shrink-0 px-2 font-normal text-xs" variant="outline" onclick={() => (createDrawerOpen = true)}>
|
|
184
121
|
<Plus size="13" />
|
|
185
122
|
Create
|
|
@@ -218,13 +155,3 @@
|
|
|
218
155
|
onCancel={async () => { createDrawerOpen = false; }}
|
|
219
156
|
/>
|
|
220
157
|
{/if}
|
|
221
|
-
|
|
222
|
-
{#if editDrawerOpen && editValues && selectedCollection}
|
|
223
|
-
<UpdateDetailView
|
|
224
|
-
collectionName={selectedCollection}
|
|
225
|
-
recordId={String(editValues.id)}
|
|
226
|
-
values={editValues}
|
|
227
|
-
onChanges={handleEditChanges}
|
|
228
|
-
onCancel={async () => { editDrawerOpen = false; editValues = undefined; }}
|
|
229
|
-
/>
|
|
230
|
-
{/if}
|