@lobb-js/studio 0.19.1 → 0.21.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/actions.d.ts +1 -0
- package/dist/components/dataTable/fieldCell.svelte +38 -37
- package/dist/components/dataTable/polymorphicFieldCell.svelte +43 -0
- package/dist/components/dataTable/polymorphicFieldCell.svelte.d.ts +9 -0
- package/dist/components/dataTable/utils.js +27 -17
- package/dist/components/dataTableDrawer/dataTableDrawer.svelte +14 -10
- package/dist/components/dataTableDrawer/dataTableDrawer.svelte.d.ts +1 -0
- package/dist/components/detailView/create/createDetailView.svelte +38 -69
- package/dist/components/detailView/fieldInput.svelte +27 -11
- package/dist/components/detailView/fieldInput.svelte.d.ts +1 -1
- package/dist/components/detailView/update/updateDetailView.svelte +26 -30
- package/dist/components/detailView/utils.d.ts +5 -2
- package/dist/components/detailView/utils.js +53 -71
- package/dist/components/drawer.svelte +24 -8
- package/dist/components/drawer.svelte.d.ts +1 -0
- package/dist/components/polymorphicInput.svelte +141 -0
- package/dist/components/polymorphicInput.svelte.d.ts +10 -0
- package/dist/extensions/extension.types.d.ts +2 -0
- package/dist/extensions/extensionUtils.js +2 -0
- package/dist/relations.d.ts +14 -0
- package/dist/relations.js +47 -0
- package/dist/store.types.d.ts +1 -0
- package/dist/utils.d.ts +0 -3
- package/dist/utils.js +0 -21
- package/package.json +2 -2
- package/src/lib/actions.ts +1 -0
- package/src/lib/components/dataTable/fieldCell.svelte +38 -37
- package/src/lib/components/dataTable/polymorphicFieldCell.svelte +43 -0
- package/src/lib/components/dataTable/utils.ts +21 -18
- package/src/lib/components/dataTableDrawer/dataTableDrawer.svelte +14 -10
- package/src/lib/components/detailView/create/createDetailView.svelte +38 -69
- package/src/lib/components/detailView/fieldInput.svelte +27 -11
- package/src/lib/components/detailView/update/updateDetailView.svelte +26 -30
- package/src/lib/components/detailView/utils.ts +44 -75
- package/src/lib/components/drawer.svelte +24 -8
- package/src/lib/components/polymorphicInput.svelte +141 -0
- package/src/lib/extensions/extension.types.ts +2 -1
- package/src/lib/extensions/extensionUtils.ts +2 -0
- package/src/lib/relations.ts +52 -0
- package/src/lib/store.types.ts +1 -0
- package/src/lib/utils.ts +0 -21
package/dist/actions.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface OpenDataTableDrawerProps {
|
|
|
7
7
|
title?: string;
|
|
8
8
|
showHeader?: boolean;
|
|
9
9
|
showFooter?: boolean;
|
|
10
|
+
position?: "side" | "bottom";
|
|
10
11
|
}
|
|
11
12
|
export declare function showDialog(title: string, description: string): Promise<boolean>;
|
|
12
13
|
export declare function openCreateDetailView(studioContext: StudioContext, props: CreateDetailViewProp): void;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { getFieldRelationTarget, getPolymorphicRelation, isRelationField } from "../../relations";
|
|
3
3
|
import { ExternalLink } from "lucide-svelte";
|
|
4
4
|
import UpdateDetailViewButton from "../detailView/update/updateDetailViewButton.svelte";
|
|
5
5
|
import { getField } from "./utils";
|
|
6
6
|
import EnumBadge from "./enumBadge.svelte";
|
|
7
|
-
import
|
|
7
|
+
import PolymorphicFieldCell from "./polymorphicFieldCell.svelte";
|
|
8
8
|
import { getStudioContext } from "../../context";
|
|
9
9
|
|
|
10
10
|
const { ctx } = getStudioContext();
|
|
@@ -26,49 +26,50 @@
|
|
|
26
26
|
}: Props = $props();
|
|
27
27
|
|
|
28
28
|
const field = getField(ctx, fieldName, collectionName);
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
);
|
|
29
|
+
const relationTarget = getFieldRelationTarget(ctx, collectionName, fieldName);
|
|
30
|
+
const polymorphicRelation = getPolymorphicRelation(ctx, collectionName, fieldName);
|
|
31
|
+
const isRefrenceField = isRelationField(ctx, collectionName, fieldName);
|
|
33
32
|
const isPasswordField = ctx.meta.collections[collectionName].fields[fieldName]?.ui?.input?.type === "password";
|
|
34
33
|
</script>
|
|
35
34
|
|
|
36
|
-
{#if
|
|
35
|
+
{#if polymorphicRelation}
|
|
36
|
+
<PolymorphicFieldCell
|
|
37
|
+
collectionField={polymorphicRelation.from.collection_field}
|
|
38
|
+
idField={polymorphicRelation.from.id_field}
|
|
39
|
+
{entry}
|
|
40
|
+
bind:tableParams
|
|
41
|
+
/>
|
|
42
|
+
{:else if isRefrenceField}
|
|
43
|
+
{#if value?.id && value.id !== 0}
|
|
44
|
+
<div class="flex items-center gap-2">
|
|
45
|
+
<div>{value.id}</div>
|
|
46
|
+
{#if Object.values(value)[1]}
|
|
47
|
+
<div class="border bg-muted px-3 py-1 rounded-full">
|
|
48
|
+
{Object.values(value)[1]}
|
|
49
|
+
</div>
|
|
50
|
+
{/if}
|
|
51
|
+
<UpdateDetailViewButton
|
|
52
|
+
collectionName={relationTarget!}
|
|
53
|
+
recordId={value.id}
|
|
54
|
+
variant="ghost"
|
|
55
|
+
class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
|
|
56
|
+
Icon={ExternalLink}
|
|
57
|
+
onSuccessfullSave={async () => {
|
|
58
|
+
tableParams = { ...tableParams };
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
{:else if value?.id === 0}
|
|
63
|
+
<div class="text-muted-foreground">PARENT ID</div>
|
|
64
|
+
{:else}
|
|
65
|
+
<div class="text-muted-foreground">NULL</div>
|
|
66
|
+
{/if}
|
|
67
|
+
{:else if isPasswordField}
|
|
37
68
|
<div class="text-muted-foreground tracking-widest">••••••</div>
|
|
38
69
|
{:else if value === ""}
|
|
39
70
|
<div class="text-muted-foreground">EMPTY STRING</div>
|
|
40
71
|
{:else if value === null || value === undefined}
|
|
41
72
|
<div class="text-muted-foreground">NULL</div>
|
|
42
|
-
{:else if isRefrenceField}
|
|
43
|
-
{#if typeof value !== "object"}
|
|
44
|
-
<div>{value}</div>
|
|
45
|
-
{:else if value.id !== 0}
|
|
46
|
-
{@const primaryField = Object.values(value)[1]}
|
|
47
|
-
{#if value.id}
|
|
48
|
-
<div class="flex items-center gap-2">
|
|
49
|
-
<div>{value.id}</div>
|
|
50
|
-
{#if primaryField}
|
|
51
|
-
<div class="border bg-muted px-3 py-1 rounded-full">
|
|
52
|
-
{primaryField}
|
|
53
|
-
</div>
|
|
54
|
-
{/if}
|
|
55
|
-
<UpdateDetailViewButton
|
|
56
|
-
collectionName={relation.to.collection}
|
|
57
|
-
recordId={value.id}
|
|
58
|
-
variant="ghost"
|
|
59
|
-
class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
|
|
60
|
-
Icon={ExternalLink}
|
|
61
|
-
onSuccessfullSave={async () => {
|
|
62
|
-
tableParams = { ...tableParams };
|
|
63
|
-
}}
|
|
64
|
-
></UpdateDetailViewButton>
|
|
65
|
-
</div>
|
|
66
|
-
{:else}
|
|
67
|
-
<div class="text-muted-foreground">NULL</div>
|
|
68
|
-
{/if}
|
|
69
|
-
{:else}
|
|
70
|
-
<div class="text-muted-foreground">PARENT ID</div>
|
|
71
|
-
{/if}
|
|
72
73
|
{:else if field?.enum}
|
|
73
74
|
<EnumBadge {value} enum={field.enum} />
|
|
74
75
|
{:else if field.type === "datetime"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ExternalLink, Table } from "lucide-svelte";
|
|
3
|
+
import UpdateDetailViewButton from "../detailView/update/updateDetailViewButton.svelte";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
collectionField: string;
|
|
7
|
+
idField: string;
|
|
8
|
+
entry: Record<string, any>;
|
|
9
|
+
tableParams?: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
collectionField,
|
|
14
|
+
idField,
|
|
15
|
+
entry,
|
|
16
|
+
tableParams = $bindable(),
|
|
17
|
+
}: Props = $props();
|
|
18
|
+
|
|
19
|
+
const targetCollection = $derived(entry[collectionField] ?? null);
|
|
20
|
+
const targetId = $derived(entry[idField] ?? null);
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
{#if targetId && targetCollection}
|
|
24
|
+
<div class="flex items-center gap-2">
|
|
25
|
+
<div class="flex items-center gap-1 border bg-muted px-2 py-1 rounded-md text-muted-foreground">
|
|
26
|
+
<Table size="11" />
|
|
27
|
+
{targetCollection}
|
|
28
|
+
</div>
|
|
29
|
+
<div>{targetId}</div>
|
|
30
|
+
<UpdateDetailViewButton
|
|
31
|
+
collectionName={targetCollection}
|
|
32
|
+
recordId={targetId}
|
|
33
|
+
variant="ghost"
|
|
34
|
+
class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
|
|
35
|
+
Icon={ExternalLink}
|
|
36
|
+
onSuccessfullSave={async () => {
|
|
37
|
+
tableParams = { ...tableParams };
|
|
38
|
+
}}
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
{:else}
|
|
42
|
+
<div class="text-muted-foreground">NULL</div>
|
|
43
|
+
{/if}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
collectionField: string;
|
|
3
|
+
idField: string;
|
|
4
|
+
entry: Record<string, any>;
|
|
5
|
+
tableParams?: any;
|
|
6
|
+
}
|
|
7
|
+
declare const PolymorphicFieldCell: import("svelte").Component<Props, {}, "tableParams">;
|
|
8
|
+
type PolymorphicFieldCell = ReturnType<typeof PolymorphicFieldCell>;
|
|
9
|
+
export default PolymorphicFieldCell;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getFieldRelationTarget } from "../../relations";
|
|
2
|
+
import { Binary, Braces, Brackets, Calendar, CalendarClock, Clock, Hash, Key, Link, Text, Type, } from "lucide-svelte/icons";
|
|
3
|
+
import { icons } from "lucide-svelte";
|
|
2
4
|
export function getCollectionColumns(ctx, collectionName) {
|
|
5
|
+
var _a;
|
|
3
6
|
var collectionFields = getFields(ctx, collectionName);
|
|
4
7
|
var headers = [];
|
|
5
8
|
for (var fieldName in collectionFields) {
|
|
6
9
|
var field = collectionFields[fieldName];
|
|
10
|
+
if ((_a = field.ui) === null || _a === void 0 ? void 0 : _a.hidden)
|
|
11
|
+
continue;
|
|
7
12
|
headers.push({
|
|
8
13
|
id: field.key,
|
|
9
14
|
subtext: field.type,
|
|
@@ -13,7 +18,11 @@ export function getCollectionColumns(ctx, collectionName) {
|
|
|
13
18
|
return headers;
|
|
14
19
|
}
|
|
15
20
|
export function getFieldIcon(ctx, fieldName, collectionName) {
|
|
21
|
+
var _a, _b, _c;
|
|
16
22
|
var field = getField(ctx, fieldName, collectionName);
|
|
23
|
+
var uiIcon = (_a = field.ui) === null || _a === void 0 ? void 0 : _a.icon;
|
|
24
|
+
if (uiIcon && uiIcon in icons)
|
|
25
|
+
return icons[uiIcon];
|
|
17
26
|
if (fieldName === "id") {
|
|
18
27
|
return Key;
|
|
19
28
|
}
|
|
@@ -33,6 +42,13 @@ export function getFieldIcon(ctx, fieldName, collectionName) {
|
|
|
33
42
|
return Binary;
|
|
34
43
|
}
|
|
35
44
|
else if (field.type === "integer") {
|
|
45
|
+
var target = getFieldRelationTarget(ctx, collectionName, fieldName);
|
|
46
|
+
if (target) {
|
|
47
|
+
var targetIcon = (_c = (_b = ctx.meta.collections[target]) === null || _b === void 0 ? void 0 : _b.ui) === null || _c === void 0 ? void 0 : _c.icon;
|
|
48
|
+
if (targetIcon && targetIcon in icons)
|
|
49
|
+
return icons[targetIcon];
|
|
50
|
+
return Link;
|
|
51
|
+
}
|
|
36
52
|
return Hash;
|
|
37
53
|
}
|
|
38
54
|
else if (field.type === "long") {
|
|
@@ -53,7 +69,7 @@ export function getFieldIcon(ctx, fieldName, collectionName) {
|
|
|
53
69
|
else if (field.type === "time") {
|
|
54
70
|
return Clock;
|
|
55
71
|
}
|
|
56
|
-
|
|
72
|
+
return Braces;
|
|
57
73
|
}
|
|
58
74
|
export function getFields(ctx, collectionName) {
|
|
59
75
|
return ctx.meta.collections[collectionName].fields;
|
|
@@ -78,7 +94,7 @@ export function getCollectionParamsFields(ctx, collectionName, allFields) {
|
|
|
78
94
|
var relations = ctx.meta.relations;
|
|
79
95
|
var foreignFields = relations
|
|
80
96
|
.filter(function (relation) {
|
|
81
|
-
return relation.from.collection === collectionName;
|
|
97
|
+
return relation.type !== "polymorphic" && relation.from.collection === collectionName;
|
|
82
98
|
})
|
|
83
99
|
.map(function (relation) {
|
|
84
100
|
return {
|
|
@@ -87,24 +103,18 @@ export function getCollectionParamsFields(ctx, collectionName, allFields) {
|
|
|
87
103
|
};
|
|
88
104
|
});
|
|
89
105
|
var columns = [];
|
|
90
|
-
for (var
|
|
91
|
-
var foreignField =
|
|
92
|
-
if (!foreignField.collection || !ctx.meta.collections[foreignField.collection])
|
|
106
|
+
for (var _i = 0, foreignFields_1 = foreignFields; _i < foreignFields_1.length; _i++) {
|
|
107
|
+
var foreignField = foreignFields_1[_i];
|
|
108
|
+
if (!foreignField.collection || !ctx.meta.collections[foreignField.collection])
|
|
93
109
|
continue;
|
|
110
|
+
if (allFields) {
|
|
111
|
+
columns.push("".concat(foreignField.field, ".*"));
|
|
94
112
|
}
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
else {
|
|
114
|
+
columns.push("".concat(foreignField.field, ".id"));
|
|
97
115
|
var primaryField = getCollectionPrimaryField(ctx, foreignField.collection);
|
|
98
|
-
if (primaryField)
|
|
116
|
+
if (primaryField)
|
|
99
117
|
columns.push("".concat(foreignField.field, ".").concat(primaryField));
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
var fieldNames = Object.keys(ctx.meta.collections[foreignField.collection].fields);
|
|
104
|
-
for (var index_1 = 0; index_1 < fieldNames.length; index_1++) {
|
|
105
|
-
var fieldName = fieldNames[index_1];
|
|
106
|
-
columns.push("".concat(foreignField.field, ".").concat(fieldName));
|
|
107
|
-
}
|
|
108
118
|
}
|
|
109
119
|
}
|
|
110
120
|
var foreignColumns = columns.join(",");
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
showHeader?: boolean;
|
|
13
13
|
showFooter?: boolean;
|
|
14
14
|
tableProps?: Partial<TableProps>;
|
|
15
|
+
position?: "side" | "bottom";
|
|
15
16
|
onClose?: () => void;
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -22,20 +23,23 @@
|
|
|
22
23
|
showHeader = true,
|
|
23
24
|
showFooter = true,
|
|
24
25
|
tableProps,
|
|
26
|
+
position = "side",
|
|
25
27
|
onClose,
|
|
26
28
|
}: Props = $props();
|
|
27
29
|
</script>
|
|
28
30
|
|
|
29
|
-
<Drawer onHide={async () => onClose?.()}>
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
<Drawer onHide={async () => onClose?.()} {position}>
|
|
32
|
+
{#if position !== "bottom"}
|
|
33
|
+
<div class="flex h-12 shrink-0 items-center gap-4 border-b px-4">
|
|
34
|
+
<Button
|
|
35
|
+
variant="outline"
|
|
36
|
+
onclick={() => onClose?.()}
|
|
37
|
+
class="h-8 w-8 rounded-full text-xs font-normal"
|
|
38
|
+
Icon={ArrowLeft}
|
|
39
|
+
/>
|
|
40
|
+
<div class="text-sm font-medium">{title ?? collectionName}</div>
|
|
41
|
+
</div>
|
|
42
|
+
{/if}
|
|
39
43
|
<div class="min-h-0 flex-1 overflow-auto">
|
|
40
44
|
<DataTable
|
|
41
45
|
{collectionName}
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
import { getField, getFieldIcon } from "../../dataTable/utils";
|
|
29
29
|
import Children from "./children.svelte";
|
|
30
30
|
import {
|
|
31
|
-
|
|
31
|
+
buildChildren,
|
|
32
32
|
getDefaultEntry,
|
|
33
33
|
parseDetailViewValues,
|
|
34
34
|
serializeEntry,
|
|
@@ -82,31 +82,15 @@
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
const serializedEntry = serializeEntry(
|
|
86
|
-
ctx,
|
|
87
|
-
collectionName,
|
|
88
|
-
localEntry,
|
|
89
|
-
rollback,
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
let transactionBody;
|
|
93
85
|
if (rollback) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
props: { collectionName: collectionName, data: serializedEntry },
|
|
98
|
-
},
|
|
99
|
-
];
|
|
100
|
-
} else {
|
|
101
|
-
transactionBody = generateTransactionBody(
|
|
102
|
-
ctx,
|
|
103
|
-
collectionName,
|
|
104
|
-
serializedEntry,
|
|
105
|
-
);
|
|
86
|
+
if (onSuccessfullSave) await onSuccessfullSave(localEntry);
|
|
87
|
+
onCancel?.();
|
|
88
|
+
return;
|
|
106
89
|
}
|
|
107
90
|
|
|
108
|
-
|
|
109
|
-
|
|
91
|
+
const serializedEntry = serializeEntry(ctx, collectionName, localEntry);
|
|
92
|
+
const children = buildChildren(ctx, collectionName, localEntry);
|
|
93
|
+
const response = await lobb.createOne(collectionName, serializedEntry, children);
|
|
110
94
|
|
|
111
95
|
await emitEvent({ lobb, ctx }, "studio.collections.create", {
|
|
112
96
|
collectionName,
|
|
@@ -126,15 +110,8 @@
|
|
|
126
110
|
}
|
|
127
111
|
}
|
|
128
112
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
await onSuccessfullSave(localEntry);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (!rollback) {
|
|
135
|
-
toast.success(`The record was successfully created`);
|
|
136
|
-
}
|
|
137
|
-
|
|
113
|
+
if (onSuccessfullSave) await onSuccessfullSave(localEntry);
|
|
114
|
+
toast.success(`The record was successfully created`);
|
|
138
115
|
onCancel?.();
|
|
139
116
|
}
|
|
140
117
|
</script>
|
|
@@ -161,46 +138,38 @@
|
|
|
161
138
|
<div class="flex-1 overflow-y-auto">
|
|
162
139
|
<div class="flex flex-col gap-4 p-4">
|
|
163
140
|
{#each fieldNames as fieldName}
|
|
164
|
-
{
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
class="flex flex-col gap-2"
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
>
|
|
177
|
-
<
|
|
178
|
-
|
|
141
|
+
{#if !ctx.meta.collections[collectionName].fields[fieldName]?.ui?.hidden}
|
|
142
|
+
{@const field = getField(ctx, fieldName, collectionName)}
|
|
143
|
+
{@const FieldIcon = getFieldIcon(ctx, fieldName, collectionName)}
|
|
144
|
+
<div class="flex flex-col gap-2">
|
|
145
|
+
<div class="flex flex-1 items-end justify-between gap-2 text-xs">
|
|
146
|
+
<div class="flex gap-2">
|
|
147
|
+
<div class="h-fit">{field.label}</div>
|
|
148
|
+
<div class="flex h-fit items-center gap-1 text-[0.7rem] text-muted-foreground">
|
|
149
|
+
<FieldIcon size="12" />
|
|
150
|
+
{field.type}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
<div>
|
|
154
|
+
<ExtensionsComponents
|
|
155
|
+
name="dvFields.topRight.{collectionName}.{fieldName}"
|
|
156
|
+
utils={getExtensionUtils(lobb, ctx)}
|
|
157
|
+
bind:value={entry[fieldName]}
|
|
158
|
+
/>
|
|
179
159
|
</div>
|
|
180
160
|
</div>
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
161
|
+
<FieldInput
|
|
162
|
+
{collectionName}
|
|
163
|
+
{fieldName}
|
|
164
|
+
bind:value={
|
|
165
|
+
() => entry[fieldName],
|
|
166
|
+
(v) => (entry = { ...entry, [fieldName]: v })
|
|
167
|
+
}
|
|
168
|
+
bind:entry
|
|
169
|
+
errorMessages={fieldsErrors[fieldName]}
|
|
170
|
+
/>
|
|
188
171
|
</div>
|
|
189
|
-
|
|
190
|
-
{collectionName}
|
|
191
|
-
{fieldName}
|
|
192
|
-
bind:value={
|
|
193
|
-
() => entry[fieldName],
|
|
194
|
-
(v) =>
|
|
195
|
-
(entry = {
|
|
196
|
-
...entry,
|
|
197
|
-
[fieldName]: v,
|
|
198
|
-
})
|
|
199
|
-
}
|
|
200
|
-
{entry}
|
|
201
|
-
errorMessages={fieldsErrors[fieldName]}
|
|
202
|
-
/>
|
|
203
|
-
</div>
|
|
172
|
+
{/if}
|
|
204
173
|
{/each}
|
|
205
174
|
</div>
|
|
206
175
|
{#if showRelatedRecords}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getStudioContext } from "../../context";
|
|
3
|
-
import {
|
|
3
|
+
import { getFieldRelationTarget, getPolymorphicRelation } from "../../relations";
|
|
4
4
|
import { Ban, Check, CircleAlert, X } from "lucide-svelte";
|
|
5
5
|
import { getField } from "../dataTable/utils";
|
|
6
6
|
import Button from "../ui/button/button.svelte";
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import type { EnumOption } from "@lobb-js/core";
|
|
12
12
|
import Textarea from "../ui/textarea/textarea.svelte";
|
|
13
13
|
import ForeingKeyInput from "../foreingKeyInput.svelte";
|
|
14
|
+
import PolymorphicInput from "../polymorphicInput.svelte";
|
|
14
15
|
import ExtensionsComponents from "../extensionsComponents.svelte";
|
|
15
16
|
import { getExtensionUtils } from "../../extensions/extensionUtils";
|
|
16
17
|
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
fieldName,
|
|
30
31
|
value = $bindable(),
|
|
31
32
|
errorMessages = [],
|
|
32
|
-
entry,
|
|
33
|
+
entry = $bindable(),
|
|
33
34
|
}: Props = $props();
|
|
34
35
|
|
|
35
36
|
const ui_input =
|
|
@@ -37,10 +38,11 @@
|
|
|
37
38
|
const ui =
|
|
38
39
|
ctx.meta.collections[collectionName].fields[fieldName].ui;
|
|
39
40
|
const field = getField(ctx, fieldName, collectionName);
|
|
40
|
-
const
|
|
41
|
+
const fieldRelationTarget = getFieldRelationTarget(ctx, collectionName, fieldName);
|
|
42
|
+
const polymorphicRelation = getPolymorphicRelation(ctx, collectionName, fieldName);
|
|
41
43
|
const isDisabled = field.key === 'id' || Boolean(ui?.disabled)
|
|
42
44
|
const disabledClasses = "pointer-events-none opacity-50";
|
|
43
|
-
const destructive: boolean = $derived(Boolean(errorMessages.length));
|
|
45
|
+
const destructive: boolean = $derived(!isDisabled && Boolean(errorMessages.length));
|
|
44
46
|
|
|
45
47
|
</script>
|
|
46
48
|
|
|
@@ -53,13 +55,27 @@
|
|
|
53
55
|
style="flex: 2;"
|
|
54
56
|
>
|
|
55
57
|
<Button
|
|
56
|
-
onclick={() =>
|
|
58
|
+
onclick={() => {
|
|
59
|
+
value = null;
|
|
60
|
+
if (polymorphicRelation && entry) {
|
|
61
|
+
entry[polymorphicRelation.from.collection_field] = null;
|
|
62
|
+
entry[polymorphicRelation.from.id_field] = null;
|
|
63
|
+
}
|
|
64
|
+
}}
|
|
57
65
|
variant="outline"
|
|
58
66
|
class="absolute right-0 top-0 z-10 mr-1.5 mt-1.5 aspect-square h-6 w-6 p-0"
|
|
59
67
|
Icon={Ban}
|
|
60
68
|
tabindex={-1}
|
|
61
69
|
></Button>
|
|
62
|
-
{#if
|
|
70
|
+
{#if polymorphicRelation && entry}
|
|
71
|
+
<PolymorphicInput
|
|
72
|
+
collectionField={polymorphicRelation.from.collection_field}
|
|
73
|
+
idField={polymorphicRelation.from.id_field}
|
|
74
|
+
targetCollections={polymorphicRelation.to}
|
|
75
|
+
bind:entry
|
|
76
|
+
{destructive}
|
|
77
|
+
/>
|
|
78
|
+
{:else if ui_input}
|
|
63
79
|
<FieldCustomInput
|
|
64
80
|
bind:value
|
|
65
81
|
type={ui_input.type}
|
|
@@ -73,12 +89,12 @@
|
|
|
73
89
|
class="bg-muted/30 text-xs"
|
|
74
90
|
bind:value
|
|
75
91
|
/>
|
|
76
|
-
{:else if
|
|
92
|
+
{:else if fieldRelationTarget && entry}
|
|
77
93
|
<ExtensionsComponents
|
|
78
|
-
name="detailView.fields.foreignKey.{
|
|
94
|
+
name="detailView.fields.foreignKey.{fieldRelationTarget}"
|
|
79
95
|
utils={getExtensionUtils(lobb, ctx)}
|
|
80
96
|
parentCollectionName={collectionName}
|
|
81
|
-
collectionName={
|
|
97
|
+
collectionName={fieldRelationTarget}
|
|
82
98
|
bind:value
|
|
83
99
|
{entry}
|
|
84
100
|
fieldName={field.key}
|
|
@@ -86,7 +102,7 @@
|
|
|
86
102
|
>
|
|
87
103
|
<ForeingKeyInput
|
|
88
104
|
parentCollectionName={collectionName}
|
|
89
|
-
collectionName={
|
|
105
|
+
collectionName={fieldRelationTarget}
|
|
90
106
|
bind:value
|
|
91
107
|
{entry}
|
|
92
108
|
fieldName={field.key}
|
|
@@ -263,7 +279,7 @@
|
|
|
263
279
|
bind:value
|
|
264
280
|
/>
|
|
265
281
|
{/if}
|
|
266
|
-
{#if errorMessages}
|
|
282
|
+
{#if !isDisabled && errorMessages}
|
|
267
283
|
{#each errorMessages as message}
|
|
268
284
|
<div class="flex gap-1 text-destructive">
|
|
269
285
|
<CircleAlert size="15" class="translate-y-[0.025rem]" />
|
|
@@ -5,6 +5,6 @@ interface Props {
|
|
|
5
5
|
errorMessages?: string[];
|
|
6
6
|
entry?: Record<string, any>;
|
|
7
7
|
}
|
|
8
|
-
declare const FieldInput: import("svelte").Component<Props, {}, "value">;
|
|
8
|
+
declare const FieldInput: import("svelte").Component<Props, {}, "value" | "entry">;
|
|
9
9
|
type FieldInput = ReturnType<typeof FieldInput>;
|
|
10
10
|
export default FieldInput;
|
|
@@ -113,39 +113,35 @@
|
|
|
113
113
|
<div class="flex-1 overflow-y-auto">
|
|
114
114
|
<div class="flex flex-col gap-4 p-4">
|
|
115
115
|
{#each fieldNames as fieldName}
|
|
116
|
-
{
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
class="flex flex-col gap-2"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
>
|
|
129
|
-
<
|
|
130
|
-
|
|
116
|
+
{#if !ctx.meta.collections[collectionName].fields[fieldName]?.ui?.hidden}
|
|
117
|
+
{@const field = getField(ctx, fieldName, collectionName)}
|
|
118
|
+
{@const FieldIcon = getFieldIcon(ctx, fieldName, collectionName)}
|
|
119
|
+
<div class="flex flex-col gap-2">
|
|
120
|
+
<div class="flex flex-1 items-end justify-between gap-2 text-xs">
|
|
121
|
+
<div class="flex gap-2">
|
|
122
|
+
<div class="h-fit">{field.label}</div>
|
|
123
|
+
<div class="flex h-fit items-center gap-1 text-[0.7rem] text-muted-foreground">
|
|
124
|
+
<FieldIcon size="12" />
|
|
125
|
+
{field.type}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
<div>
|
|
129
|
+
<ExtensionsComponents
|
|
130
|
+
name="dvFields.topRight.{collectionName}.{fieldName}"
|
|
131
|
+
utils={getExtensionUtils(lobb, ctx)}
|
|
132
|
+
bind:value={entry[fieldName]}
|
|
133
|
+
/>
|
|
131
134
|
</div>
|
|
132
135
|
</div>
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
<FieldInput
|
|
137
|
+
{collectionName}
|
|
138
|
+
{fieldName}
|
|
139
|
+
bind:value={entry[fieldName]}
|
|
140
|
+
bind:entry
|
|
141
|
+
errorMessages={fieldsErrors[fieldName]}
|
|
142
|
+
/>
|
|
140
143
|
</div>
|
|
141
|
-
|
|
142
|
-
{collectionName}
|
|
143
|
-
{fieldName}
|
|
144
|
-
bind:value={entry[fieldName]}
|
|
145
|
-
{entry}
|
|
146
|
-
errorMessages={fieldsErrors[fieldName]}
|
|
147
|
-
/>
|
|
148
|
-
</div>
|
|
144
|
+
{/if}
|
|
149
145
|
{/each}
|
|
150
146
|
</div>
|
|
151
147
|
{#if showRelatedRecords}
|
|
@@ -3,7 +3,10 @@ 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
|
|
7
|
-
export declare function
|
|
6
|
+
export declare function serializeEntry(ctx: CTX, collectionName: string, entry: Record<string, any>): Record<string, any>;
|
|
7
|
+
export declare function buildChildren(ctx: CTX, collectionName: string, entry: Record<string, any>): Record<string, {
|
|
8
|
+
create?: any[];
|
|
9
|
+
link?: any[];
|
|
10
|
+
}> | undefined;
|
|
8
11
|
export declare function parseDetailViewValues(ctx: CTX, collectionName: string, values: Record<string, any>): void;
|
|
9
12
|
export declare function getCollectionFields(ctx: CTX, collectionName: string): DetailFormField[];
|