@lobb-js/studio 0.27.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/listViewChildren.svelte +1 -1
- package/dist/components/detailView/create/createDetailView.svelte +1 -6
- package/dist/components/detailView/update/updateDetailView.svelte +1 -4
- 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/package.json +2 -2
- package/src/lib/components/dataTable/listViewChildren.svelte +1 -1
- package/src/lib/components/detailView/create/createDetailView.svelte +1 -6
- package/src/lib/components/detailView/update/updateDetailView.svelte +1 -4
- 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
|
@@ -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,
|
|
@@ -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),
|
|
@@ -61,7 +59,6 @@
|
|
|
61
59
|
|
|
62
60
|
async function handleSave() {
|
|
63
61
|
delete localEntry.id;
|
|
64
|
-
localEntry = serializeEntry(ctx, collectionName, localEntry);
|
|
65
62
|
|
|
66
63
|
const children = Object.keys(pendingChildren).length ? pendingChildren : undefined;
|
|
67
64
|
const response = await lobb.updateOne(
|
|
@@ -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
|
};
|
|
@@ -3,10 +3,8 @@ import type { DetailFormField } from "./detailViewForm.svelte";
|
|
|
3
3
|
export declare function getDefaultEntry(ctx: CTX, fieldNames: string[], collectionName: string, values?: Record<string, any>): {
|
|
4
4
|
[k: string]: any;
|
|
5
5
|
};
|
|
6
|
-
export declare function serializeEntry(ctx: CTX, collectionName: string, entry: Record<string, any>): Record<string, any>;
|
|
7
6
|
export declare function buildChildren(ctx: CTX, collectionName: string, entry: Record<string, any>): Record<string, {
|
|
8
7
|
create?: any[];
|
|
9
8
|
link?: any[];
|
|
10
9
|
}> | undefined;
|
|
11
|
-
export declare function parseDetailViewValues(ctx: CTX, collectionName: string, values: Record<string, any>): void;
|
|
12
10
|
export declare function getCollectionFields(ctx: CTX, collectionName: string): DetailFormField[];
|
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
var __assign = (this && this.__assign) || function () {
|
|
2
|
-
__assign = Object.assign || function(t) {
|
|
3
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
-
s = arguments[i];
|
|
5
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
-
t[p] = s[p];
|
|
7
|
-
}
|
|
8
|
-
return t;
|
|
9
|
-
};
|
|
10
|
-
return __assign.apply(this, arguments);
|
|
11
|
-
};
|
|
12
1
|
import Mustache from "mustache";
|
|
13
|
-
import { isRelationField } from "../../relations";
|
|
14
2
|
import { getField } from "../dataTable/utils";
|
|
15
3
|
export function getDefaultEntry(ctx, fieldNames, collectionName, values) {
|
|
16
4
|
return Object.fromEntries(fieldNames.map(function (fieldName) {
|
|
@@ -33,42 +21,17 @@ export function getDefaultEntry(ctx, fieldNames, collectionName, values) {
|
|
|
33
21
|
return [fieldName, value];
|
|
34
22
|
}));
|
|
35
23
|
}
|
|
36
|
-
export function serializeEntry(ctx, collectionName, entry) {
|
|
37
|
-
entry = __assign({}, entry);
|
|
38
|
-
// extract FK object fields → ID
|
|
39
|
-
for (var _i = 0, _a = Object.entries(entry); _i < _a.length; _i++) {
|
|
40
|
-
var _b = _a[_i], fieldName = _b[0], fieldValue = _b[1];
|
|
41
|
-
var isRefrenceField = isRelationField(ctx, collectionName, fieldName);
|
|
42
|
-
if (isRefrenceField && fieldValue !== null && fieldValue.id !== undefined) {
|
|
43
|
-
entry[fieldName] = fieldValue.id;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
// extract polymorphic id_field objects → ID
|
|
47
|
-
for (var _c = 0, _d = ctx.meta.relations; _c < _d.length; _c++) {
|
|
48
|
-
var relation = _d[_c];
|
|
49
|
-
if (relation.type !== "polymorphic")
|
|
50
|
-
continue;
|
|
51
|
-
if (relation.from.collection !== collectionName)
|
|
52
|
-
continue;
|
|
53
|
-
var idField = relation.from.id_field;
|
|
54
|
-
var fieldValue = entry[idField];
|
|
55
|
-
if (fieldValue !== null && typeof fieldValue === "object" && fieldValue.id !== undefined) {
|
|
56
|
-
entry[idField] = fieldValue.id;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return entry;
|
|
60
|
-
}
|
|
61
24
|
export function buildChildren(ctx, collectionName, entry) {
|
|
62
25
|
var childrenRelations = ctx.meta.relations.filter(function (relation) { return relation.type !== "polymorphic" && relation.to.collection === collectionName; });
|
|
63
26
|
var children = {};
|
|
64
|
-
var
|
|
27
|
+
for (var _i = 0, childrenRelations_1 = childrenRelations; _i < childrenRelations_1.length; _i++) {
|
|
28
|
+
var relation = childrenRelations_1[_i];
|
|
65
29
|
var childCollection = relation.from.collection;
|
|
66
30
|
var childEntries = entry[childCollection];
|
|
67
31
|
if (!(childEntries === null || childEntries === void 0 ? void 0 : childEntries.length))
|
|
68
|
-
|
|
32
|
+
continue;
|
|
69
33
|
var toCreate = childEntries
|
|
70
|
-
.filter(function (e) { return !e.id; })
|
|
71
|
-
.map(function (e) { return serializeEntry(ctx, childCollection, e); });
|
|
34
|
+
.filter(function (e) { return !e.id; });
|
|
72
35
|
var toLink = childEntries
|
|
73
36
|
.filter(function (e) { return e.id; })
|
|
74
37
|
.map(function (e) { return e.id; });
|
|
@@ -79,54 +42,15 @@ export function buildChildren(ctx, collectionName, entry) {
|
|
|
79
42
|
if (toLink.length)
|
|
80
43
|
children[childCollection].link = toLink;
|
|
81
44
|
}
|
|
82
|
-
};
|
|
83
|
-
for (var _i = 0, childrenRelations_1 = childrenRelations; _i < childrenRelations_1.length; _i++) {
|
|
84
|
-
var relation = childrenRelations_1[_i];
|
|
85
|
-
_loop_1(relation);
|
|
86
45
|
}
|
|
87
46
|
return Object.keys(children).length ? children : undefined;
|
|
88
47
|
}
|
|
89
|
-
export function parseDetailViewValues(ctx, collectionName, values) {
|
|
90
|
-
var forignFieldNames = ctx.meta.relations
|
|
91
|
-
.filter(function (relation) { return relation.type !== "polymorphic" && relation.from.collection === collectionName; })
|
|
92
|
-
.map(function (relation) { return relation.from.field; });
|
|
93
|
-
var childCollectionNames = ctx.meta.relations
|
|
94
|
-
.filter(function (relation) { return relation.to.collection === collectionName; })
|
|
95
|
-
.map(function (relation) { return relation.from.collection; });
|
|
96
|
-
for (var _i = 0, _a = Object.entries(values); _i < _a.length; _i++) {
|
|
97
|
-
var _b = _a[_i], key = _b[0], value = _b[1];
|
|
98
|
-
if (forignFieldNames.includes(key)) {
|
|
99
|
-
if (typeof value === 'number') {
|
|
100
|
-
values[key] = {
|
|
101
|
-
id: value,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
else if (childCollectionNames.includes(key)) {
|
|
106
|
-
for (var index = 0; index < values[key].length; index++) {
|
|
107
|
-
parseDetailViewValues(ctx, key, values[key][index]);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
// wrap polymorphic id_field scalar values into { id: value }
|
|
112
|
-
for (var _c = 0, _d = ctx.meta.relations; _c < _d.length; _c++) {
|
|
113
|
-
var relation = _d[_c];
|
|
114
|
-
if (relation.type !== "polymorphic")
|
|
115
|
-
continue;
|
|
116
|
-
if (relation.from.collection !== collectionName)
|
|
117
|
-
continue;
|
|
118
|
-
var idField = relation.from.id_field;
|
|
119
|
-
if (idField in values && typeof values[idField] === 'number') {
|
|
120
|
-
values[idField] = { id: values[idField] };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
48
|
export function getCollectionFields(ctx, collectionName) {
|
|
125
49
|
var returnedData = [];
|
|
126
50
|
var collectionFields = ctx.meta.collections[collectionName].fields;
|
|
127
51
|
var isSingleton = ctx.meta.collections[collectionName].singleton;
|
|
128
52
|
for (var _i = 0, _a = Object.entries(collectionFields); _i < _a.length; _i++) {
|
|
129
|
-
var
|
|
53
|
+
var fieldName = _a[_i][0];
|
|
130
54
|
if (isSingleton && fieldName === "id") {
|
|
131
55
|
continue;
|
|
132
56
|
}
|
|
@@ -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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobb-js/studio",
|
|
3
3
|
"license": "UNLICENSED",
|
|
4
|
-
"version": "0.27.
|
|
4
|
+
"version": "0.27.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"postpublish": "./scripts/postpublish.sh"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@lobb-js/core": "^0.
|
|
45
|
+
"@lobb-js/core": "^0.31.0",
|
|
46
46
|
"@chromatic-com/storybook": "^4.1.2",
|
|
47
47
|
"@storybook/addon-a11y": "^10.0.1",
|
|
48
48
|
"@storybook/addon-docs": "^10.0.1",
|
|
@@ -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,
|
|
@@ -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),
|
|
@@ -61,7 +59,6 @@
|
|
|
61
59
|
|
|
62
60
|
async function handleSave() {
|
|
63
61
|
delete localEntry.id;
|
|
64
|
-
localEntry = serializeEntry(ctx, collectionName, localEntry);
|
|
65
62
|
|
|
66
63
|
const children = Object.keys(pendingChildren).length ? pendingChildren : undefined;
|
|
67
64
|
const response = await lobb.updateOne(
|
|
@@ -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
|
|