@lobb-js/studio 0.26.0 → 0.27.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 +70 -9
- package/dist/components/dataTable/dataTable.svelte.d.ts +24 -0
- package/dist/components/dataTable/header.svelte +88 -24
- package/dist/components/dataTable/header.svelte.d.ts +4 -0
- package/dist/components/dataTable/listViewChildren.svelte +10 -3
- package/dist/components/dataTable/table.svelte +1 -1
- package/dist/components/detailView/create/createDetailView.svelte +1 -6
- package/dist/components/detailView/update/detailViewChildren.svelte +50 -55
- package/dist/components/detailView/update/detailViewChildren.svelte.d.ts +8 -1
- package/dist/components/detailView/update/updateDetailView.svelte +6 -6
- package/dist/components/detailView/update/updateDetailViewButton.svelte +2 -3
- package/dist/components/detailView/utils.d.ts +0 -2
- package/dist/components/detailView/utils.js +5 -81
- package/dist/components/foreingKeyInput.svelte +42 -17
- package/dist/components/foreingKeyInput.svelte.d.ts +1 -1
- package/dist/components/polymorphicInput.svelte +25 -9
- package/dist/utils.js +2 -1
- package/package.json +2 -2
- package/src/lib/components/dataTable/dataTable.svelte +70 -9
- package/src/lib/components/dataTable/header.svelte +88 -24
- package/src/lib/components/dataTable/listViewChildren.svelte +10 -3
- package/src/lib/components/dataTable/table.svelte +1 -1
- package/src/lib/components/detailView/create/createDetailView.svelte +1 -6
- package/src/lib/components/detailView/update/detailViewChildren.svelte +50 -55
- package/src/lib/components/detailView/update/updateDetailView.svelte +6 -6
- package/src/lib/components/detailView/update/updateDetailViewButton.svelte +2 -3
- package/src/lib/components/detailView/utils.ts +2 -64
- package/src/lib/components/foreingKeyInput.svelte +42 -17
- package/src/lib/components/polymorphicInput.svelte +25 -9
- package/src/lib/utils.ts +2 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getStudioContext } from "../../context";
|
|
3
|
-
import { ChevronRight, Table, Plus } from "lucide-svelte";
|
|
3
|
+
import { ChevronRight, Table, Plus, Link, ArrowLeftRight, GitFork } from "lucide-svelte";
|
|
4
4
|
import DataTable from "./dataTable.svelte";
|
|
5
5
|
import CreateDetailViewButton from "../detailView/create/createDetailViewButton.svelte";
|
|
6
6
|
import ExtensionsComponents from "../extensionsComponents.svelte";
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
let { collectionName, recordId, width, unifiedBgColor }: Props = $props();
|
|
19
19
|
|
|
20
20
|
const children = (ctx.meta.collections[collectionName]?.children ?? [])
|
|
21
|
-
.filter((c: any) => c.type === "fk" || c.type === "m2m");
|
|
21
|
+
.filter((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic");
|
|
22
22
|
|
|
23
23
|
let expandedRows: boolean[] = $state(new Array(children.length).fill(false));
|
|
24
24
|
let refreshDataTable = $state(true);
|
|
@@ -49,6 +49,13 @@
|
|
|
49
49
|
/>
|
|
50
50
|
<Table size="17.5" class="text-muted-foreground" />
|
|
51
51
|
<div class="text-muted-foreground">{child.collection}</div>
|
|
52
|
+
{#if child.type === "fk"}
|
|
53
|
+
<span title="Direct (FK)"><Link size="13" class="text-muted-foreground/50" /></span>
|
|
54
|
+
{:else if child.type === "m2m"}
|
|
55
|
+
<span title="Many to Many"><ArrowLeftRight size="13" class="text-muted-foreground/50" /></span>
|
|
56
|
+
{:else if child.type === "polymorphic"}
|
|
57
|
+
<span title="Polymorphic"><GitFork size="13" class="text-muted-foreground/50" /></span>
|
|
58
|
+
{/if}
|
|
52
59
|
</button>
|
|
53
60
|
{#if child.type === "fk"}
|
|
54
61
|
<div class="flex items-center px-2">
|
|
@@ -57,7 +64,7 @@
|
|
|
57
64
|
variant="ghost"
|
|
58
65
|
class="h-7 px-3 text-xs font-normal"
|
|
59
66
|
Icon={Plus}
|
|
60
|
-
values={{ [child.field]:
|
|
67
|
+
values={{ [child.field]: recordId }}
|
|
61
68
|
onSuccessfullSave={async () => { refreshDataTable = !refreshDataTable; }}
|
|
62
69
|
>
|
|
63
70
|
Create
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
<div
|
|
173
173
|
style="
|
|
174
174
|
display: grid;
|
|
175
|
-
grid-template-columns: minmax(auto,
|
|
175
|
+
grid-template-columns: minmax(auto, 10rem) repeat({columnsLength - 1}, minmax(auto, 15rem)){rowActionsExists ? ' minmax(auto, 7.5rem)' : ''};
|
|
176
176
|
grid-template-rows: 2.5rem;
|
|
177
177
|
"
|
|
178
178
|
>
|
|
@@ -30,8 +30,6 @@
|
|
|
30
30
|
import {
|
|
31
31
|
buildChildren,
|
|
32
32
|
getDefaultEntry,
|
|
33
|
-
parseDetailViewValues,
|
|
34
|
-
serializeEntry,
|
|
35
33
|
} from "../utils";
|
|
36
34
|
import { getChangedProperties } from "../../../utils";
|
|
37
35
|
import type { Snippet } from "svelte";
|
|
@@ -50,8 +48,6 @@
|
|
|
50
48
|
submitButton,
|
|
51
49
|
}: CreateDetailViewProp = $props();
|
|
52
50
|
|
|
53
|
-
parseDetailViewValues(ctx, collectionName, values)
|
|
54
|
-
|
|
55
51
|
const fieldNames = Object.keys(ctx.meta.collections[collectionName].fields);
|
|
56
52
|
let entry: Record<string, any> = $state(
|
|
57
53
|
getDefaultEntry(ctx, fieldNames, collectionName, values),
|
|
@@ -88,9 +84,8 @@
|
|
|
88
84
|
return;
|
|
89
85
|
}
|
|
90
86
|
|
|
91
|
-
const serializedEntry = serializeEntry(ctx, collectionName, localEntry);
|
|
92
87
|
const children = buildChildren(ctx, collectionName, localEntry);
|
|
93
|
-
const response = await lobb.createOne(collectionName,
|
|
88
|
+
const response = await lobb.createOne(collectionName, localEntry, children);
|
|
94
89
|
|
|
95
90
|
await emitEvent({ lobb, ctx }, "studio.collections.create", {
|
|
96
91
|
collectionName,
|
|
@@ -1,77 +1,72 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import DataTable from "../../../components/dataTable/dataTable.svelte";
|
|
2
|
+
import DataTable, { type RecordOperation } from "../../../components/dataTable/dataTable.svelte";
|
|
3
3
|
import { getStudioContext } from "../../../context";
|
|
4
|
-
import {
|
|
5
|
-
import CreateDetailViewButton from "../create/createDetailViewButton.svelte";
|
|
6
|
-
import ExtensionsComponents from "../../../components/extensionsComponents.svelte";
|
|
7
|
-
import { getExtensionUtils } from "../../../extensions/extensionUtils";
|
|
4
|
+
import { Table, Link } from "lucide-svelte";
|
|
8
5
|
|
|
9
|
-
const { ctx
|
|
6
|
+
const { ctx } = getStudioContext();
|
|
7
|
+
|
|
8
|
+
type PendingOps = {
|
|
9
|
+
link?: (string | number)[];
|
|
10
|
+
unlink?: (string | number)[];
|
|
11
|
+
delete?: (string | number)[];
|
|
12
|
+
create?: any[];
|
|
13
|
+
};
|
|
10
14
|
|
|
11
15
|
interface LocalProp {
|
|
12
16
|
collectionName: string;
|
|
13
17
|
entry: any;
|
|
18
|
+
pendingChildren?: Record<string, PendingOps>;
|
|
14
19
|
}
|
|
15
20
|
|
|
16
|
-
let { collectionName, entry }: LocalProp = $props();
|
|
21
|
+
let { collectionName, entry, pendingChildren = $bindable({}) }: LocalProp = $props();
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
function makeHandler(collection: string) {
|
|
24
|
+
return (op: RecordOperation) => {
|
|
25
|
+
if (!pendingChildren[collection]) pendingChildren[collection] = {};
|
|
26
|
+
const c = pendingChildren[collection];
|
|
27
|
+
if (op.type === "link") {
|
|
28
|
+
(c.link ??= []).push(op.record.id);
|
|
29
|
+
} else if (op.type === "unlink") {
|
|
30
|
+
(c.unlink ??= []).push(op.id);
|
|
31
|
+
} else if (op.type === "delete") {
|
|
32
|
+
(c.delete ??= []).push(op.id);
|
|
33
|
+
} else if (op.type === "create") {
|
|
34
|
+
(c.create ??= []).push(op.record);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
20
38
|
|
|
21
|
-
const
|
|
39
|
+
const children = (ctx.meta.collections[collectionName]?.children ?? [])
|
|
40
|
+
.filter((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic");
|
|
22
41
|
</script>
|
|
23
42
|
|
|
24
43
|
{#if children.length}
|
|
25
|
-
<div class="flex flex-col gap-
|
|
44
|
+
<div class="flex flex-col gap-3 border-t p-4">
|
|
26
45
|
<div class="flex items-center gap-2">
|
|
27
|
-
<Link size="
|
|
28
|
-
<
|
|
46
|
+
<Link size="14" class="text-muted-foreground" />
|
|
47
|
+
<span class="text-sm font-medium">Sub Records</span>
|
|
29
48
|
</div>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<
|
|
33
|
-
name="detailView.update.subRecords.{child.collection}"
|
|
34
|
-
utils={getExtensionUtils(lobb, ctx)}
|
|
49
|
+
{#each children as child}
|
|
50
|
+
<div class="rounded-lg border bg-background overflow-hidden flex flex-col max-h-96">
|
|
51
|
+
<DataTable
|
|
35
52
|
collectionName={child.collection}
|
|
36
53
|
searchParams={{ children_of: collectionName, parent_id: entry.id }}
|
|
37
|
-
|
|
54
|
+
parentContext={{ collectionName, recordId: entry.id }}
|
|
55
|
+
onOperation={makeHandler(child.collection)}
|
|
56
|
+
showImport={false}
|
|
57
|
+
showHeader={true}
|
|
58
|
+
showFooter={true}
|
|
59
|
+
showDelete={child.type === "fk" || child.type === "m2m"}
|
|
60
|
+
tableProps={{ showLastColumnBorder: false, showLastRowBorder: true }}
|
|
38
61
|
>
|
|
39
|
-
|
|
40
|
-
<div class="flex items-center
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
<div class="text-sm text-muted-foreground">{child.collection}</div>
|
|
44
|
-
</div>
|
|
45
|
-
{#if child.type === "fk"}
|
|
46
|
-
<div class="flex gap-2">
|
|
47
|
-
<CreateDetailViewButton
|
|
48
|
-
variant="ghost"
|
|
49
|
-
class="h-7 px-2 font-normal text-xs"
|
|
50
|
-
Icon={Plus}
|
|
51
|
-
collectionName={child.collection}
|
|
52
|
-
onSuccessfullSave={async () => { refresh[index] = !refresh[index]; }}
|
|
53
|
-
>
|
|
54
|
-
Create
|
|
55
|
-
</CreateDetailViewButton>
|
|
56
|
-
</div>
|
|
57
|
-
{/if}
|
|
58
|
-
</div>
|
|
59
|
-
<div class="max-h-72 overflow-auto rounded-md">
|
|
60
|
-
{#key refresh[index]}
|
|
61
|
-
<DataTable
|
|
62
|
-
collectionName={child.collection}
|
|
63
|
-
searchParams={{ children_of: collectionName, parent_id: entry.id }}
|
|
64
|
-
unifiedBgColor="bg-muted/30"
|
|
65
|
-
showHeader={false}
|
|
66
|
-
showFooter={false}
|
|
67
|
-
showDelete={child.type === "fk"}
|
|
68
|
-
tableProps={{ showLastColumnBorder: false, showLastRowBorder: false, showCheckboxes: false }}
|
|
69
|
-
/>
|
|
70
|
-
{/key}
|
|
62
|
+
{#snippet headerLeft()}
|
|
63
|
+
<div class="flex items-center gap-2 px-1">
|
|
64
|
+
<Table size="14" class="text-muted-foreground" />
|
|
65
|
+
<span class="text-sm font-medium">{child.collection}</span>
|
|
71
66
|
</div>
|
|
72
|
-
|
|
73
|
-
</
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
{/snippet}
|
|
68
|
+
</DataTable>
|
|
69
|
+
</div>
|
|
70
|
+
{/each}
|
|
76
71
|
</div>
|
|
77
72
|
{/if}
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
import { getField, getFieldIcon } from "../../dataTable/utils";
|
|
32
32
|
import DetailViewChildren from "../update/detailViewChildren.svelte";
|
|
33
33
|
import type { Snippet } from "svelte";
|
|
34
|
-
import { getDefaultEntry
|
|
34
|
+
import { getDefaultEntry } from "../utils";
|
|
35
35
|
import FieldInput from "../fieldInput.svelte";
|
|
36
36
|
import Drawer from "../../../components/drawer.svelte";
|
|
37
37
|
|
|
@@ -46,8 +46,6 @@
|
|
|
46
46
|
recordId,
|
|
47
47
|
}: UpdateDetailViewProp = $props();
|
|
48
48
|
|
|
49
|
-
parseDetailViewValues(ctx, collectionName, values)
|
|
50
|
-
|
|
51
49
|
const fieldNames = Object.keys(ctx.meta.collections[collectionName].fields);
|
|
52
50
|
let entry: Record<string, any> = $state(
|
|
53
51
|
getDefaultEntry(ctx, fieldNames, collectionName, values),
|
|
@@ -57,15 +55,17 @@
|
|
|
57
55
|
getChangedProperties(initialEntry, $state.snapshot(entry)),
|
|
58
56
|
);
|
|
59
57
|
let fieldsErrors: Record<string, any> = $state({});
|
|
58
|
+
let pendingChildren = $state<Record<string, any>>({});
|
|
60
59
|
|
|
61
60
|
async function handleSave() {
|
|
62
61
|
delete localEntry.id;
|
|
63
|
-
localEntry = serializeEntry(ctx, collectionName, localEntry);
|
|
64
62
|
|
|
63
|
+
const children = Object.keys(pendingChildren).length ? pendingChildren : undefined;
|
|
65
64
|
const response = await lobb.updateOne(
|
|
66
65
|
collectionName,
|
|
67
66
|
recordId,
|
|
68
67
|
localEntry,
|
|
68
|
+
children,
|
|
69
69
|
);
|
|
70
70
|
|
|
71
71
|
if (!response.bodyUsed) {
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
{/each}
|
|
146
146
|
</div>
|
|
147
147
|
{#if showRelatedRecords}
|
|
148
|
-
<DetailViewChildren {collectionName} {entry} />
|
|
148
|
+
<DetailViewChildren {collectionName} {entry} bind:pendingChildren />
|
|
149
149
|
{/if}
|
|
150
150
|
</div>
|
|
151
151
|
<div class="flex h-12 items-center justify-end gap-2 border-t px-4">
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
class="h-7 px-3 text-xs font-normal"
|
|
164
164
|
Icon={submitButton?.icon ? submitButton.icon : Pencil}
|
|
165
165
|
onclick={handleSave}
|
|
166
|
-
disabled={!Object.keys(localEntry).length}
|
|
166
|
+
disabled={!Object.keys(localEntry).length && !Object.keys(pendingChildren).length}
|
|
167
167
|
>
|
|
168
168
|
{submitButton?.text ? submitButton.text : "Update"}
|
|
169
169
|
</Button>
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
import Button from "../../../components/ui/button/button.svelte";
|
|
5
5
|
import UpdateDetailView from "./updateDetailView.svelte";
|
|
6
6
|
import { getStudioContext } from "../../../context";
|
|
7
|
-
import { getCollectionParamsFields } from "../../dataTable/utils";
|
|
8
7
|
|
|
9
8
|
interface LocalProp extends UpdateDetailViewProp {
|
|
10
9
|
variant?: ButtonProps["variant"];
|
|
@@ -17,11 +16,11 @@
|
|
|
17
16
|
let open = $state(false);
|
|
18
17
|
let values: Record<string, any> | undefined = $state(undefined);
|
|
19
18
|
|
|
20
|
-
const { lobb
|
|
19
|
+
const { lobb } = getStudioContext();
|
|
21
20
|
|
|
22
21
|
async function openView() {
|
|
23
22
|
const params = {
|
|
24
|
-
fields:
|
|
23
|
+
fields: "*",
|
|
25
24
|
filter: { id: props.recordId },
|
|
26
25
|
limit: 1,
|
|
27
26
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import Mustache from "mustache";
|
|
2
2
|
import type { CTX } from "../../store.types";
|
|
3
|
-
import { isRelationField } from "../../relations";
|
|
4
3
|
import { getField } from "../dataTable/utils";
|
|
5
4
|
import type { DetailFormField } from "./detailViewForm.svelte";
|
|
6
5
|
|
|
@@ -26,34 +25,6 @@ export function getDefaultEntry(ctx: CTX, fieldNames: string[], collectionName:
|
|
|
26
25
|
);
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
export function serializeEntry(
|
|
30
|
-
ctx: CTX,
|
|
31
|
-
collectionName: string,
|
|
32
|
-
entry: Record<string, any>,
|
|
33
|
-
) {
|
|
34
|
-
entry = { ...entry }
|
|
35
|
-
|
|
36
|
-
// extract FK object fields → ID
|
|
37
|
-
for (const [fieldName, fieldValue] of Object.entries(entry)) {
|
|
38
|
-
const isRefrenceField = isRelationField(ctx, collectionName, fieldName);
|
|
39
|
-
if (isRefrenceField && fieldValue !== null && fieldValue.id !== undefined) {
|
|
40
|
-
entry[fieldName] = fieldValue.id;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// extract polymorphic id_field objects → ID
|
|
45
|
-
for (const relation of ctx.meta.relations) {
|
|
46
|
-
if (relation.type !== "polymorphic") continue;
|
|
47
|
-
if ((relation as any).from.collection !== collectionName) continue;
|
|
48
|
-
const idField = (relation as any).from.id_field;
|
|
49
|
-
const fieldValue = entry[idField];
|
|
50
|
-
if (fieldValue !== null && typeof fieldValue === "object" && fieldValue.id !== undefined) {
|
|
51
|
-
entry[idField] = fieldValue.id;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return entry;
|
|
56
|
-
}
|
|
57
28
|
|
|
58
29
|
export function buildChildren(
|
|
59
30
|
ctx: CTX,
|
|
@@ -72,8 +43,7 @@ export function buildChildren(
|
|
|
72
43
|
if (!childEntries?.length) continue;
|
|
73
44
|
|
|
74
45
|
const toCreate = childEntries
|
|
75
|
-
.filter((e) => !e.id)
|
|
76
|
-
.map((e) => serializeEntry(ctx, childCollection, e));
|
|
46
|
+
.filter((e) => !e.id);
|
|
77
47
|
const toLink = childEntries
|
|
78
48
|
.filter((e) => e.id)
|
|
79
49
|
.map((e) => e.id);
|
|
@@ -88,45 +58,13 @@ export function buildChildren(
|
|
|
88
58
|
return Object.keys(children).length ? children : undefined;
|
|
89
59
|
}
|
|
90
60
|
|
|
91
|
-
export function parseDetailViewValues(ctx: CTX, collectionName: string, values: Record<string, any>) {
|
|
92
|
-
const forignFieldNames = ctx.meta.relations
|
|
93
|
-
.filter((relation) => relation.type !== "polymorphic" && relation.from.collection === collectionName)
|
|
94
|
-
.map((relation) => relation.from.field);
|
|
95
|
-
const childCollectionNames = ctx.meta.relations
|
|
96
|
-
.filter((relation) => relation.to.collection === collectionName)
|
|
97
|
-
.map((relation) => relation.from.collection);
|
|
98
|
-
|
|
99
|
-
for (const [key, value] of Object.entries(values)) {
|
|
100
|
-
if (forignFieldNames.includes(key)) {
|
|
101
|
-
if (typeof value === 'number') {
|
|
102
|
-
values[key] = {
|
|
103
|
-
id: value,
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
} else if (childCollectionNames.includes(key)) {
|
|
107
|
-
for (let index = 0; index < values[key].length; index++) {
|
|
108
|
-
parseDetailViewValues(ctx, key, values[key][index]);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// wrap polymorphic id_field scalar values into { id: value }
|
|
114
|
-
for (const relation of ctx.meta.relations) {
|
|
115
|
-
if (relation.type !== "polymorphic") continue;
|
|
116
|
-
if ((relation as any).from.collection !== collectionName) continue;
|
|
117
|
-
const idField = (relation as any).from.id_field;
|
|
118
|
-
if (idField in values && typeof values[idField] === 'number') {
|
|
119
|
-
values[idField] = { id: values[idField] };
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
61
|
|
|
124
62
|
export function getCollectionFields(ctx: CTX, collectionName: string) {
|
|
125
63
|
let returnedData: DetailFormField[] = [];
|
|
126
64
|
|
|
127
65
|
const collectionFields = ctx.meta.collections[collectionName].fields;
|
|
128
66
|
const isSingleton = ctx.meta.collections[collectionName].singleton;
|
|
129
|
-
for (const [fieldName
|
|
67
|
+
for (const [fieldName] of Object.entries(collectionFields)) {
|
|
130
68
|
|
|
131
69
|
if (isSingleton && fieldName === "id") {
|
|
132
70
|
continue;
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
2
3
|
import Input from "./ui/input/input.svelte";
|
|
3
4
|
import SelectRecord from "./selectRecord.svelte";
|
|
4
5
|
import UpdateDetailViewButton from "./detailView/update/updateDetailViewButton.svelte";
|
|
6
|
+
import { getCollectionPrimaryField } from "./dataTable/utils";
|
|
7
|
+
import { getStudioContext } from "../context";
|
|
5
8
|
import { ExternalLink } from "lucide-svelte";
|
|
6
9
|
|
|
10
|
+
const { lobb, ctx } = getStudioContext();
|
|
11
|
+
|
|
7
12
|
interface LocalProps {
|
|
8
13
|
parentCollectionName: string;
|
|
9
14
|
collectionName: string;
|
|
10
15
|
fieldName: string;
|
|
11
|
-
value?:
|
|
16
|
+
value?: number | null;
|
|
12
17
|
destructive?: boolean;
|
|
13
18
|
entry: Record<string, any>;
|
|
14
19
|
}
|
|
@@ -22,31 +27,48 @@
|
|
|
22
27
|
entry,
|
|
23
28
|
}: LocalProps = $props();
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
let displayName = $state<string | null>(null);
|
|
31
|
+
|
|
32
|
+
onMount(async () => {
|
|
33
|
+
if (value == null) return;
|
|
34
|
+
try {
|
|
35
|
+
const res = await lobb.findOne(collectionName, value);
|
|
36
|
+
const record = (await res.json()).data;
|
|
37
|
+
const primaryFieldName = getCollectionPrimaryField(ctx, collectionName);
|
|
38
|
+
displayName = primaryFieldName ? String(record[primaryFieldName]) : null;
|
|
39
|
+
} catch {
|
|
40
|
+
displayName = null;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
$effect(() => {
|
|
45
|
+
if (value == null) displayName = null;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function handleSelect(selectedEntry: any) {
|
|
49
|
+
const primaryFieldName = getCollectionPrimaryField(ctx, collectionName);
|
|
50
|
+
value = selectedEntry.id;
|
|
51
|
+
displayName = primaryFieldName ? String(selectedEntry[primaryFieldName]) : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const idIsZero = $derived(value === 0);
|
|
28
55
|
</script>
|
|
29
56
|
|
|
30
|
-
<!-- THE SELECT BUTTON -->
|
|
31
57
|
{#if !idIsZero}
|
|
32
58
|
<div class="relative">
|
|
33
|
-
<div
|
|
34
|
-
|
|
35
|
-
>
|
|
36
|
-
{#if value !== null}
|
|
59
|
+
<div class="flex gap-2 absolute right-0 top-0 mr-9 h-full items-center text-xs">
|
|
60
|
+
{#if value != null}
|
|
37
61
|
<UpdateDetailViewButton
|
|
38
62
|
collectionName={collectionName}
|
|
39
|
-
recordId={value
|
|
63
|
+
recordId={value}
|
|
40
64
|
variant="ghost"
|
|
41
65
|
class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
|
|
42
66
|
Icon={ExternalLink}
|
|
43
67
|
></UpdateDetailViewButton>
|
|
44
68
|
{/if}
|
|
45
|
-
{#if
|
|
46
|
-
<div
|
|
47
|
-
|
|
48
|
-
>
|
|
49
|
-
{primaryField}
|
|
69
|
+
{#if displayName}
|
|
70
|
+
<div class="flex items-center bg-background rounded-full border h-6 px-3 shadow-sm">
|
|
71
|
+
{displayName}
|
|
50
72
|
</div>
|
|
51
73
|
{/if}
|
|
52
74
|
<SelectRecord
|
|
@@ -55,7 +77,7 @@
|
|
|
55
77
|
{parentCollectionName}
|
|
56
78
|
{collectionName}
|
|
57
79
|
{fieldName}
|
|
58
|
-
|
|
80
|
+
onSelect={handleSelect}
|
|
59
81
|
{entry}
|
|
60
82
|
/>
|
|
61
83
|
</div>
|
|
@@ -66,7 +88,10 @@
|
|
|
66
88
|
bg-muted/30 text-xs
|
|
67
89
|
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
68
90
|
"
|
|
69
|
-
bind:value={
|
|
91
|
+
bind:value={
|
|
92
|
+
() => value ?? "",
|
|
93
|
+
(v) => (value = (v === "" || v == null) ? null : Number(v))
|
|
94
|
+
}
|
|
70
95
|
/>
|
|
71
96
|
</div>
|
|
72
97
|
{:else}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
2
3
|
import Button from "./ui/button/button.svelte";
|
|
3
4
|
import DataTable from "./dataTable/dataTable.svelte";
|
|
4
5
|
import Drawer from "./drawer.svelte";
|
|
@@ -7,7 +8,7 @@
|
|
|
7
8
|
import { getStudioContext } from "../context";
|
|
8
9
|
import { ArrowLeft, Link, ChevronDown } from "lucide-svelte";
|
|
9
10
|
|
|
10
|
-
const { ctx } = getStudioContext();
|
|
11
|
+
const { ctx, lobb } = getStudioContext();
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
13
14
|
collectionField: string;
|
|
@@ -26,12 +27,28 @@
|
|
|
26
27
|
}: Props = $props();
|
|
27
28
|
|
|
28
29
|
const selectedCollection = $derived(entry[collectionField] ?? null);
|
|
29
|
-
const selectedId = $derived(entry[idField]
|
|
30
|
-
const primaryField = $derived(entry[idField] ? Object.values(entry[idField])[1] : null);
|
|
30
|
+
const selectedId = $derived(entry[idField] ?? null);
|
|
31
31
|
|
|
32
|
+
let displayName = $state<string | null>(null);
|
|
32
33
|
let collectionPopoverOpen = $state(false);
|
|
33
34
|
let recordDrawerOpen = $state(false);
|
|
34
35
|
|
|
36
|
+
onMount(async () => {
|
|
37
|
+
if (selectedCollection == null || selectedId == null) return;
|
|
38
|
+
try {
|
|
39
|
+
const res = await lobb.findOne(selectedCollection, selectedId);
|
|
40
|
+
const record = await res.json();
|
|
41
|
+
const primaryFieldName = getCollectionPrimaryField(ctx, selectedCollection);
|
|
42
|
+
displayName = primaryFieldName ? String(record[primaryFieldName]) : null;
|
|
43
|
+
} catch {
|
|
44
|
+
displayName = null;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
$effect(() => {
|
|
49
|
+
if (entry[idField] == null) displayName = null;
|
|
50
|
+
});
|
|
51
|
+
|
|
35
52
|
function onCollectionChange(col: string) {
|
|
36
53
|
collectionPopoverOpen = false;
|
|
37
54
|
if (entry[collectionField] !== col) {
|
|
@@ -42,14 +59,13 @@
|
|
|
42
59
|
function onIdChange(e: Event) {
|
|
43
60
|
const raw = (e.target as HTMLInputElement).value;
|
|
44
61
|
const id = raw === "" ? null : Number(raw);
|
|
45
|
-
entry = { ...entry, [idField]: id
|
|
62
|
+
entry = { ...entry, [idField]: id };
|
|
46
63
|
}
|
|
47
64
|
|
|
48
65
|
function onRecordSelect(record: any) {
|
|
49
66
|
const primaryFieldName = getCollectionPrimaryField(ctx, selectedCollection!);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
entry = { ...entry, [idField]: value };
|
|
67
|
+
entry = { ...entry, [idField]: record.id };
|
|
68
|
+
displayName = primaryFieldName ? String(record[primaryFieldName]) : null;
|
|
53
69
|
recordDrawerOpen = false;
|
|
54
70
|
}
|
|
55
71
|
</script>
|
|
@@ -93,9 +109,9 @@
|
|
|
93
109
|
/>
|
|
94
110
|
|
|
95
111
|
<!-- Primary field badge -->
|
|
96
|
-
{#if
|
|
112
|
+
{#if displayName}
|
|
97
113
|
<div class="flex shrink-0 items-center bg-background rounded-full border h-6 px-3 shadow-sm">
|
|
98
|
-
{
|
|
114
|
+
{displayName}
|
|
99
115
|
</div>
|
|
100
116
|
{/if}
|
|
101
117
|
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { clsx, type ClassValue } from "clsx";
|
|
2
2
|
import { twMerge } from "tailwind-merge";
|
|
3
|
+
import { isEqual } from "lodash";
|
|
3
4
|
|
|
4
5
|
import { MediaQuery } from 'svelte/reactivity';
|
|
5
6
|
|
|
@@ -34,7 +35,7 @@ export function calculateDrawerWidth() {
|
|
|
34
35
|
export function getChangedProperties(oldObj: Record<string, any>, newObj: Record<string, any>) {
|
|
35
36
|
const changes: Record<string, any> = {};
|
|
36
37
|
for (const key of Object.keys(newObj)) {
|
|
37
|
-
if (oldObj[key]
|
|
38
|
+
if (!isEqual(oldObj[key], newObj[key])) {
|
|
38
39
|
changes[key] = newObj[key];
|
|
39
40
|
}
|
|
40
41
|
}
|