@lobb-js/studio 0.28.5 → 0.29.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/Studio.svelte +7 -4
- package/dist/components/combobox.svelte +3 -3
- package/dist/components/confirmationDialog/confirmationDialog.svelte +1 -1
- package/dist/components/dataTable/dataTable.svelte +74 -82
- package/dist/components/dataTable/dataTable.svelte.d.ts +3 -19
- package/dist/components/dataTable/filter.svelte +1 -1
- package/dist/components/dataTable/filterButton.svelte +1 -1
- package/dist/components/dataTable/header.svelte +33 -54
- package/dist/components/dataTable/header.svelte.d.ts +3 -2
- package/dist/components/dataTable/sort.svelte +1 -1
- package/dist/components/dataTable/sortButton.svelte +2 -2
- package/dist/components/detailView/create/children.svelte +1 -1
- package/dist/components/detailView/create/createDetailView.svelte +77 -42
- package/dist/components/detailView/create/createDetailView.svelte.d.ts +2 -2
- package/dist/components/detailView/create/createDetailViewButton.svelte +2 -2
- package/dist/components/detailView/create/createDetailViewButton.svelte.d.ts +1 -1
- package/dist/components/detailView/create/createManyView.svelte +10 -6
- package/dist/components/detailView/fieldInput.svelte +1 -1
- package/dist/components/detailView/fieldInputReplacement.svelte +1 -1
- package/dist/components/detailView/update/detailViewChildren.svelte +15 -26
- package/dist/components/detailView/update/detailViewChildren.svelte.d.ts +3 -8
- package/dist/components/detailView/update/updateDetailView.svelte +85 -27
- package/dist/components/detailView/update/updateDetailView.svelte.d.ts +2 -2
- package/dist/components/detailView/update/updateDetailViewButton.svelte +3 -2
- package/dist/components/detailView/update/updateDetailViewButton.svelte.d.ts +1 -1
- package/dist/components/detailView/utils.d.ts +17 -0
- package/dist/components/miniSidebar.svelte +4 -4
- package/dist/components/rangeCalendarButton.svelte +3 -3
- package/dist/components/routes/collections/collection.svelte +3 -3
- package/dist/components/routes/collections/collections.svelte +2 -2
- package/dist/components/routes/data_model/dataModel.svelte +2 -2
- package/dist/components/routes/data_model/syncManager.svelte +4 -4
- package/dist/components/routes/extensions/extension.svelte +1 -1
- package/dist/components/routes/home.svelte +1 -1
- package/dist/components/routes/workflows/workflows.svelte +5 -5
- package/dist/components/selectRecord.svelte +2 -21
- package/dist/components/setServerPage.svelte +1 -1
- package/dist/components/ui/alert-dialog/alert-dialog-action.svelte +1 -1
- package/dist/components/ui/alert-dialog/alert-dialog-cancel.svelte +1 -1
- package/dist/components/ui/command/command-dialog.svelte +1 -1
- package/dist/components/ui/range-calendar/range-calendar-day.svelte +1 -1
- package/dist/components/ui/range-calendar/range-calendar-next-button.svelte +1 -1
- package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte +1 -1
- package/dist/components/ui/select/select-separator.svelte +1 -1
- package/dist/components/workflowEditor.svelte +3 -3
- package/dist/store.types.d.ts +1 -1
- package/package.json +2 -2
- package/src/lib/components/Studio.svelte +7 -4
- package/src/lib/components/combobox.svelte +3 -3
- package/src/lib/components/confirmationDialog/confirmationDialog.svelte +1 -1
- package/src/lib/components/dataTable/dataTable.svelte +74 -82
- package/src/lib/components/dataTable/filter.svelte +1 -1
- package/src/lib/components/dataTable/filterButton.svelte +1 -1
- package/src/lib/components/dataTable/header.svelte +33 -54
- package/src/lib/components/dataTable/sort.svelte +1 -1
- package/src/lib/components/dataTable/sortButton.svelte +2 -2
- package/src/lib/components/detailView/create/children.svelte +1 -1
- package/src/lib/components/detailView/create/createDetailView.svelte +77 -42
- package/src/lib/components/detailView/create/createDetailViewButton.svelte +2 -2
- package/src/lib/components/detailView/create/createManyView.svelte +10 -6
- package/src/lib/components/detailView/fieldInput.svelte +1 -1
- package/src/lib/components/detailView/fieldInputReplacement.svelte +1 -1
- package/src/lib/components/detailView/update/detailViewChildren.svelte +15 -26
- package/src/lib/components/detailView/update/updateDetailView.svelte +85 -27
- package/src/lib/components/detailView/update/updateDetailViewButton.svelte +3 -2
- package/src/lib/components/detailView/utils.ts +13 -0
- package/src/lib/components/miniSidebar.svelte +4 -4
- package/src/lib/components/rangeCalendarButton.svelte +3 -3
- package/src/lib/components/routes/collections/collection.svelte +3 -3
- package/src/lib/components/routes/collections/collections.svelte +2 -2
- package/src/lib/components/routes/data_model/dataModel.svelte +2 -2
- package/src/lib/components/routes/data_model/syncManager.svelte +4 -4
- package/src/lib/components/routes/extensions/extension.svelte +1 -1
- package/src/lib/components/routes/home.svelte +1 -1
- package/src/lib/components/routes/workflows/workflows.svelte +5 -5
- package/src/lib/components/selectRecord.svelte +2 -21
- package/src/lib/components/setServerPage.svelte +1 -1
- package/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte +1 -1
- package/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte +1 -1
- package/src/lib/components/ui/command/command-dialog.svelte +1 -1
- package/src/lib/components/ui/range-calendar/range-calendar-day.svelte +1 -1
- package/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte +1 -1
- package/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte +1 -1
- package/src/lib/components/ui/select/select-separator.svelte +1 -1
- package/src/lib/components/workflowEditor.svelte +3 -3
- package/src/lib/store.types.ts +1 -1
- package/vite-plugins/index.js +2 -4
- package/vite-plugins/lobb-extensions.js +12 -0
- package/vite-plugins/utils.js +15 -0
- package/vite-plugins/workspace-fs-allow.js +33 -0
- package/vite-plugins/contextual-lib-alias.js +0 -67
- package/vite-plugins/workspace-optimize.js +0 -106
|
@@ -8,93 +8,128 @@
|
|
|
8
8
|
collectionName: string;
|
|
9
9
|
values?: Record<string, any>;
|
|
10
10
|
showRelatedRecords?: boolean;
|
|
11
|
-
rollback?: boolean;
|
|
12
11
|
submitButton?: SubmitButton;
|
|
13
12
|
title?: Snippet<[string]>;
|
|
14
13
|
onSuccessfullSave?: (entry: any) => Promise<void>;
|
|
15
14
|
onCancel?: () => Promise<void>;
|
|
15
|
+
changes?: import("../utils").Changes | undefined;
|
|
16
16
|
}
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
<script lang="ts">
|
|
20
20
|
import { ArrowLeft, Plus, X } from "lucide-svelte";
|
|
21
|
-
import Button from "
|
|
21
|
+
import Button from "../../ui/button/button.svelte";
|
|
22
22
|
import { getStudioContext } from "../../../context";
|
|
23
23
|
import { toast } from "svelte-sonner";
|
|
24
|
+
import { untrack } from "svelte";
|
|
24
25
|
|
|
25
26
|
const { lobb, ctx } = getStudioContext();
|
|
26
27
|
import ExtensionsComponents from "../../extensionsComponents.svelte";
|
|
27
28
|
import { getExtensionUtils } from "../../../extensions/extensionUtils";
|
|
28
29
|
import { getField, getFieldIcon } from "../../dataTable/utils";
|
|
29
30
|
import Children from "./children.svelte";
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
getDefaultEntry,
|
|
33
|
-
} from "../utils";
|
|
31
|
+
import { buildChildren, getDefaultEntry } from "../utils";
|
|
32
|
+
import type { Changes } from "../utils";
|
|
34
33
|
import { getChangedProperties } from "../../../utils";
|
|
35
34
|
import type { Snippet } from "svelte";
|
|
36
35
|
import FieldInput from "../fieldInput.svelte";
|
|
37
|
-
import
|
|
38
|
-
import Drawer from "../../../components/drawer.svelte";
|
|
36
|
+
import Drawer from "../../drawer.svelte";
|
|
39
37
|
|
|
40
38
|
let {
|
|
41
39
|
collectionName,
|
|
42
40
|
values = {},
|
|
43
41
|
showRelatedRecords = true,
|
|
44
|
-
rollback = false,
|
|
45
42
|
onCancel,
|
|
46
43
|
onSuccessfullSave,
|
|
47
44
|
title,
|
|
48
45
|
submitButton,
|
|
46
|
+
changes = $bindable<Changes | undefined>(undefined),
|
|
49
47
|
}: CreateDetailViewProp = $props();
|
|
50
48
|
|
|
49
|
+
let _changes = $state<Changes>({ data: {}, children: {} });
|
|
50
|
+
const isRecordingMode = $derived(changes !== undefined);
|
|
51
|
+
|
|
51
52
|
const fieldNames = Object.keys(ctx.meta.collections[collectionName].fields);
|
|
52
53
|
let entry: Record<string, any> = $state(
|
|
53
54
|
getDefaultEntry(ctx, fieldNames, collectionName, values),
|
|
54
55
|
);
|
|
55
56
|
const initialEntry = $state.snapshot(entry);
|
|
56
57
|
let fieldsErrors: Record<string, any> = $state({});
|
|
57
|
-
|
|
58
|
+
|
|
59
|
+
const childCollections = ctx.meta.relations
|
|
60
|
+
.filter((r) => r.to.collection === collectionName)
|
|
61
|
+
.map((r) => (r as any).from.collection);
|
|
62
|
+
|
|
63
|
+
const subCollections = childCollections;
|
|
58
64
|
const subCollectionsValues: Record<string, any> = {};
|
|
59
|
-
for (
|
|
60
|
-
|
|
61
|
-
if (values[subCollection]) {
|
|
62
|
-
subCollectionsValues[subCollection] = values[subCollection];
|
|
63
|
-
}
|
|
65
|
+
for (const col of subCollections) {
|
|
66
|
+
if (values[col]) subCollectionsValues[col] = values[col];
|
|
64
67
|
}
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
$effect(() => {
|
|
70
|
+
const snap = $state.snapshot(entry);
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
untrack(() => {
|
|
73
|
+
const target = changes ?? _changes;
|
|
74
|
+
const data: Record<string, any> = {};
|
|
75
|
+
const children: Record<string, any> = {};
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
for (const [key, value] of Object.entries(snap)) {
|
|
78
|
+
if (childCollections.includes(key) && Array.isArray(value)) {
|
|
79
|
+
children[key] = {
|
|
80
|
+
created: (value as any[]).filter((r) => !r.id).map((r) => ({ data: r })),
|
|
81
|
+
updated: [],
|
|
82
|
+
deleted: [],
|
|
83
|
+
linked: (value as any[]).filter((r) => r.id).map((r) => r.id),
|
|
84
|
+
unlinked: [],
|
|
85
|
+
};
|
|
86
|
+
} else if (value !== null && value !== undefined && value !== '') {
|
|
87
|
+
data[key] = value;
|
|
88
|
+
}
|
|
78
89
|
}
|
|
79
|
-
}
|
|
80
90
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
target.data = data;
|
|
92
|
+
target.children = children;
|
|
93
|
+
|
|
94
|
+
if (!isRecordingMode) {
|
|
95
|
+
console.log(`[${collectionName}] changes:`, $state.snapshot(target));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
function handleCancel() {
|
|
101
|
+
if (changes !== undefined) {
|
|
102
|
+
changes.data = {};
|
|
103
|
+
changes.children = {};
|
|
85
104
|
}
|
|
105
|
+
onCancel?.();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function handleSave() {
|
|
109
|
+
const target = changes ?? _changes;
|
|
110
|
+
const snap = $state.snapshot(target);
|
|
86
111
|
|
|
87
|
-
const children = buildChildren(ctx, collectionName,
|
|
88
|
-
|
|
112
|
+
const children = buildChildren(ctx, collectionName, { ...snap.data, ...Object.fromEntries(
|
|
113
|
+
Object.entries(snap.children).map(([col, ops]) => [
|
|
114
|
+
col,
|
|
115
|
+
[
|
|
116
|
+
...(ops.created.map((c) => c.data)),
|
|
117
|
+
...(ops.linked.map((id) => ({ id }))),
|
|
118
|
+
]
|
|
119
|
+
])
|
|
120
|
+
)});
|
|
89
121
|
|
|
90
|
-
await
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
122
|
+
const response = await lobb.createOne(collectionName, snap.data, children, undefined, isRecordingMode);
|
|
123
|
+
|
|
124
|
+
if (response.status === 204) {
|
|
125
|
+
if (onSuccessfullSave) await onSuccessfullSave(snap);
|
|
126
|
+
toast.success(`The record was successfully created`);
|
|
127
|
+
handleCancel();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
95
130
|
|
|
96
131
|
if (!response.bodyUsed) {
|
|
97
|
-
|
|
132
|
+
const result = await response.json();
|
|
98
133
|
if (response.status >= 400) {
|
|
99
134
|
if (result.message && result.details) {
|
|
100
135
|
fieldsErrors = result.details;
|
|
@@ -105,17 +140,17 @@
|
|
|
105
140
|
}
|
|
106
141
|
}
|
|
107
142
|
|
|
108
|
-
if (onSuccessfullSave) await onSuccessfullSave(
|
|
143
|
+
if (onSuccessfullSave) await onSuccessfullSave(snap);
|
|
109
144
|
toast.success(`The record was successfully created`);
|
|
110
145
|
onCancel?.();
|
|
111
146
|
}
|
|
112
147
|
</script>
|
|
113
148
|
|
|
114
|
-
<Drawer onHide={
|
|
149
|
+
<Drawer onHide={handleCancel}>
|
|
115
150
|
<div class="flex h-12 items-center gap-4 border-b px-4">
|
|
116
151
|
<Button
|
|
117
152
|
variant="outline"
|
|
118
|
-
onclick={
|
|
153
|
+
onclick={handleCancel}
|
|
119
154
|
class=" h-8 w-8 rounded-full text-xs font-normal"
|
|
120
155
|
Icon={ArrowLeft}
|
|
121
156
|
></Button>
|
|
@@ -175,7 +210,7 @@
|
|
|
175
210
|
<div class="flex gap-3">
|
|
176
211
|
<Button
|
|
177
212
|
variant="outline"
|
|
178
|
-
onclick={
|
|
213
|
+
onclick={handleCancel}
|
|
179
214
|
class="h-7 px-3 text-xs font-normal"
|
|
180
215
|
Icon={X}
|
|
181
216
|
>
|
|
@@ -6,13 +6,13 @@ export interface CreateDetailViewProp {
|
|
|
6
6
|
collectionName: string;
|
|
7
7
|
values?: Record<string, any>;
|
|
8
8
|
showRelatedRecords?: boolean;
|
|
9
|
-
rollback?: boolean;
|
|
10
9
|
submitButton?: SubmitButton;
|
|
11
10
|
title?: Snippet<[string]>;
|
|
12
11
|
onSuccessfullSave?: (entry: any) => Promise<void>;
|
|
13
12
|
onCancel?: () => Promise<void>;
|
|
13
|
+
changes?: import("../utils").Changes | undefined;
|
|
14
14
|
}
|
|
15
15
|
import type { Snippet } from "svelte";
|
|
16
|
-
declare const CreateDetailView: import("svelte").Component<CreateDetailViewProp, {}, "">;
|
|
16
|
+
declare const CreateDetailView: import("svelte").Component<CreateDetailViewProp, {}, "changes">;
|
|
17
17
|
type CreateDetailView = ReturnType<typeof CreateDetailView>;
|
|
18
18
|
export default CreateDetailView;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { CreateDetailViewProp } from "./createDetailView.svelte";
|
|
3
|
-
import type { ButtonProps } from "
|
|
4
|
-
import Button from "
|
|
3
|
+
import type { ButtonProps } from "../../ui/button/button.svelte";
|
|
4
|
+
import Button from "../../ui/button/button.svelte";
|
|
5
5
|
import CreateDetailView from "./createDetailView.svelte";
|
|
6
6
|
|
|
7
7
|
interface LocalProp extends CreateDetailViewProp {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CreateDetailViewProp } from "./createDetailView.svelte";
|
|
2
|
-
import type { ButtonProps } from "
|
|
2
|
+
import type { ButtonProps } from "../../ui/button/button.svelte";
|
|
3
3
|
interface LocalProp extends CreateDetailViewProp {
|
|
4
4
|
variant?: ButtonProps["variant"];
|
|
5
5
|
class?: ButtonProps["class"];
|
|
@@ -11,11 +11,15 @@
|
|
|
11
11
|
import Table, { type TableProps } from "../../dataTable/table.svelte";
|
|
12
12
|
import Button from "../../ui/button/button.svelte";
|
|
13
13
|
import CreateDetailViewButton from "./createDetailViewButton.svelte";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
import type { Changes } from "../utils";
|
|
15
|
+
|
|
16
|
+
let addChanges = $state<Changes>({ data: {}, children: {} });
|
|
17
|
+
let editChanges = $state<Changes>({ data: {}, children: {} });
|
|
18
|
+
import { getCollectionColumns } from "../../dataTable/utils";
|
|
19
|
+
import SelectRecord from "../../selectRecord.svelte";
|
|
20
|
+
import FieldCell from "../../dataTable/fieldCell.svelte";
|
|
17
21
|
import SubRecords from "./subRecords.svelte";
|
|
18
|
-
import ListViewChildren from "
|
|
22
|
+
import ListViewChildren from "../../dataTable/listViewChildren.svelte";
|
|
19
23
|
import { getStudioContext } from "../../../context";
|
|
20
24
|
|
|
21
25
|
const { ctx } = getStudioContext();
|
|
@@ -141,7 +145,7 @@
|
|
|
141
145
|
class="h-7 px-2 font-normal text-xs"
|
|
142
146
|
Icon={Plus}
|
|
143
147
|
{collectionName}
|
|
144
|
-
|
|
148
|
+
changes={addChanges}
|
|
145
149
|
showRelatedRecords={true}
|
|
146
150
|
onSuccessfullSave={onRecordAdd}
|
|
147
151
|
values={createValues}
|
|
@@ -197,7 +201,7 @@
|
|
|
197
201
|
class="h-6 w-6 text-muted-foreground hover:bg-transparent p-0"
|
|
198
202
|
Icon={Pencil}
|
|
199
203
|
{collectionName}
|
|
200
|
-
|
|
204
|
+
changes={editChanges}
|
|
201
205
|
showRelatedRecords={true}
|
|
202
206
|
onSuccessfullSave={(entry) =>
|
|
203
207
|
onRecordOverride(entry, index)}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import Button from "../ui/button/button.svelte";
|
|
7
7
|
import FieldCustomInput from "./fieldCustomInput.svelte";
|
|
8
8
|
import Input from "../ui/input/input.svelte";
|
|
9
|
-
import * as Select from "
|
|
9
|
+
import * as Select from "../ui/select/index";
|
|
10
10
|
import EnumBadge from "../dataTable/enumBadge.svelte";
|
|
11
11
|
import type { EnumOption } from "@lobb-js/core";
|
|
12
12
|
import Textarea from "../ui/textarea/textarea.svelte";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Ban, Check, CircleAlert, X } from "lucide-svelte";
|
|
3
3
|
import Button from "../ui/button/button.svelte";
|
|
4
4
|
import Input from "../ui/input/input.svelte";
|
|
5
|
-
import * as Select from "
|
|
5
|
+
import * as Select from "../ui/select/index";
|
|
6
6
|
import Textarea from "../ui/textarea/textarea.svelte";
|
|
7
7
|
import type { EntryField } from "./detailViewForm.svelte";
|
|
8
8
|
import type { Snippet } from "svelte";
|
|
@@ -1,43 +1,32 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import DataTable
|
|
2
|
+
import DataTable from "../../dataTable/dataTable.svelte";
|
|
3
3
|
import { getStudioContext } from "../../../context";
|
|
4
4
|
import { Table, Link } from "lucide-svelte";
|
|
5
5
|
|
|
6
6
|
const { ctx } = getStudioContext();
|
|
7
7
|
|
|
8
|
-
type
|
|
9
|
-
link?: (string | number)[];
|
|
10
|
-
unlink?: (string | number)[];
|
|
11
|
-
delete?: (string | number)[];
|
|
12
|
-
create?: any[];
|
|
13
|
-
};
|
|
8
|
+
import type { Changes, ChildrenChanges } from "../utils";
|
|
14
9
|
|
|
15
10
|
interface LocalProp {
|
|
16
11
|
collectionName: string;
|
|
17
12
|
entry: any;
|
|
18
|
-
|
|
13
|
+
activeChanges?: Changes;
|
|
19
14
|
}
|
|
20
15
|
|
|
21
|
-
let { collectionName, entry,
|
|
22
|
-
|
|
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
|
-
}
|
|
16
|
+
let { collectionName, entry, activeChanges }: LocalProp = $props();
|
|
38
17
|
|
|
39
18
|
const children = (ctx.meta.collections[collectionName]?.children ?? [])
|
|
40
19
|
.filter((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic");
|
|
20
|
+
|
|
21
|
+
// Ensure all child collection slots exist before the template renders so bind:changes works
|
|
22
|
+
$effect.pre(() => {
|
|
23
|
+
if (!activeChanges) return;
|
|
24
|
+
for (const child of children) {
|
|
25
|
+
if (!activeChanges.children[child.collection]) {
|
|
26
|
+
activeChanges.children[child.collection] = { created: [], updated: [], deleted: [], linked: [], unlinked: [] };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
41
30
|
</script>
|
|
42
31
|
|
|
43
32
|
{#if children.length}
|
|
@@ -52,7 +41,7 @@
|
|
|
52
41
|
collectionName={child.collection}
|
|
53
42
|
searchParams={{ children_of: collectionName, parent_id: entry.id }}
|
|
54
43
|
parentContext={{ collectionName, recordId: entry.id }}
|
|
55
|
-
|
|
44
|
+
bind:changes={activeChanges!.children[child.collection]}
|
|
56
45
|
showImport={false}
|
|
57
46
|
showHeader={true}
|
|
58
47
|
showFooter={true}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
type
|
|
2
|
-
link?: (string | number)[];
|
|
3
|
-
unlink?: (string | number)[];
|
|
4
|
-
delete?: (string | number)[];
|
|
5
|
-
create?: any[];
|
|
6
|
-
};
|
|
1
|
+
import type { Changes } from "../utils";
|
|
7
2
|
interface LocalProp {
|
|
8
3
|
collectionName: string;
|
|
9
4
|
entry: any;
|
|
10
|
-
|
|
5
|
+
activeChanges?: Changes;
|
|
11
6
|
}
|
|
12
|
-
declare const DetailViewChildren: import("svelte").Component<LocalProp, {}, "
|
|
7
|
+
declare const DetailViewChildren: import("svelte").Component<LocalProp, {}, "">;
|
|
13
8
|
type DetailViewChildren = ReturnType<typeof DetailViewChildren>;
|
|
14
9
|
export default DetailViewChildren;
|
|
@@ -9,31 +9,32 @@
|
|
|
9
9
|
recordId: string;
|
|
10
10
|
values?: Record<string, any>;
|
|
11
11
|
showRelatedRecords?: boolean;
|
|
12
|
-
rollback?: boolean;
|
|
13
12
|
submitButton?: SubmitButton;
|
|
14
13
|
title?: Snippet<[string]>;
|
|
15
14
|
onSuccessfullSave?: (entry: any) => Promise<void>;
|
|
16
15
|
onCancel?: () => Promise<void>;
|
|
16
|
+
changes?: import("../utils").Changes | undefined;
|
|
17
17
|
}
|
|
18
18
|
</script>
|
|
19
19
|
|
|
20
20
|
<script lang="ts">
|
|
21
21
|
import { ArrowLeft, Pencil, X } from "lucide-svelte";
|
|
22
|
-
import Button from "
|
|
23
|
-
import { fade, fly } from "svelte/transition";
|
|
22
|
+
import Button from "../../ui/button/button.svelte";
|
|
24
23
|
import { getStudioContext } from "../../../context";
|
|
25
24
|
import { toast } from "svelte-sonner";
|
|
26
25
|
import ExtensionsComponents from "../../extensionsComponents.svelte";
|
|
27
26
|
import { getExtensionUtils } from "../../../extensions/extensionUtils";
|
|
27
|
+
import { untrack } from "svelte";
|
|
28
28
|
|
|
29
29
|
const { lobb, ctx } = getStudioContext();
|
|
30
|
-
import {
|
|
30
|
+
import { getChangedProperties } from "../../../utils";
|
|
31
31
|
import { getField, getFieldIcon } from "../../dataTable/utils";
|
|
32
|
-
import DetailViewChildren from "
|
|
32
|
+
import DetailViewChildren from "./detailViewChildren.svelte";
|
|
33
33
|
import type { Snippet } from "svelte";
|
|
34
34
|
import { getDefaultEntry } from "../utils";
|
|
35
|
+
import type { Changes, ChildrenChanges } from "../utils";
|
|
35
36
|
import FieldInput from "../fieldInput.svelte";
|
|
36
|
-
|
|
37
|
+
import Drawer from "../../drawer.svelte";
|
|
37
38
|
|
|
38
39
|
let {
|
|
39
40
|
collectionName,
|
|
@@ -44,29 +45,74 @@
|
|
|
44
45
|
title,
|
|
45
46
|
submitButton,
|
|
46
47
|
recordId,
|
|
48
|
+
changes = $bindable<Changes | undefined>(undefined),
|
|
47
49
|
}: UpdateDetailViewProp = $props();
|
|
48
50
|
|
|
51
|
+
// Internal changes — used when not in recording mode, passed down to children
|
|
52
|
+
let _changes = $state<Changes>({ data: {}, children: {} });
|
|
53
|
+
|
|
54
|
+
// Recording mode = changes was passed from a parent component
|
|
55
|
+
const isRecordingMode = $derived(changes !== undefined);
|
|
56
|
+
|
|
49
57
|
const fieldNames = Object.keys(ctx.meta.collections[collectionName].fields);
|
|
50
58
|
let entry: Record<string, any> = $state(
|
|
51
59
|
getDefaultEntry(ctx, fieldNames, collectionName, values),
|
|
52
60
|
);
|
|
53
61
|
const initialEntry = $state.snapshot(entry);
|
|
54
|
-
let localEntry = $derived(
|
|
55
|
-
getChangedProperties(initialEntry, $state.snapshot(entry)),
|
|
56
|
-
);
|
|
57
62
|
let fieldsErrors: Record<string, any> = $state({});
|
|
58
|
-
|
|
63
|
+
|
|
64
|
+
// Tracks field edits into the active changes object.
|
|
65
|
+
// Child ops (create/link/unlink/delete) are written directly by DataTable into changes.children.
|
|
66
|
+
$effect(() => {
|
|
67
|
+
const currentEntrySnap = $state.snapshot(entry);
|
|
68
|
+
|
|
69
|
+
untrack(() => {
|
|
70
|
+
const target = changes ?? _changes;
|
|
71
|
+
target.data = getChangedProperties(initialEntry, currentEntrySnap);
|
|
72
|
+
|
|
73
|
+
if (!isRecordingMode) {
|
|
74
|
+
console.log(`[${collectionName}] changes:`, $state.snapshot(target));
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
function buildApiChildren(children: Record<string, ChildrenChanges>): Record<string, any> | undefined {
|
|
80
|
+
const result: Record<string, any> = {};
|
|
81
|
+
for (const [collection, ops] of Object.entries(children)) {
|
|
82
|
+
const hasOps = ops.created.length || ops.deleted.length || ops.linked.length || ops.unlinked.length;
|
|
83
|
+
if (!hasOps) continue;
|
|
84
|
+
result[collection] = {
|
|
85
|
+
...(ops.created.length ? { create: ops.created.map((c) => c.data) } : {}),
|
|
86
|
+
...(ops.deleted.length ? { delete: ops.deleted.map((r) => r.id) } : {}),
|
|
87
|
+
...(ops.linked.length ? { link: ops.linked.map((r) => r.id) } : {}),
|
|
88
|
+
...(ops.unlinked.length ? { unlink: ops.unlinked.map((r) => r.id) } : {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return Object.keys(result).length ? result : undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function handleCancel() {
|
|
95
|
+
if (changes !== undefined) {
|
|
96
|
+
changes.data = {};
|
|
97
|
+
changes.children = {};
|
|
98
|
+
}
|
|
99
|
+
onCancel?.();
|
|
100
|
+
}
|
|
59
101
|
|
|
60
102
|
async function handleSave() {
|
|
61
|
-
|
|
103
|
+
const target = changes ?? _changes;
|
|
104
|
+
const snap = $state.snapshot(target);
|
|
105
|
+
const { id: _id, ...data } = snap.data;
|
|
106
|
+
const children = buildApiChildren(snap.children);
|
|
62
107
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
108
|
+
const response = await lobb.updateOne(collectionName, recordId, data, children, isRecordingMode);
|
|
109
|
+
|
|
110
|
+
if (response.status === 204) {
|
|
111
|
+
if (onSuccessfullSave) await onSuccessfullSave(snap);
|
|
112
|
+
toast.success(`The record was successfully updated`);
|
|
113
|
+
onCancel?.();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
70
116
|
|
|
71
117
|
if (!response.bodyUsed) {
|
|
72
118
|
const result = await response.json();
|
|
@@ -80,22 +126,34 @@
|
|
|
80
126
|
}
|
|
81
127
|
}
|
|
82
128
|
|
|
83
|
-
//
|
|
84
|
-
if (
|
|
85
|
-
|
|
129
|
+
// Real mode: also fire separate update requests for edited children
|
|
130
|
+
if (!isRecordingMode) {
|
|
131
|
+
for (const [collection, ops] of Object.entries(snap.children)) {
|
|
132
|
+
for (const updated of ops.updated) {
|
|
133
|
+
await lobb.updateOne(collection, String(updated.id), updated.data);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
86
136
|
}
|
|
87
137
|
|
|
138
|
+
if (onSuccessfullSave) await onSuccessfullSave(snap);
|
|
88
139
|
toast.success(`The record was successfully updated`);
|
|
89
|
-
|
|
90
140
|
onCancel?.();
|
|
91
141
|
}
|
|
142
|
+
|
|
143
|
+
const activeChanges = $derived(changes ?? _changes);
|
|
144
|
+
const hasChanges = $derived(
|
|
145
|
+
Object.keys(activeChanges.data).length > 0 ||
|
|
146
|
+
Object.values(activeChanges.children).some(
|
|
147
|
+
(c) => c.created.length || c.updated.length || c.deleted.length || c.linked.length || c.unlinked.length,
|
|
148
|
+
),
|
|
149
|
+
);
|
|
92
150
|
</script>
|
|
93
151
|
|
|
94
|
-
<Drawer onHide={
|
|
152
|
+
<Drawer onHide={handleCancel}>
|
|
95
153
|
<div class="flex h-12 items-center gap-4 border-b px-4">
|
|
96
154
|
<Button
|
|
97
155
|
variant="outline"
|
|
98
|
-
onclick={
|
|
156
|
+
onclick={handleCancel}
|
|
99
157
|
class=" h-8 w-8 rounded-full text-xs font-normal"
|
|
100
158
|
Icon={ArrowLeft}
|
|
101
159
|
></Button>
|
|
@@ -145,14 +203,14 @@
|
|
|
145
203
|
{/each}
|
|
146
204
|
</div>
|
|
147
205
|
{#if showRelatedRecords}
|
|
148
|
-
<DetailViewChildren {collectionName} {entry}
|
|
206
|
+
<DetailViewChildren {collectionName} {entry} {activeChanges} />
|
|
149
207
|
{/if}
|
|
150
208
|
</div>
|
|
151
209
|
<div class="flex h-12 items-center justify-end gap-2 border-t px-4">
|
|
152
210
|
<div class="flex gap-3">
|
|
153
211
|
<Button
|
|
154
212
|
variant="outline"
|
|
155
|
-
onclick={
|
|
213
|
+
onclick={handleCancel}
|
|
156
214
|
class="h-7 px-3 text-xs font-normal"
|
|
157
215
|
Icon={X}
|
|
158
216
|
>
|
|
@@ -163,7 +221,7 @@
|
|
|
163
221
|
class="h-7 px-3 text-xs font-normal"
|
|
164
222
|
Icon={submitButton?.icon ? submitButton.icon : Pencil}
|
|
165
223
|
onclick={handleSave}
|
|
166
|
-
disabled={!
|
|
224
|
+
disabled={!hasChanges}
|
|
167
225
|
>
|
|
168
226
|
{submitButton?.text ? submitButton.text : "Update"}
|
|
169
227
|
</Button>
|
|
@@ -7,13 +7,13 @@ export interface UpdateDetailViewProp {
|
|
|
7
7
|
recordId: string;
|
|
8
8
|
values?: Record<string, any>;
|
|
9
9
|
showRelatedRecords?: boolean;
|
|
10
|
-
rollback?: boolean;
|
|
11
10
|
submitButton?: SubmitButton;
|
|
12
11
|
title?: Snippet<[string]>;
|
|
13
12
|
onSuccessfullSave?: (entry: any) => Promise<void>;
|
|
14
13
|
onCancel?: () => Promise<void>;
|
|
14
|
+
changes?: import("../utils").Changes | undefined;
|
|
15
15
|
}
|
|
16
16
|
import type { Snippet } from "svelte";
|
|
17
|
-
declare const UpdateDetailView: import("svelte").Component<UpdateDetailViewProp, {}, "">;
|
|
17
|
+
declare const UpdateDetailView: import("svelte").Component<UpdateDetailViewProp, {}, "changes">;
|
|
18
18
|
type UpdateDetailView = ReturnType<typeof UpdateDetailView>;
|
|
19
19
|
export default UpdateDetailView;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { UpdateDetailViewProp } from "./updateDetailView.svelte";
|
|
3
|
-
import type { ButtonProps } from "
|
|
4
|
-
import Button from "
|
|
3
|
+
import type { ButtonProps } from "../../ui/button/button.svelte";
|
|
4
|
+
import Button from "../../ui/button/button.svelte";
|
|
5
5
|
import UpdateDetailView from "./updateDetailView.svelte";
|
|
6
6
|
import { getStudioContext } from "../../../context";
|
|
7
7
|
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
<UpdateDetailView
|
|
47
47
|
{...props}
|
|
48
48
|
{values}
|
|
49
|
+
changes={props.changes}
|
|
49
50
|
onCancel={async () => {
|
|
50
51
|
open = false;
|
|
51
52
|
values = undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { UpdateDetailViewProp } from "./updateDetailView.svelte";
|
|
2
|
-
import type { ButtonProps } from "
|
|
2
|
+
import type { ButtonProps } from "../../ui/button/button.svelte";
|
|
3
3
|
interface LocalProp extends UpdateDetailViewProp {
|
|
4
4
|
variant?: ButtonProps["variant"];
|
|
5
5
|
class?: ButtonProps["class"];
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import type { CTX } from "../../store.types";
|
|
2
2
|
import type { DetailFormField } from "./detailViewForm.svelte";
|
|
3
|
+
export type ChildrenChanges = {
|
|
4
|
+
created: Array<{
|
|
5
|
+
data: Record<string, any>;
|
|
6
|
+
}>;
|
|
7
|
+
updated: Array<{
|
|
8
|
+
id: string | number;
|
|
9
|
+
data: Record<string, any>;
|
|
10
|
+
children: Record<string, ChildrenChanges>;
|
|
11
|
+
}>;
|
|
12
|
+
deleted: Array<Record<string, any>>;
|
|
13
|
+
linked: Array<Record<string, any>>;
|
|
14
|
+
unlinked: Array<Record<string, any>>;
|
|
15
|
+
};
|
|
16
|
+
export type Changes = {
|
|
17
|
+
data: Record<string, any>;
|
|
18
|
+
children: Record<string, ChildrenChanges>;
|
|
19
|
+
};
|
|
3
20
|
export declare function getDefaultEntry(ctx: CTX, fieldNames: string[], collectionName: string, values?: Record<string, any>): {
|
|
4
21
|
[k: string]: any;
|
|
5
22
|
};
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
|
|
13
13
|
<script lang="ts">
|
|
14
14
|
import { House, Layers, Library, Workflow, X } from "lucide-svelte";
|
|
15
|
-
import Button from "
|
|
16
|
-
import Separator from "
|
|
17
|
-
import * as Tooltip from "
|
|
18
|
-
import * as Accordion from "
|
|
15
|
+
import Button from "./ui/button/button.svelte";
|
|
16
|
+
import Separator from "./ui/separator/separator.svelte";
|
|
17
|
+
import * as Tooltip from "./ui/tooltip";
|
|
18
|
+
import * as Accordion from "./ui/accordion/index.js";
|
|
19
19
|
|
|
20
20
|
import { getStudioContext } from "../context";
|
|
21
21
|
import { getDashboardNavs } from "../extensions/extensionUtils";
|