@lobb-js/studio 0.7.1 → 0.7.3

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.
Files changed (162) hide show
  1. package/package.json +3 -2
  2. package/src/App.svelte +5 -0
  3. package/src/app.css +124 -0
  4. package/src/lib/components/LlmButton.svelte +137 -0
  5. package/src/lib/components/Studio.svelte +129 -0
  6. package/src/lib/components/alertView.svelte +20 -0
  7. package/src/lib/components/breadCrumbs.svelte +60 -0
  8. package/src/lib/components/codeEditor.svelte +152 -0
  9. package/src/lib/components/combobox.svelte +92 -0
  10. package/src/lib/components/confirmationDialog/confirmationDialog.svelte +33 -0
  11. package/src/lib/components/confirmationDialog/store.svelte.ts +28 -0
  12. package/src/lib/components/createManyButton.svelte +109 -0
  13. package/src/lib/components/dataTable/childRecords.svelte +142 -0
  14. package/src/lib/components/dataTable/dataTable.svelte +225 -0
  15. package/src/lib/components/dataTable/fieldCell.svelte +77 -0
  16. package/src/lib/components/dataTable/filter.svelte +284 -0
  17. package/src/lib/components/dataTable/filterButton.svelte +39 -0
  18. package/src/lib/components/dataTable/footer.svelte +84 -0
  19. package/src/lib/components/dataTable/header.svelte +155 -0
  20. package/src/lib/components/dataTable/sort.svelte +173 -0
  21. package/src/lib/components/dataTable/sortButton.svelte +36 -0
  22. package/src/lib/components/dataTable/table.svelte +337 -0
  23. package/src/lib/components/dataTable/utils.ts +127 -0
  24. package/src/lib/components/detailView/create/children.svelte +70 -0
  25. package/src/lib/components/detailView/create/createDetailView.svelte +228 -0
  26. package/src/lib/components/detailView/create/createDetailViewButton.svelte +37 -0
  27. package/src/lib/components/detailView/create/createManyView.svelte +252 -0
  28. package/src/lib/components/detailView/create/subRecords.svelte +50 -0
  29. package/src/lib/components/detailView/detailViewForm.svelte +104 -0
  30. package/src/lib/components/detailView/fieldCustomInput.svelte +26 -0
  31. package/src/lib/components/detailView/fieldInput.svelte +258 -0
  32. package/src/lib/components/detailView/fieldInputReplacement.svelte +199 -0
  33. package/src/lib/components/detailView/store.svelte.ts +59 -0
  34. package/src/lib/components/detailView/update/children.svelte +96 -0
  35. package/src/lib/components/detailView/update/updateDetailView.svelte +176 -0
  36. package/src/lib/components/detailView/update/updateDetailViewButton.svelte +56 -0
  37. package/src/lib/components/detailView/utils.ts +176 -0
  38. package/src/lib/components/diffViewer.svelte +105 -0
  39. package/src/lib/components/drawer.svelte +28 -0
  40. package/src/lib/components/extensionsComponents.svelte +33 -0
  41. package/src/lib/components/foreingKeyInput.svelte +80 -0
  42. package/src/lib/components/header.svelte +45 -0
  43. package/src/lib/components/loadingTypesForMonacoEditor.ts +36 -0
  44. package/src/lib/components/miniSidebar.svelte +226 -0
  45. package/src/lib/components/rangeCalendarButton.svelte +257 -0
  46. package/src/lib/components/richTextEditor.svelte +284 -0
  47. package/src/lib/components/routes/collections/collection.svelte +57 -0
  48. package/src/lib/components/routes/collections/collections.svelte +45 -0
  49. package/src/lib/components/routes/data_model/dataModel.svelte +40 -0
  50. package/src/lib/components/routes/data_model/flow.css +22 -0
  51. package/src/lib/components/routes/data_model/flow.svelte +84 -0
  52. package/src/lib/components/routes/data_model/syncManager.svelte +94 -0
  53. package/src/lib/components/routes/data_model/utils.ts +35 -0
  54. package/src/lib/components/routes/extensions/extension.svelte +19 -0
  55. package/src/lib/components/routes/home.svelte +40 -0
  56. package/src/lib/components/routes/workflows/workflows.svelte +136 -0
  57. package/src/lib/components/selectRecord.svelte +130 -0
  58. package/src/lib/components/setServerPage.svelte +50 -0
  59. package/src/lib/components/sidebar/index.ts +4 -0
  60. package/src/lib/components/sidebar/sidebar.svelte +149 -0
  61. package/src/lib/components/sidebar/sidebarElements.svelte +144 -0
  62. package/src/lib/components/sidebar/sidebarTrigger.svelte +33 -0
  63. package/src/lib/components/singletone.svelte +71 -0
  64. package/src/lib/components/ui/accordion/accordion-content.svelte +22 -0
  65. package/src/lib/components/ui/accordion/accordion-item.svelte +12 -0
  66. package/src/lib/components/ui/accordion/accordion-trigger.svelte +31 -0
  67. package/src/lib/components/ui/accordion/index.ts +17 -0
  68. package/src/lib/components/ui/alert/alert-description.svelte +16 -0
  69. package/src/lib/components/ui/alert/alert-title.svelte +24 -0
  70. package/src/lib/components/ui/alert/alert.svelte +39 -0
  71. package/src/lib/components/ui/alert/index.ts +14 -0
  72. package/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte +13 -0
  73. package/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte +17 -0
  74. package/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte +26 -0
  75. package/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte +16 -0
  76. package/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte +20 -0
  77. package/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte +20 -0
  78. package/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte +19 -0
  79. package/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte +18 -0
  80. package/src/lib/components/ui/alert-dialog/index.ts +40 -0
  81. package/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +23 -0
  82. package/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte +16 -0
  83. package/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte +31 -0
  84. package/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte +23 -0
  85. package/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte +23 -0
  86. package/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte +27 -0
  87. package/src/lib/components/ui/breadcrumb/breadcrumb.svelte +15 -0
  88. package/src/lib/components/ui/breadcrumb/index.ts +25 -0
  89. package/src/lib/components/ui/button/button.svelte +110 -0
  90. package/src/lib/components/ui/button/index.ts +17 -0
  91. package/src/lib/components/ui/checkbox/checkbox.svelte +35 -0
  92. package/src/lib/components/ui/checkbox/index.ts +6 -0
  93. package/src/lib/components/ui/command/command-dialog.svelte +35 -0
  94. package/src/lib/components/ui/command/command-empty.svelte +12 -0
  95. package/src/lib/components/ui/command/command-group.svelte +31 -0
  96. package/src/lib/components/ui/command/command-input.svelte +25 -0
  97. package/src/lib/components/ui/command/command-item.svelte +19 -0
  98. package/src/lib/components/ui/command/command-link-item.svelte +19 -0
  99. package/src/lib/components/ui/command/command-list.svelte +16 -0
  100. package/src/lib/components/ui/command/command-separator.svelte +12 -0
  101. package/src/lib/components/ui/command/command-shortcut.svelte +20 -0
  102. package/src/lib/components/ui/command/command.svelte +21 -0
  103. package/src/lib/components/ui/command/index.ts +40 -0
  104. package/src/lib/components/ui/dialog/dialog-content.svelte +38 -0
  105. package/src/lib/components/ui/dialog/dialog-description.svelte +16 -0
  106. package/src/lib/components/ui/dialog/dialog-footer.svelte +20 -0
  107. package/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
  108. package/src/lib/components/ui/dialog/dialog-overlay.svelte +19 -0
  109. package/src/lib/components/ui/dialog/dialog-title.svelte +16 -0
  110. package/src/lib/components/ui/dialog/index.ts +37 -0
  111. package/src/lib/components/ui/input/index.ts +7 -0
  112. package/src/lib/components/ui/input/input.svelte +46 -0
  113. package/src/lib/components/ui/label/index.ts +7 -0
  114. package/src/lib/components/ui/label/label.svelte +19 -0
  115. package/src/lib/components/ui/popover/index.ts +17 -0
  116. package/src/lib/components/ui/popover/popover-content.svelte +28 -0
  117. package/src/lib/components/ui/range-calendar/index.ts +30 -0
  118. package/src/lib/components/ui/range-calendar/range-calendar-cell.svelte +19 -0
  119. package/src/lib/components/ui/range-calendar/range-calendar-day.svelte +35 -0
  120. package/src/lib/components/ui/range-calendar/range-calendar-grid-body.svelte +12 -0
  121. package/src/lib/components/ui/range-calendar/range-calendar-grid-head.svelte +12 -0
  122. package/src/lib/components/ui/range-calendar/range-calendar-grid-row.svelte +12 -0
  123. package/src/lib/components/ui/range-calendar/range-calendar-grid.svelte +16 -0
  124. package/src/lib/components/ui/range-calendar/range-calendar-head-cell.svelte +16 -0
  125. package/src/lib/components/ui/range-calendar/range-calendar-header.svelte +16 -0
  126. package/src/lib/components/ui/range-calendar/range-calendar-heading.svelte +16 -0
  127. package/src/lib/components/ui/range-calendar/range-calendar-months.svelte +20 -0
  128. package/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte +27 -0
  129. package/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte +27 -0
  130. package/src/lib/components/ui/range-calendar/range-calendar.svelte +57 -0
  131. package/src/lib/components/ui/select/index.ts +34 -0
  132. package/src/lib/components/ui/select/select-content.svelte +38 -0
  133. package/src/lib/components/ui/select/select-group-heading.svelte +16 -0
  134. package/src/lib/components/ui/select/select-item.svelte +37 -0
  135. package/src/lib/components/ui/select/select-scroll-down-button.svelte +19 -0
  136. package/src/lib/components/ui/select/select-scroll-up-button.svelte +19 -0
  137. package/src/lib/components/ui/select/select-separator.svelte +13 -0
  138. package/src/lib/components/ui/select/select-trigger.svelte +24 -0
  139. package/src/lib/components/ui/separator/index.ts +7 -0
  140. package/src/lib/components/ui/separator/separator.svelte +22 -0
  141. package/src/lib/components/ui/skeleton/index.ts +7 -0
  142. package/src/lib/components/ui/skeleton/skeleton.svelte +22 -0
  143. package/src/lib/components/ui/sonner/index.ts +1 -0
  144. package/src/lib/components/ui/sonner/sonner.svelte +20 -0
  145. package/src/lib/components/ui/switch/index.ts +7 -0
  146. package/src/lib/components/ui/switch/switch.svelte +27 -0
  147. package/src/lib/components/ui/textarea/index.ts +7 -0
  148. package/src/lib/components/ui/textarea/textarea.svelte +22 -0
  149. package/src/lib/components/ui/tooltip/index.ts +18 -0
  150. package/src/lib/components/ui/tooltip/tooltip-content.svelte +21 -0
  151. package/src/lib/components/workflowEditor.svelte +188 -0
  152. package/src/lib/context.ts +22 -0
  153. package/src/lib/eventSystem.ts +40 -0
  154. package/src/lib/extensions/extension.types.ts +92 -0
  155. package/src/lib/extensions/extensionUtils.ts +156 -0
  156. package/src/lib/index.ts +24 -0
  157. package/src/lib/store.svelte.ts +13 -0
  158. package/src/lib/store.types.ts +28 -0
  159. package/src/lib/utils.ts +68 -0
  160. package/src/main.ts +18 -0
  161. package/src/stories/detailView/detailViewForm.stories.svelte +79 -0
  162. package/src/vite-env.d.ts +2 -0
@@ -0,0 +1,70 @@
1
+ <script lang="ts">
2
+ import { getStudioContext } from "../../../context";
3
+ import { Link } from "lucide-svelte";
4
+ import CreateManyView from "./createManyView.svelte";
5
+ import ExtensionsComponents from "../../../components/extensionsComponents.svelte";
6
+ import { getExtensionUtils } from "../../../extensions/extensionUtils";
7
+
8
+ const { ctx, lobb } = getStudioContext();
9
+
10
+ interface LocalProp {
11
+ collectionName: string;
12
+ entry: any;
13
+ values?: Record<string, any>;
14
+ }
15
+
16
+ let { collectionName, entry = $bindable(), values = {} }: LocalProp = $props();
17
+
18
+ const childrenRelations = ctx.meta.relations.filter(
19
+ (relation) => relation.to.collection === collectionName,
20
+ );
21
+
22
+ // filling the children properties
23
+ for (let index = 0; index < childrenRelations.length; index++) {
24
+ const relation = childrenRelations[index];
25
+ const childCollection = relation.from.collection;
26
+ entry[childCollection] = [];
27
+ if (values[childCollection]) {
28
+ entry[childCollection] = [
29
+ ...entry[childCollection],
30
+ ...values[childCollection],
31
+ ];
32
+ }
33
+ }
34
+ </script>
35
+
36
+ {#if childrenRelations.length}
37
+ <div class="flex flex-col gap-4 border-t p-4">
38
+ <div class="flex items-center gap-2">
39
+ <Link size="17.5" />
40
+ <div>Sub Records</div>
41
+ </div>
42
+ <div class="flex flex-col gap-4">
43
+ {#each childrenRelations as relation}
44
+ {@const childCollection = relation.from.collection}
45
+ <ExtensionsComponents
46
+ name="detailView.create.subRecords.{childCollection}"
47
+ utils={getExtensionUtils(lobb, ctx)}
48
+ parentCollectionName={collectionName}
49
+ collectionName={childCollection}
50
+ parentRecord={{
51
+ id: entry.id,
52
+ collectionName: collectionName,
53
+ }}
54
+ class="bg-muted/30 border rounded-md overflow-hidden"
55
+ bind:value={entry[childCollection]}
56
+ >
57
+ <CreateManyView
58
+ parentCollectionName={collectionName}
59
+ collectionName={childCollection}
60
+ parentRecord={{
61
+ id: entry.id,
62
+ collectionName: collectionName,
63
+ }}
64
+ bind:entries={entry[childCollection]}
65
+ />
66
+ </ExtensionsComponents>
67
+ {/each}
68
+ </div>
69
+ </div>
70
+ {/if}
@@ -0,0 +1,228 @@
1
+ <script lang="ts" module>
2
+ interface SubmitButton {
3
+ text: string;
4
+ icon: any;
5
+ }
6
+
7
+ export interface CreateDetailViewProp {
8
+ collectionName: string;
9
+ values?: Record<string, any>;
10
+ showRelatedRecords?: boolean;
11
+ rollback?: boolean;
12
+ submitButton?: SubmitButton;
13
+ title?: Snippet<[string]>;
14
+ onSuccessfullSave?: (entry: any) => Promise<void>;
15
+ onCancel?: () => Promise<void>;
16
+ }
17
+ </script>
18
+
19
+ <script lang="ts">
20
+ import { ArrowLeft, Plus, X } from "lucide-svelte";
21
+ import Button from "../../../components/ui/button/button.svelte";
22
+ import { getStudioContext } from "../../../context";
23
+ import { toast } from "svelte-sonner";
24
+
25
+ const { lobb, ctx } = getStudioContext();
26
+ import ExtensionsComponents from "../../extensionsComponents.svelte";
27
+ import { getExtensionUtils } from "../../../extensions/extensionUtils";
28
+ import { getField, getFieldIcon } from "../../dataTable/utils";
29
+ import Children from "./children.svelte";
30
+ import {
31
+ generateTransactionBody,
32
+ getDefaultEntry,
33
+ parseDetailViewValues,
34
+ serializeEntry,
35
+ } from "../utils";
36
+ import type { Snippet } from "svelte";
37
+ import FieldInput from "../fieldInput.svelte";
38
+ import { emitEvent } from "../../../eventSystem";
39
+ import Drawer from "../../../components/drawer.svelte";
40
+
41
+ let {
42
+ collectionName,
43
+ values = {},
44
+ showRelatedRecords = true,
45
+ rollback = false,
46
+ onCancel,
47
+ onSuccessfullSave,
48
+ title,
49
+ submitButton,
50
+ }: CreateDetailViewProp = $props();
51
+
52
+ parseDetailViewValues(ctx, collectionName, values)
53
+
54
+ const fieldNames = Object.keys(ctx.meta.collections[collectionName].fields);
55
+ let entry: Record<string, any> = $state(
56
+ getDefaultEntry(ctx, fieldNames, collectionName, values),
57
+ );
58
+ let fieldsErrors: Record<string, any> = $state({});
59
+ const subCollections = ctx.meta.relations.filter((relation) => relation.to.collection === collectionName).map((relation) => relation.from.collection);
60
+ const subCollectionsValues: Record<string, any> = {};
61
+ for (let index = 0; index < subCollections.length; index++) {
62
+ const subCollection = subCollections[index];
63
+ if (values[subCollection]) {
64
+ subCollectionsValues[subCollection] = values[subCollection];
65
+ }
66
+ }
67
+
68
+ async function handleSave() {
69
+ let localEntry = $state.snapshot(entry);
70
+
71
+ await emitEvent({ lobb, ctx }, "studio.collections.preCreate", {
72
+ collectionName,
73
+ entry: localEntry,
74
+ });
75
+
76
+ // remove empty children records data
77
+ for (const [key, value] of Object.entries(localEntry)) {
78
+ if (Array.isArray(value) && !value.length) {
79
+ delete localEntry[key];
80
+ }
81
+ }
82
+
83
+ const serializedEntry = serializeEntry(
84
+ ctx,
85
+ collectionName,
86
+ localEntry,
87
+ rollback,
88
+ );
89
+
90
+ let transactionBody;
91
+ if (rollback) {
92
+ transactionBody = [
93
+ {
94
+ method: "createOne",
95
+ props: { collectionName: collectionName, data: serializedEntry },
96
+ },
97
+ ];
98
+ } else {
99
+ transactionBody = generateTransactionBody(
100
+ ctx,
101
+ collectionName,
102
+ serializedEntry,
103
+ );
104
+ }
105
+
106
+ // create the record
107
+ let response = await lobb.transactions(transactionBody, rollback);
108
+
109
+ await emitEvent({ lobb, ctx }, "studio.collections.create", {
110
+ collectionName,
111
+ entry: localEntry,
112
+ response: response,
113
+ });
114
+
115
+ if (!response.bodyUsed) {
116
+ let result = await response.json();
117
+ if (response.status >= 400) {
118
+ if (result.message && result.details) {
119
+ fieldsErrors = result.details;
120
+ return;
121
+ } else if (result.message) {
122
+ return;
123
+ }
124
+ }
125
+ }
126
+
127
+ // close detailView side bar
128
+ if (onSuccessfullSave) {
129
+ await onSuccessfullSave(localEntry);
130
+ }
131
+
132
+ if (!rollback) {
133
+ toast.success(`The record was successfully created`);
134
+ }
135
+
136
+ onCancel?.();
137
+ }
138
+ </script>
139
+
140
+ <Drawer onHide={onCancel}>
141
+ <div class="flex h-12 items-center gap-4 border-b px-4">
142
+ <Button
143
+ variant="outline"
144
+ onclick={onCancel}
145
+ class=" h-8 w-8 rounded-full text-xs font-normal"
146
+ Icon={ArrowLeft}
147
+ ></Button>
148
+ <div class="flex items-center gap-2 text-sm">
149
+ {#if title}
150
+ {@render title(collectionName)}
151
+ {:else}
152
+ <div>Create new record to</div>
153
+ <div class="rounded-md border bg-muted px-2 py-0.5">
154
+ {collectionName}
155
+ </div>
156
+ {/if}
157
+ </div>
158
+ </div>
159
+ <div class="flex-1 overflow-y-auto">
160
+ <div class="flex flex-col gap-4 p-4">
161
+ {#each fieldNames as fieldName}
162
+ {@const field = getField(ctx, fieldName, collectionName)}
163
+ {@const FieldIcon = getFieldIcon(ctx, fieldName, collectionName)}
164
+ <div
165
+ class="flex flex-col gap-2"
166
+ >
167
+ <div
168
+ class="flex flex-1 items-end justify-between gap-2 text-xs"
169
+ >
170
+ <div class="flex gap-2">
171
+ <div class="h-fit">{field.label}</div>
172
+ <div
173
+ class="flex h-fit items-center gap-1 text-[0.7rem] text-muted-foreground"
174
+ >
175
+ <FieldIcon size="12" />
176
+ {field.type}
177
+ </div>
178
+ </div>
179
+ <div>
180
+ <ExtensionsComponents
181
+ name="dvFields.topRight.{collectionName}.{fieldName}"
182
+ utils={getExtensionUtils(lobb, ctx)}
183
+ bind:value={entry[fieldName]}
184
+ />
185
+ </div>
186
+ </div>
187
+ <FieldInput
188
+ {collectionName}
189
+ {fieldName}
190
+ bind:value={
191
+ () => entry[fieldName],
192
+ (v) =>
193
+ (entry = {
194
+ ...entry,
195
+ [fieldName]: v,
196
+ })
197
+ }
198
+ {entry}
199
+ errorMessages={fieldsErrors[fieldName]}
200
+ />
201
+ </div>
202
+ {/each}
203
+ </div>
204
+ {#if showRelatedRecords}
205
+ <Children {collectionName} values={subCollectionsValues} bind:entry />
206
+ {/if}
207
+ </div>
208
+ <div class="flex h-12 items-center justify-end gap-2 border-t px-4">
209
+ <div class="flex gap-3">
210
+ <Button
211
+ variant="outline"
212
+ onclick={onCancel}
213
+ class="h-7 px-3 text-xs font-normal"
214
+ Icon={X}
215
+ >
216
+ Cancel
217
+ </Button>
218
+ <Button
219
+ variant="default"
220
+ class="h-7 px-3 text-xs font-normal"
221
+ Icon={submitButton?.icon ? submitButton.icon : Plus}
222
+ onclick={handleSave}
223
+ >
224
+ {submitButton?.text ? submitButton.text : "Create"}
225
+ </Button>
226
+ </div>
227
+ </div>
228
+ </Drawer>
@@ -0,0 +1,37 @@
1
+ <script lang="ts">
2
+ import type { CreateDetailViewProp } from "./createDetailView.svelte";
3
+ import type { ButtonProps } from "../../../components/ui/button/button.svelte";
4
+ import Button from "../../../components/ui/button/button.svelte";
5
+ import CreateDetailView from "./createDetailView.svelte";
6
+
7
+ interface LocalProp extends CreateDetailViewProp {
8
+ variant?: ButtonProps["variant"];
9
+ class?: ButtonProps["class"];
10
+ Icon?: ButtonProps["Icon"];
11
+ children?: ButtonProps["children"];
12
+ }
13
+
14
+ let props: LocalProp = $props();
15
+ let open = $state(false);
16
+ </script>
17
+
18
+ <Button
19
+ variant={props.variant}
20
+ class={props.class}
21
+ Icon={props.Icon}
22
+ onclick={() => { open = true; }}
23
+ >
24
+ {#if props.children}
25
+ {@render props.children()}
26
+ {/if}
27
+ </Button>
28
+
29
+ {#if open}
30
+ <CreateDetailView
31
+ {...props}
32
+ onCancel={async () => {
33
+ open = false;
34
+ await props.onCancel?.();
35
+ }}
36
+ />
37
+ {/if}
@@ -0,0 +1,252 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import {
4
+ ChevronRight,
5
+ Pencil,
6
+ Plus,
7
+ Replace,
8
+ Table as TableIcon,
9
+ X,
10
+ } from "lucide-svelte";
11
+ import Table, { type TableProps } from "../../dataTable/table.svelte";
12
+ import Button from "../../ui/button/button.svelte";
13
+ import CreateDetailViewButton from "./createDetailViewButton.svelte";
14
+ import { getCollectionColumns } from "../../../components/dataTable/utils";
15
+ import SelectRecord from "../../../components/selectRecord.svelte";
16
+ import FieldCell from "../../../components/dataTable/fieldCell.svelte";
17
+ import SubRecords from "./subRecords.svelte";
18
+ import ChildRecords from "../../../components/dataTable/childRecords.svelte";
19
+ import { getStudioContext } from "../../../context";
20
+
21
+ const { ctx } = getStudioContext();
22
+
23
+ interface ParentRecord {
24
+ id: string;
25
+ collectionName: string;
26
+ }
27
+
28
+ interface LocalProp {
29
+ parentCollectionName: string;
30
+ collectionName: string;
31
+ entries: any[];
32
+ parentRecord?: ParentRecord;
33
+ class?: HTMLAttributes<HTMLDivElement>["class"];
34
+ expanded?: boolean;
35
+ }
36
+
37
+ let {
38
+ parentCollectionName,
39
+ collectionName,
40
+ entries = $bindable(),
41
+ class: className,
42
+ expanded = true,
43
+ parentRecord,
44
+ }: LocalProp = $props();
45
+
46
+ let tableWidth: number = $state(0);
47
+ const doesCollectionHasChildren = Boolean(
48
+ ctx.meta.relations.find(
49
+ (relation) => relation.to.collection === collectionName,
50
+ ),
51
+ );
52
+ const columns: TableProps["columns"] = getCollectionColumns(ctx, collectionName);
53
+ const refrenceFieldName = ctx.meta.relations.find(
54
+ (relation) =>
55
+ relation.from.collection === collectionName &&
56
+ relation.to.collection === parentRecord?.collectionName,
57
+ )?.from.field;
58
+ const createValues = {
59
+ [refrenceFieldName]: {
60
+ id: 0,
61
+ },
62
+ };
63
+ let selectedRecordsIds: string[] = $derived(
64
+ entries.filter((entry) => entry.id).map((entry) => entry.id),
65
+ );
66
+ let selectRecordFilter: Record<string, any> = $state({});
67
+
68
+ $effect(() => {
69
+ if (selectedRecordsIds.length) {
70
+ selectRecordFilter = {
71
+ id: {
72
+ $nin: selectedRecordsIds,
73
+ },
74
+ };
75
+ } else {
76
+ selectRecordFilter = {};
77
+ }
78
+ });
79
+
80
+ async function onRecordAdd(entry: any) {
81
+ delete entry.id;
82
+ entries.unshift(entry);
83
+ }
84
+
85
+ async function onRecordOverride(entry: any, index: number) {
86
+ delete entry.id;
87
+ entries[index] = entry;
88
+ }
89
+
90
+ async function onRecordSelect(entry: any) {
91
+ entries.unshift(entry);
92
+ }
93
+
94
+ async function onRecordSelectReplace(entry: any, index: number) {
95
+ entries[index] = entry;
96
+ }
97
+
98
+ async function onRecordRemove(entry: any, index: number) {
99
+ entries.splice(index, 1);
100
+ }
101
+ </script>
102
+
103
+ <div
104
+ class="
105
+ flex flex-col border rounded-md overflow-clip
106
+ {className ? className : ''}
107
+ "
108
+ >
109
+ <div
110
+ class="
111
+ flex items-center justify-between px-2 h-10 bg-muted/30
112
+ {expanded ? 'border-b' : ''}
113
+ "
114
+ >
115
+ <button
116
+ onclick={() => (expanded = !expanded)}
117
+ class="flex-1 flex h-full items-center gap-2"
118
+ >
119
+ <ChevronRight
120
+ class="text-muted-foreground transition-transform"
121
+ style={expanded
122
+ ? "transform: rotate(90deg);"
123
+ : "transform: rotate(0deg);"}
124
+ size="17.5"
125
+ />
126
+ <TableIcon class="text-muted-foreground" size="17.5" />
127
+ <div class="text-sm text-muted-foreground">{collectionName}</div>
128
+ </button>
129
+ <div class="flex gap-2">
130
+ <SelectRecord
131
+ {parentCollectionName}
132
+ {collectionName}
133
+ text="Select existing"
134
+ onSelect={onRecordSelect}
135
+ filter={selectRecordFilter}
136
+ class="h-7 px-2 font-normal text-xs"
137
+ variant="ghost"
138
+ />
139
+ <CreateDetailViewButton
140
+ variant="ghost"
141
+ class="h-7 px-2 font-normal text-xs"
142
+ Icon={Plus}
143
+ {collectionName}
144
+ rollback={true}
145
+ showRelatedRecords={true}
146
+ onSuccessfullSave={onRecordAdd}
147
+ values={createValues}
148
+ submitButton={{
149
+ text: "Add",
150
+ icon: Plus,
151
+ }}
152
+ >
153
+ {#snippet title(collectionName)}
154
+ <div>Add record to</div>
155
+ <div class="rounded-md border bg-muted px-2 py-0.5">
156
+ {collectionName}
157
+ </div>
158
+ {/snippet}
159
+ Add
160
+ </CreateDetailViewButton>
161
+ </div>
162
+ </div>
163
+ {#if expanded}
164
+ <div bind:clientWidth={tableWidth} class="bg-muted/30 overflow-auto">
165
+ <Table
166
+ data={entries}
167
+ {columns}
168
+ selectByColumn="id"
169
+ showCollapsible={doesCollectionHasChildren}
170
+ unifiedBgColor="bg-muted/30"
171
+ >
172
+ {#snippet tools(entry, index)}
173
+ <Button
174
+ class="h-6 w-6 text-muted-foreground hover:bg-transparent"
175
+ variant="ghost"
176
+ size="icon"
177
+ onclick={() => onRecordRemove(entry, index)}
178
+ Icon={X}
179
+ ></Button>
180
+ {#if entry.id}
181
+ <!-- TODO: remove this. I think its totally unneccessary.
182
+ its the replace record button. there is no need for that.
183
+ that can just delete and add a new one -->
184
+ <!-- <SelectRecord
185
+ {collectionName}
186
+ onSelect={(entry) =>
187
+ onRecordSelectReplace(entry, index)}
188
+ filter={selectRecordFilter}
189
+ class="h-6 w-6 text-muted-foreground hover:bg-transparent p-0"
190
+ variant="ghost"
191
+ >
192
+ <Replace size="17.5" />
193
+ </SelectRecord> -->
194
+ {:else}
195
+ <CreateDetailViewButton
196
+ variant="ghost"
197
+ class="h-6 w-6 text-muted-foreground hover:bg-transparent p-0"
198
+ Icon={Pencil}
199
+ {collectionName}
200
+ rollback={true}
201
+ showRelatedRecords={true}
202
+ onSuccessfullSave={(entry) =>
203
+ onRecordOverride(entry, index)}
204
+ values={entry}
205
+ submitButton={{
206
+ text: "Edit",
207
+ icon: Pencil,
208
+ }}
209
+ >
210
+ {#snippet title(collectionName)}
211
+ <div>Update record of</div>
212
+ <div
213
+ class="rounded-md border bg-muted px-2 py-0.5"
214
+ >
215
+ {collectionName}
216
+ </div>
217
+ {/snippet}
218
+ </CreateDetailViewButton>
219
+ {/if}
220
+ {/snippet}
221
+ {#snippet overrideCell(value, column)}
222
+ {#if column.id === "id" && !value}
223
+ <div class="text-muted-foreground">AUTO GENERATED</div>
224
+ {:else}
225
+ <FieldCell
226
+ {collectionName}
227
+ fieldName={column.id}
228
+ {value}
229
+ entry={column}
230
+ />
231
+ {/if}
232
+ {/snippet}
233
+ {#snippet collapsible(entry, index)}
234
+ {#if entry.id}
235
+ <ChildRecords
236
+ {collectionName}
237
+ recordId={entry.id}
238
+ width={tableWidth}
239
+ unifiedBgColor="bg-muted/30"
240
+ />
241
+ {:else}
242
+ <SubRecords
243
+ {collectionName}
244
+ bind:parentEntry={entries[index]}
245
+ width={tableWidth}
246
+ />
247
+ {/if}
248
+ {/snippet}
249
+ </Table>
250
+ </div>
251
+ {/if}
252
+ </div>
@@ -0,0 +1,50 @@
1
+ <script lang="ts">
2
+ import { getStudioContext } from "../../../context";
3
+ import CreateManyView from "./createManyView.svelte";
4
+
5
+ const { ctx } = getStudioContext();
6
+
7
+ interface Props {
8
+ collectionName: string;
9
+ parentEntry: any;
10
+ width: number;
11
+ }
12
+
13
+ let { collectionName, width, parentEntry = $bindable() }: Props = $props();
14
+
15
+ const childrenRelations = ctx.meta.relations.filter(
16
+ (relation) => relation.to.collection === collectionName,
17
+ );
18
+
19
+ // filling the children properties
20
+ for (let index = 0; index < childrenRelations.length; index++) {
21
+ const relation = childrenRelations[index];
22
+ const childCollection = relation.from.collection;
23
+ if (!parentEntry[childCollection]) {
24
+ parentEntry[childCollection] = [];
25
+ }
26
+ }
27
+ </script>
28
+
29
+ <div class="flex">
30
+ <div class="flex justify-center border-r" style="width: 40px;"></div>
31
+ <div class="flex flex-col" style="width: {width - 40}px;">
32
+ {#each childrenRelations as relation, index}
33
+ {@const lastRow = childrenRelations.length - 1 === index}
34
+ {@const fromCollection = relation.from.collection}
35
+ {@const fromField = relation.from.field}
36
+ <div class="overflow-hidden">
37
+ <CreateManyView
38
+ collectionName={fromCollection}
39
+ bind:entries={parentEntry[fromCollection]}
40
+ parentRecord={{
41
+ id: parentEntry.id,
42
+ collectionName: collectionName,
43
+ }}
44
+ class="border-none"
45
+ expanded={false}
46
+ />
47
+ </div>
48
+ {/each}
49
+ </div>
50
+ </div>