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