@lobb-js/studio 0.7.2 → 0.8.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.
Files changed (164) hide show
  1. package/package.json +2 -1
  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
  163. package/vite-plugins/index.js +2 -0
  164. package/vite-plugins/lobb-proxy.js +36 -0
@@ -0,0 +1,96 @@
1
+ <script lang="ts">
2
+ import DataTable from "../../../components/dataTable/dataTable.svelte";
3
+ import { getStudioContext } from "../../../context";
4
+ import { Link, Plus, TableIcon } from "lucide-svelte";
5
+ import CreateDetailViewButton from "../create/createDetailViewButton.svelte";
6
+ import ExtensionsComponents from "../../../components/extensionsComponents.svelte";
7
+ import { getExtensionUtils } from "../../../extensions/extensionUtils";
8
+
9
+ const { ctx, lobb } = getStudioContext();
10
+
11
+ interface LocalProp {
12
+ collectionName: string;
13
+ entry: any;
14
+ }
15
+
16
+ let { collectionName, entry }: LocalProp = $props();
17
+
18
+ const childrenRelations = ctx.meta.relations.filter(
19
+ (relation) => relation.to.collection === collectionName,
20
+ );
21
+ const refresh: boolean[] = $state(
22
+ new Array(childrenRelations.length).fill(true),
23
+ );
24
+ </script>
25
+
26
+ {#if childrenRelations.length}
27
+ <div class="flex flex-col gap-4 border-t p-4">
28
+ <div class="flex items-center gap-2">
29
+ <Link size="17.5" />
30
+ <div>Sub Records</div>
31
+ </div>
32
+ <div class="flex flex-col gap-4">
33
+ {#each childrenRelations as relation, index}
34
+ {@const childCollection = relation.from.collection}
35
+ {@const childField = relation.from.field}
36
+ <ExtensionsComponents
37
+ name="detailView.update.subRecords.{childCollection}"
38
+ utils={getExtensionUtils(lobb, ctx)}
39
+ collectionName={childCollection}
40
+ filter={{
41
+ [childField]: entry.id,
42
+ }}
43
+ class="bg-muted/30 border rounded-md overflow-hidden"
44
+ >
45
+ <div class="border rounded-md overflow-clip">
46
+ <div
47
+ class="flex items-center justify-between px-2 h-10 bg-muted/30 border-b"
48
+ >
49
+ <div class="flex-1 flex h-full items-center gap-2">
50
+ <TableIcon
51
+ class="text-muted-foreground"
52
+ size="17.5"
53
+ />
54
+ <div class="text-sm text-muted-foreground">
55
+ {childCollection}
56
+ </div>
57
+ </div>
58
+ <div class="flex gap-2">
59
+ <CreateDetailViewButton
60
+ variant="ghost"
61
+ class="h-7 px-2 font-normal text-xs"
62
+ Icon={Plus}
63
+ collectionName={childCollection}
64
+ onSuccessfullSave={async () => {
65
+ refresh[index] = !refresh[index];
66
+ }}
67
+ >
68
+ Create
69
+ </CreateDetailViewButton>
70
+ </div>
71
+ </div>
72
+ <div class="max-h-72 overflow-auto rounded-md">
73
+ {#key refresh[index]}
74
+ <DataTable
75
+ collectionName={childCollection}
76
+ filter={{
77
+ [childField]: entry.id,
78
+ }}
79
+ unifiedBgColor="bg-muted/30"
80
+ showHeader={false}
81
+ showFooter={false}
82
+ showDelete={true}
83
+ tableProps={{
84
+ showLastColumnBorder: false,
85
+ showLastRowBorder: false,
86
+ showCheckboxes: false,
87
+ }}
88
+ />
89
+ {/key}
90
+ </div>
91
+ </div>
92
+ </ExtensionsComponents>
93
+ {/each}
94
+ </div>
95
+ </div>
96
+ {/if}
@@ -0,0 +1,176 @@
1
+ <script lang="ts" module>
2
+ interface SubmitButton {
3
+ text: string;
4
+ icon: any;
5
+ }
6
+
7
+ export interface UpdateDetailViewProp {
8
+ collectionName: string;
9
+ recordId: string;
10
+ values?: Record<string, any>;
11
+ showRelatedRecords?: boolean;
12
+ rollback?: boolean;
13
+ submitButton?: SubmitButton;
14
+ title?: Snippet<[string]>;
15
+ onSuccessfullSave?: (entry: any) => Promise<void>;
16
+ onCancel?: () => Promise<void>;
17
+ }
18
+ </script>
19
+
20
+ <script lang="ts">
21
+ import { ArrowLeft, Pencil, X } from "lucide-svelte";
22
+ import Button from "../../../components/ui/button/button.svelte";
23
+ import { fade, fly } from "svelte/transition";
24
+ import { getStudioContext } from "../../../context";
25
+ import { toast } from "svelte-sonner";
26
+ import ExtensionsComponents from "../../extensionsComponents.svelte";
27
+ import { getExtensionUtils } from "../../../extensions/extensionUtils";
28
+
29
+ const { lobb, ctx } = getStudioContext();
30
+ import { calculateDrawerWidth, getChangedProperties } from "../../../utils";
31
+ import { getField, getFieldIcon } from "../../dataTable/utils";
32
+ import Children from "../update/children.svelte";
33
+ import type { Snippet } from "svelte";
34
+ import { getDefaultEntry, parseDetailViewValues, serializeEntry } from "../utils";
35
+ import FieldInput from "../fieldInput.svelte";
36
+ import Drawer from "../../../components/drawer.svelte";
37
+
38
+ let {
39
+ collectionName,
40
+ values = {},
41
+ showRelatedRecords = true,
42
+ onCancel,
43
+ onSuccessfullSave,
44
+ title,
45
+ submitButton,
46
+ recordId,
47
+ }: UpdateDetailViewProp = $props();
48
+
49
+ parseDetailViewValues(ctx, collectionName, values)
50
+
51
+ const fieldNames = Object.keys(ctx.meta.collections[collectionName].fields);
52
+ let entry: Record<string, any> = $state(
53
+ getDefaultEntry(ctx, fieldNames, collectionName, values),
54
+ );
55
+ const initialEntry = $state.snapshot(entry);
56
+ let localEntry = $derived(
57
+ getChangedProperties(initialEntry, $state.snapshot(entry)),
58
+ );
59
+ let fieldsErrors: Record<string, any> = $state({});
60
+
61
+ async function handleSave() {
62
+ delete localEntry.id;
63
+ localEntry = serializeEntry(ctx, collectionName, localEntry);
64
+
65
+ const response = await lobb.updateOne(
66
+ collectionName,
67
+ recordId,
68
+ localEntry,
69
+ );
70
+
71
+ if (!response.bodyUsed) {
72
+ const result = await response.json();
73
+ if (response.status >= 400) {
74
+ if (result.message && result.details) {
75
+ fieldsErrors = result.details;
76
+ return;
77
+ } else if (result.message) {
78
+ return;
79
+ }
80
+ }
81
+ }
82
+
83
+ // close detailView side bar
84
+ if (onSuccessfullSave) {
85
+ await onSuccessfullSave(localEntry);
86
+ }
87
+
88
+ toast.success(`The record was successfully updated`);
89
+
90
+ onCancel?.();
91
+ }
92
+ </script>
93
+
94
+ <Drawer onHide={onCancel}>
95
+ <div class="flex h-12 items-center gap-4 border-b px-4">
96
+ <Button
97
+ variant="outline"
98
+ onclick={onCancel}
99
+ class=" h-8 w-8 rounded-full text-xs font-normal"
100
+ Icon={ArrowLeft}
101
+ ></Button>
102
+ <div class="flex items-center gap-2 text-sm">
103
+ {#if title}
104
+ {@render title(collectionName)}
105
+ {:else}
106
+ <div>Update record of</div>
107
+ <div class="rounded-md border bg-muted px-2 py-0.5">
108
+ {collectionName}
109
+ </div>
110
+ {/if}
111
+ </div>
112
+ </div>
113
+ <div class="flex-1 overflow-y-auto">
114
+ <div class="flex flex-col gap-4 p-4">
115
+ {#each fieldNames as fieldName}
116
+ {@const field = getField(ctx, fieldName, collectionName)}
117
+ {@const FieldIcon = getFieldIcon(ctx, fieldName, collectionName)}
118
+ <div
119
+ class="flex flex-col gap-2"
120
+ >
121
+ <div
122
+ class="flex flex-1 items-end justify-between gap-2 text-xs"
123
+ >
124
+ <div class="flex gap-2">
125
+ <div class="h-fit">{field.label}</div>
126
+ <div
127
+ class="flex h-fit items-center gap-1 text-[0.7rem] text-muted-foreground"
128
+ >
129
+ <FieldIcon size="12" />
130
+ {field.type}
131
+ </div>
132
+ </div>
133
+ <div>
134
+ <ExtensionsComponents
135
+ name="dvFields.topRight.{collectionName}.{fieldName}"
136
+ utils={getExtensionUtils(lobb, ctx)}
137
+ bind:value={entry[fieldName]}
138
+ />
139
+ </div>
140
+ </div>
141
+ <FieldInput
142
+ {collectionName}
143
+ {fieldName}
144
+ bind:value={entry[fieldName]}
145
+ {entry}
146
+ errorMessages={fieldsErrors[fieldName]}
147
+ />
148
+ </div>
149
+ {/each}
150
+ </div>
151
+ {#if showRelatedRecords}
152
+ <Children {collectionName} {entry} />
153
+ {/if}
154
+ </div>
155
+ <div class="flex h-12 items-center justify-end gap-2 border-t px-4">
156
+ <div class="flex gap-3">
157
+ <Button
158
+ variant="outline"
159
+ onclick={onCancel}
160
+ class="h-7 px-3 text-xs font-normal"
161
+ Icon={X}
162
+ >
163
+ Cancel
164
+ </Button>
165
+ <Button
166
+ variant="default"
167
+ class="h-7 px-3 text-xs font-normal"
168
+ Icon={submitButton?.icon ? submitButton.icon : Pencil}
169
+ onclick={handleSave}
170
+ disabled={!Object.keys(localEntry).length}
171
+ >
172
+ {submitButton?.text ? submitButton.text : "Update"}
173
+ </Button>
174
+ </div>
175
+ </div>
176
+ </Drawer>
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ import type { UpdateDetailViewProp } from "./updateDetailView.svelte";
3
+ import type { ButtonProps } from "../../../components/ui/button/button.svelte";
4
+ import Button from "../../../components/ui/button/button.svelte";
5
+ import UpdateDetailView from "./updateDetailView.svelte";
6
+ import { getStudioContext } from "../../../context";
7
+ import { getCollectionParamsFields } from "../../dataTable/utils";
8
+
9
+ interface LocalProp extends UpdateDetailViewProp {
10
+ variant?: ButtonProps["variant"];
11
+ class?: ButtonProps["class"];
12
+ Icon?: ButtonProps["Icon"];
13
+ children?: ButtonProps["children"];
14
+ }
15
+
16
+ let props: LocalProp = $props();
17
+ let open = $state(false);
18
+ let values: Record<string, any> | undefined = $state(undefined);
19
+
20
+ const { lobb, ctx } = getStudioContext();
21
+
22
+ async function openView() {
23
+ const params = {
24
+ fields: getCollectionParamsFields(ctx, props.collectionName, true),
25
+ filter: { id: props.recordId },
26
+ limit: 1,
27
+ };
28
+ const response = await lobb.findAll(props.collectionName, params);
29
+ const result = await response.json();
30
+ values = result.data[0];
31
+ open = true;
32
+ }
33
+ </script>
34
+
35
+ <Button
36
+ variant={props.variant}
37
+ class={props.class}
38
+ Icon={props.Icon}
39
+ onclick={openView}
40
+ >
41
+ {#if props.children}
42
+ {@render props.children()}
43
+ {/if}
44
+ </Button>
45
+
46
+ {#if open && values}
47
+ <UpdateDetailView
48
+ {...props}
49
+ {values}
50
+ onCancel={async () => {
51
+ open = false;
52
+ values = undefined;
53
+ await props.onCancel?.();
54
+ }}
55
+ />
56
+ {/if}
@@ -0,0 +1,176 @@
1
+ import Mustache from "mustache";
2
+ import type { CTX } from "../../store.types";
3
+ import { getFieldRelation } from "../../utils";
4
+ import { getField } from "../dataTable/utils";
5
+ import type { DetailFormField } from "./detailViewForm.svelte";
6
+
7
+ export function getDefaultEntry(ctx: CTX, fieldNames: string[], collectionName: string, values?: Record<string, any>) {
8
+ return Object.fromEntries(
9
+ fieldNames.map((fieldName) => {
10
+ let value = null;
11
+ const field = getField(ctx, fieldName, collectionName);
12
+ if (values && values[fieldName] !== undefined) {
13
+ value = values[fieldName];
14
+ } else if (field.pre_processors?.default) {
15
+ const defualtValue = field.pre_processors.default;
16
+ if (typeof defualtValue === "string") {
17
+ value = Mustache.render(defualtValue, {
18
+ now: new Date().toISOString(),
19
+ });
20
+ } else {
21
+ value = defualtValue;
22
+ }
23
+ }
24
+ return [fieldName, value];
25
+ }),
26
+ );
27
+ }
28
+
29
+ export function serializeEntry(
30
+ ctx: CTX,
31
+ collectionName: string,
32
+ entry: Record<string, any>,
33
+ rollback: boolean = false,
34
+ ) {
35
+ // deep clone the object
36
+ entry = { ...entry }
37
+
38
+ // serialize the foreign key field's value
39
+ for (const [fieldName, fieldValue] of Object.entries(entry)) {
40
+ const isRefrenceField = Boolean(getFieldRelation(ctx, collectionName, fieldName));
41
+ if (isRefrenceField && fieldValue !== null && fieldValue.id !== undefined) {
42
+ entry[fieldName] = fieldValue.id;
43
+ }
44
+ }
45
+
46
+ // check for related collections properties and serialize them too
47
+ if (!rollback) {
48
+ const childrenRelations = ctx.meta.relations.filter((relation) => relation.to.collection === collectionName);
49
+ const childrenCollectionNames = childrenRelations.map((relation) => relation.from.collection);
50
+ for (let index = 0; index < childrenCollectionNames.length; index++) {
51
+ const childrenCollectionName = childrenCollectionNames[index];
52
+ const childrenEntries = entry[childrenCollectionName];
53
+ if (childrenEntries) {
54
+ for (let index = 0; index < childrenEntries.length; index++) {
55
+ childrenEntries[index] = serializeEntry(ctx, childrenCollectionName, childrenEntries[index]);
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ return entry;
62
+ }
63
+
64
+ export function generateTransactionBody(
65
+ ctx: CTX,
66
+ collectionName: string,
67
+ entry: Record<string, any>,
68
+ ) {
69
+ entry = { ...entry }
70
+ function handleEntryRecursive(
71
+ transactionBody: any[],
72
+ collectionName: string,
73
+ entry: Record<string, any>,
74
+ parentTransactionIndex?: number,
75
+ ) {
76
+ const parentCollectionName = parentTransactionIndex !== undefined ? transactionBody[parentTransactionIndex].props.collectionName : null;
77
+ const foreignKeyFieldName = ctx.meta.relations.find(relation => relation.from.collection === collectionName && relation.to.collection === parentCollectionName)?.from.field;
78
+ const collectionFieldNames = Object.keys(ctx.meta.collections[collectionName].fields);
79
+ const payload: any = {};
80
+ for (let index = 0; index < collectionFieldNames.length; index++) {
81
+ const fieldName = collectionFieldNames[index];
82
+ const isForeignKeyField = fieldName === foreignKeyFieldName;
83
+ if (isForeignKeyField) {
84
+ payload[fieldName] = `{{ responses[${parentTransactionIndex}].data.id }}`
85
+ continue;
86
+ }
87
+
88
+ payload[fieldName] = entry[fieldName];
89
+ }
90
+
91
+ const localTransactionIndex = transactionBody.length;
92
+ if (payload.id) {
93
+ const localPayload = {
94
+ [foreignKeyFieldName]: payload[foreignKeyFieldName],
95
+ };
96
+ transactionBody.push({
97
+ method: "updateMany",
98
+ props: {
99
+ collectionName: collectionName,
100
+ data: localPayload,
101
+ filter: { id: payload.id },
102
+ },
103
+ });
104
+ } else {
105
+ transactionBody.push({
106
+ method: "createOne",
107
+ props: { collectionName: collectionName, data: payload },
108
+ });
109
+ }
110
+
111
+ const childrenRelations = ctx.meta.relations.filter((relation) => relation.to.collection === collectionName);
112
+ const childrenCollectionNames = childrenRelations.map((relation) => relation.from.collection);
113
+ for (let index = 0; index < childrenCollectionNames.length; index++) {
114
+ const childrenCollectionName = childrenCollectionNames[index];
115
+ const childrenEntries = entry[childrenCollectionName];
116
+ if (childrenEntries) {
117
+ for (let index = 0; index < childrenEntries.length; index++) {
118
+ const childrenEntry = childrenEntries[index];
119
+ handleEntryRecursive(transactionBody, childrenCollectionName, childrenEntry, localTransactionIndex);
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ const transactionBody: any[] = [];
126
+
127
+ handleEntryRecursive(transactionBody, collectionName, entry);
128
+
129
+ return transactionBody
130
+ }
131
+
132
+ export function parseDetailViewValues(ctx: CTX, collectionName: string, values: Record<string, any>) {
133
+ const forignFieldNames = ctx.meta.relations
134
+ .filter((relation) => relation.from.collection === collectionName)
135
+ .map((relation) => relation.from.field);
136
+ const childCollectionNames = ctx.meta.relations
137
+ .filter((relation) => relation.to.collection === collectionName)
138
+ .map((relation) => relation.from.collection);
139
+
140
+ for (const [key, value] of Object.entries(values)) {
141
+ if (forignFieldNames.includes(key)) {
142
+ if (typeof value === 'number') {
143
+ values[key] = {
144
+ id: value,
145
+ }
146
+ }
147
+ } else if (childCollectionNames.includes(key)) {
148
+ for (let index = 0; index < values[key].length; index++) {
149
+ parseDetailViewValues(ctx, key, values[key][index]);
150
+ }
151
+ }
152
+ }
153
+ }
154
+
155
+ export function getCollectionFields(ctx: CTX, collectionName: string) {
156
+ let returnedData: DetailFormField[] = [];
157
+
158
+ const collectionFields = ctx.meta.collections[collectionName].fields;
159
+ const isSingleton = ctx.meta.collections[collectionName].singleton;
160
+ for (const [fieldName, value] of Object.entries(collectionFields)) {
161
+
162
+ if (isSingleton && fieldName === "id") {
163
+ continue;
164
+ }
165
+
166
+ returnedData.push({
167
+ type: "input",
168
+ key: fieldName,
169
+ label: fieldName,
170
+ disabled: fieldName == "id",
171
+ placeholder: fieldName == "id" ? "AUTO_GENERATED" : "",
172
+ })
173
+ }
174
+
175
+ return returnedData;
176
+ }
@@ -0,0 +1,105 @@
1
+ <script lang="ts">
2
+ import { onMount, onDestroy } from "svelte";
3
+ import { MergeView } from "@codemirror/merge";
4
+ import { EditorView } from "@codemirror/view";
5
+ import { EditorState } from "@codemirror/state";
6
+ import { basicSetup } from "codemirror";
7
+ import { javascript } from "@codemirror/lang-javascript";
8
+ import { sql } from "@codemirror/lang-sql";
9
+ import { cn } from "../utils";
10
+
11
+ interface Props {
12
+ type: "javascript" | "typescript" | "json" | "sql";
13
+ original: string;
14
+ modified: string;
15
+ class?: string;
16
+ }
17
+
18
+ let {
19
+ type,
20
+ original,
21
+ modified,
22
+ class: className,
23
+ ...props
24
+ }: Props = $props();
25
+
26
+ let editorContainer: HTMLDivElement;
27
+ let mergeView: MergeView | null = null;
28
+
29
+ const getLanguageExtension = () => {
30
+ switch (type) {
31
+ case 'javascript':
32
+ case 'typescript':
33
+ case 'json':
34
+ return javascript();
35
+ case 'sql':
36
+ return sql();
37
+ default:
38
+ return javascript();
39
+ }
40
+ };
41
+
42
+ onMount(() => {
43
+ const langExtension = getLanguageExtension();
44
+
45
+ mergeView = new MergeView({
46
+ a: {
47
+ doc: original,
48
+ extensions: [
49
+ basicSetup,
50
+ langExtension,
51
+ EditorView.editable.of(false),
52
+ EditorState.readOnly.of(true),
53
+ EditorView.theme({
54
+ '&': {
55
+ backgroundColor: 'transparent',
56
+ },
57
+ '.cm-gutters': {
58
+ backgroundColor: 'transparent',
59
+ border: 'none',
60
+ },
61
+ '.cm-content': {
62
+ paddingTop: '10px',
63
+ paddingBottom: '1px',
64
+ }
65
+ })
66
+ ]
67
+ },
68
+ b: {
69
+ doc: modified,
70
+ extensions: [
71
+ basicSetup,
72
+ langExtension,
73
+ EditorView.editable.of(false),
74
+ EditorState.readOnly.of(true),
75
+ EditorView.theme({
76
+ '&': {
77
+ backgroundColor: 'transparent',
78
+ },
79
+ '.cm-gutters': {
80
+ backgroundColor: 'transparent',
81
+ border: 'none',
82
+ },
83
+ '.cm-content': {
84
+ paddingTop: '10px',
85
+ paddingBottom: '1px',
86
+ }
87
+ })
88
+ ]
89
+ },
90
+ parent: editorContainer
91
+ });
92
+ });
93
+
94
+ onDestroy(() => {
95
+ mergeView?.destroy();
96
+ });
97
+ </script>
98
+
99
+ <div class={cn("w-full resize-y rounded-md border bg-muted/30 shadow-sm", className)}>
100
+ <div
101
+ bind:this={editorContainer}
102
+ class="editor pl-2"
103
+ style="height: 100%; width: 100%;"
104
+ ></div>
105
+ </div>
@@ -0,0 +1,28 @@
1
+ <script lang="ts">
2
+ import { calculateDrawerWidth } from "../utils";
3
+ import type { Snippet } from "svelte";
4
+ import { fade, fly } from "svelte/transition";
5
+
6
+ interface Props {
7
+ children?: Snippet<[]>;
8
+ onHide?: () => Promise<void>;
9
+ }
10
+
11
+ let { onHide, children }: Props = $props();
12
+ </script>
13
+
14
+ <button
15
+ transition:fade={{ duration: 250 }}
16
+ onclick={() => onHide?.()}
17
+ class="backgroundDrawerButton fixed left-0 top-0 z-30 h-screen w-screen bg-background opacity-50 cursor-default"
18
+ aria-label="background used to hide the background"
19
+ ></button>
20
+
21
+ <!-- the drawer -->
22
+ <div
23
+ transition:fly={{ x: "100%", duration: 250 }}
24
+ class="fixed right-0 top-0 z-30 flex h-full w-full flex-col border-l bg-background"
25
+ style="max-width: {calculateDrawerWidth()}px;"
26
+ >
27
+ {@render children?.()}
28
+ </div>
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { loadExtensionComponents } from "../extensions/extensionUtils";
4
+ import { getStudioContext } from "../context";
5
+
6
+ const { ctx } = getStudioContext();
7
+
8
+ interface Props {
9
+ name: string;
10
+ filterByExtensions?: string[];
11
+ value?: unknown;
12
+ children?: Snippet<[]>;
13
+ [key: string]: any;
14
+ }
15
+
16
+ let {
17
+ name,
18
+ filterByExtensions,
19
+ value = $bindable(),
20
+ children,
21
+ ...props
22
+ }: Props = $props();
23
+
24
+ const Components = loadExtensionComponents(ctx, name, filterByExtensions);
25
+ </script>
26
+
27
+ {#if Components.length}
28
+ {#each Components as Component}
29
+ <Component bind:value {...props} />
30
+ {/each}
31
+ {:else}
32
+ {@render children?.()}
33
+ {/if}