@lobb-js/studio 0.28.6 → 0.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +1 -0
  2. package/dist/actions.d.ts +2 -0
  3. package/dist/components/Studio.svelte +46 -47
  4. package/dist/components/StudioRoot.svelte +19 -0
  5. package/dist/components/StudioRoot.svelte.d.ts +6 -0
  6. package/dist/components/breadCrumbs.svelte +5 -4
  7. package/dist/components/codeEditor.svelte +1 -1
  8. package/dist/components/combobox.svelte +3 -3
  9. package/dist/components/confirmationDialog/confirmationDialog.svelte +1 -1
  10. package/dist/components/dataTable/dataTable.svelte +108 -101
  11. package/dist/components/dataTable/dataTable.svelte.d.ts +5 -20
  12. package/dist/components/dataTable/dataTableTabs.svelte +4 -2
  13. package/dist/components/dataTable/dataTableTabs.svelte.d.ts +2 -0
  14. package/dist/components/dataTable/filter.svelte +1 -1
  15. package/dist/components/dataTable/filterButton.svelte +1 -1
  16. package/dist/components/dataTable/header.svelte +30 -47
  17. package/dist/components/dataTable/header.svelte.d.ts +4 -2
  18. package/dist/components/dataTable/listViewChildren.svelte +4 -6
  19. package/dist/components/dataTable/listViewChildren.svelte.d.ts +0 -1
  20. package/dist/components/dataTable/sort.svelte +1 -1
  21. package/dist/components/dataTable/sortButton.svelte +2 -2
  22. package/dist/components/dataTable/table.svelte +8 -10
  23. package/dist/components/dataTable/table.svelte.d.ts +0 -1
  24. package/dist/components/dataTableDrawer/dataTableDrawer.svelte +4 -1
  25. package/dist/components/dataTableDrawer/dataTableDrawer.svelte.d.ts +2 -0
  26. package/dist/components/detailView/create/children.svelte +2 -2
  27. package/dist/components/detailView/create/createDetailView.svelte +81 -88
  28. package/dist/components/detailView/create/createDetailView.svelte.d.ts +2 -2
  29. package/dist/components/detailView/create/createDetailViewButton.svelte +2 -2
  30. package/dist/components/detailView/create/createDetailViewButton.svelte.d.ts +1 -1
  31. package/dist/components/detailView/create/createManyView.svelte +12 -10
  32. package/dist/components/detailView/detailView.svelte +81 -0
  33. package/dist/components/detailView/detailView.svelte.d.ts +8 -0
  34. package/dist/components/detailView/fieldInput.svelte +11 -11
  35. package/dist/components/detailView/fieldInputReplacement.svelte +8 -8
  36. package/dist/components/detailView/passwordInput.svelte +1 -1
  37. package/dist/components/detailView/update/detailViewChildren.svelte +15 -26
  38. package/dist/components/detailView/update/detailViewChildren.svelte.d.ts +3 -8
  39. package/dist/components/detailView/update/updateDetailView.svelte +90 -69
  40. package/dist/components/detailView/update/updateDetailView.svelte.d.ts +2 -2
  41. package/dist/components/detailView/update/updateDetailViewButton.svelte +3 -2
  42. package/dist/components/detailView/update/updateDetailViewButton.svelte.d.ts +1 -1
  43. package/dist/components/detailView/utils.d.ts +17 -0
  44. package/dist/components/diffViewer.svelte +1 -1
  45. package/dist/components/extensionsComponents.svelte +3 -1
  46. package/dist/components/foreingKeyInput.svelte +2 -2
  47. package/dist/components/importButton.svelte +12 -9
  48. package/dist/components/landing.svelte +7 -0
  49. package/dist/components/landing.svelte.d.ts +6 -14
  50. package/dist/components/miniSidebar.svelte +90 -19
  51. package/dist/components/miniSidebar.svelte.d.ts +2 -17
  52. package/dist/components/polymorphicInput.svelte +1 -1
  53. package/dist/components/rangeCalendarButton.svelte +13 -13
  54. package/dist/components/richTextEditor.svelte +1 -1
  55. package/dist/components/routes/collections/collection.svelte +3 -3
  56. package/dist/components/routes/collections/collections.svelte +34 -12
  57. package/dist/components/routes/data_model/dataModel.svelte +6 -28
  58. package/dist/components/routes/data_model/dataModel.svelte.d.ts +17 -2
  59. package/dist/components/routes/extensions/extension.svelte +1 -1
  60. package/dist/components/routes/extensions/publicExtension.svelte +19 -0
  61. package/dist/components/routes/extensions/publicExtension.svelte.d.ts +13 -0
  62. package/dist/components/routes/home.svelte +3 -3
  63. package/dist/components/routes/workflows/workflows.svelte +9 -9
  64. package/dist/components/selectRecord.svelte +2 -21
  65. package/dist/components/setServerPage.svelte +1 -1
  66. package/dist/components/sidebar/sidebar.svelte +1 -1
  67. package/dist/components/sidebar/sidebarElements.svelte +4 -4
  68. package/dist/components/singletone.svelte +4 -6
  69. package/dist/components/ui/alert-dialog/alert-dialog-action.svelte +1 -1
  70. package/dist/components/ui/alert-dialog/alert-dialog-cancel.svelte +1 -1
  71. package/dist/components/ui/button/button.svelte +2 -3
  72. package/dist/components/ui/command/command-dialog.svelte +1 -1
  73. package/dist/components/ui/range-calendar/range-calendar-day.svelte +1 -1
  74. package/dist/components/ui/range-calendar/range-calendar-next-button.svelte +1 -1
  75. package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte +1 -1
  76. package/dist/components/ui/select/select-separator.svelte +1 -1
  77. package/dist/components/workflowEditor.svelte +5 -5
  78. package/dist/eventSystem.d.ts +1 -1
  79. package/dist/eventSystem.js +7 -5
  80. package/dist/extensions/extension.types.d.ts +38 -14
  81. package/dist/extensions/extensionUtils.js +4 -2
  82. package/dist/index.d.ts +3 -1
  83. package/dist/index.js +3 -1
  84. package/dist/store.types.d.ts +2 -2
  85. package/dist/studioLifecycle.svelte.d.ts +2 -0
  86. package/dist/studioLifecycle.svelte.js +15 -0
  87. package/package.json +3 -4
  88. package/src/app.css +3 -0
  89. package/src/lib/actions.ts +2 -0
  90. package/src/lib/components/Studio.svelte +46 -47
  91. package/src/lib/components/StudioRoot.svelte +19 -0
  92. package/src/lib/components/breadCrumbs.svelte +5 -4
  93. package/src/lib/components/codeEditor.svelte +1 -1
  94. package/src/lib/components/combobox.svelte +3 -3
  95. package/src/lib/components/confirmationDialog/confirmationDialog.svelte +1 -1
  96. package/src/lib/components/dataTable/dataTable.svelte +108 -101
  97. package/src/lib/components/dataTable/dataTableTabs.svelte +4 -2
  98. package/src/lib/components/dataTable/filter.svelte +1 -1
  99. package/src/lib/components/dataTable/filterButton.svelte +1 -1
  100. package/src/lib/components/dataTable/header.svelte +30 -47
  101. package/src/lib/components/dataTable/listViewChildren.svelte +4 -6
  102. package/src/lib/components/dataTable/sort.svelte +1 -1
  103. package/src/lib/components/dataTable/sortButton.svelte +2 -2
  104. package/src/lib/components/dataTable/table.svelte +8 -10
  105. package/src/lib/components/dataTableDrawer/dataTableDrawer.svelte +4 -1
  106. package/src/lib/components/detailView/create/children.svelte +2 -2
  107. package/src/lib/components/detailView/create/createDetailView.svelte +81 -88
  108. package/src/lib/components/detailView/create/createDetailViewButton.svelte +2 -2
  109. package/src/lib/components/detailView/create/createManyView.svelte +12 -10
  110. package/src/lib/components/detailView/detailView.svelte +81 -0
  111. package/src/lib/components/detailView/fieldInput.svelte +11 -11
  112. package/src/lib/components/detailView/fieldInputReplacement.svelte +8 -8
  113. package/src/lib/components/detailView/passwordInput.svelte +1 -1
  114. package/src/lib/components/detailView/update/detailViewChildren.svelte +15 -26
  115. package/src/lib/components/detailView/update/updateDetailView.svelte +90 -69
  116. package/src/lib/components/detailView/update/updateDetailViewButton.svelte +3 -2
  117. package/src/lib/components/detailView/utils.ts +13 -0
  118. package/src/lib/components/diffViewer.svelte +1 -1
  119. package/src/lib/components/extensionsComponents.svelte +3 -1
  120. package/src/lib/components/foreingKeyInput.svelte +2 -2
  121. package/src/lib/components/importButton.svelte +12 -9
  122. package/src/lib/components/landing.svelte +7 -0
  123. package/src/lib/components/miniSidebar.svelte +90 -19
  124. package/src/lib/components/polymorphicInput.svelte +1 -1
  125. package/src/lib/components/rangeCalendarButton.svelte +13 -13
  126. package/src/lib/components/richTextEditor.svelte +1 -1
  127. package/src/lib/components/routes/collections/collection.svelte +3 -3
  128. package/src/lib/components/routes/collections/collections.svelte +34 -12
  129. package/src/lib/components/routes/data_model/dataModel.svelte +6 -28
  130. package/src/lib/components/routes/extensions/extension.svelte +1 -1
  131. package/src/lib/components/routes/extensions/publicExtension.svelte +19 -0
  132. package/src/lib/components/routes/home.svelte +3 -3
  133. package/src/lib/components/routes/workflows/workflows.svelte +9 -9
  134. package/src/lib/components/selectRecord.svelte +2 -21
  135. package/src/lib/components/setServerPage.svelte +1 -1
  136. package/src/lib/components/sidebar/sidebar.svelte +1 -1
  137. package/src/lib/components/sidebar/sidebarElements.svelte +4 -4
  138. package/src/lib/components/singletone.svelte +4 -6
  139. package/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte +1 -1
  140. package/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte +1 -1
  141. package/src/lib/components/ui/button/button.svelte +2 -3
  142. package/src/lib/components/ui/command/command-dialog.svelte +1 -1
  143. package/src/lib/components/ui/range-calendar/range-calendar-day.svelte +1 -1
  144. package/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte +1 -1
  145. package/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte +1 -1
  146. package/src/lib/components/ui/select/select-separator.svelte +1 -1
  147. package/src/lib/components/workflowEditor.svelte +5 -5
  148. package/src/lib/eventSystem.ts +8 -7
  149. package/src/lib/extensions/extension.types.ts +39 -6
  150. package/src/lib/extensions/extensionUtils.ts +4 -2
  151. package/src/lib/index.ts +3 -1
  152. package/src/lib/store.types.ts +2 -2
  153. package/src/lib/studioLifecycle.svelte.ts +17 -0
  154. package/vite-plugins/index.js +2 -4
  155. package/vite-plugins/utils.js +15 -0
  156. package/vite-plugins/{workspace-optimize.js → workspace-fs-allow.js} +4 -18
  157. package/dist/components/routes/data_model/syncManager.svelte +0 -94
  158. package/dist/components/routes/data_model/syncManager.svelte.d.ts +0 -3
  159. package/src/lib/components/routes/data_model/syncManager.svelte +0 -94
  160. package/vite-plugins/contextual-lib-alias.js +0 -67
@@ -4,12 +4,6 @@
4
4
  recordId: string | number;
5
5
  }
6
6
 
7
- export type RecordOperation =
8
- | { type: "link"; record: any }
9
- | { type: "unlink"; id: string | number }
10
- | { type: "delete"; id: string | number }
11
- | { type: "create"; record: any }
12
- | { type: "update"; id: string | number; data: any };
13
7
  </script>
14
8
 
15
9
  <script lang="ts">
@@ -20,19 +14,21 @@
20
14
  import Table, { type TableProps } from "./table.svelte";
21
15
  import { getCollectionColumns, getCollectionParamsFields } from "./utils";
22
16
  import { Pencil, Trash, Unlink } from "lucide-svelte";
23
- import * as icons from "lucide-svelte";
24
17
  import ListViewChildren from "./listViewChildren.svelte";
25
18
  import FieldCell from "./fieldCell.svelte";
26
19
  import Skeleton from "../ui/skeleton/skeleton.svelte";
27
20
  import Button from "../ui/button/button.svelte";
28
21
  import { showDialog } from "../../actions";
29
22
  import UpdateDetailViewButton from "../detailView/update/updateDetailViewButton.svelte";
30
- import { emitEvent } from "../../eventSystem";
31
23
  import type { Snippet } from "svelte";
24
+ import type { Changes, ChildrenChanges } from "../detailView/utils";
32
25
  import ExtensionsComponents from "../extensionsComponents.svelte";
33
26
  import { getExtensionUtils, loadExtensionComponents } from "../../extensions/extensionUtils";
27
+ import { emitEvent } from "../../eventSystem";
28
+ import { onMount } from "svelte";
34
29
  import Tabs from "./dataTableTabs.svelte";
35
30
  import { fade } from "svelte/transition";
31
+ import type { CollectionTab } from "../../store.types";
36
32
 
37
33
  const { lobb, ctx } = getStudioContext();
38
34
 
@@ -41,13 +37,13 @@
41
37
  filter?: any;
42
38
  searchParams?: Record<string, any>;
43
39
  parentContext?: ParentContext;
44
- onOperation?: (op: RecordOperation) => void;
40
+ changes?: ChildrenChanges;
45
41
  showHeader?: boolean;
46
42
  showFooter?: boolean;
47
43
  showImport?: boolean;
48
- unifiedBgColor?: "bg-muted/30" | "bg-background";
49
44
  showDelete?: boolean;
50
45
  tableProps?: Partial<TableProps>;
46
+ tabs?: CollectionTab[];
51
47
  headerLeft?: Snippet<[]>;
52
48
  }
53
49
 
@@ -56,16 +52,70 @@
56
52
  filter,
57
53
  searchParams,
58
54
  parentContext,
59
- onOperation,
55
+ changes = $bindable<ChildrenChanges | undefined>(undefined),
60
56
  showHeader = true,
61
57
  showFooter = true,
62
58
  showImport = true,
63
- unifiedBgColor,
64
59
  showDelete = false,
65
60
  tableProps,
61
+ tabs,
66
62
  headerLeft,
67
63
  }: Props = $props();
68
64
 
65
+ // Gate row/header buttons by the current user's permissions:
66
+ // - showUpdate → per-row edit button
67
+ // - showCreate → header's Create + Import buttons (passed to Header)
68
+ let showUpdate = $state(false);
69
+ let showCreate = $state(false);
70
+ onMount(async () => {
71
+ const [update, create] = await Promise.all([
72
+ emitEvent({ lobb, ctx }, "auth.canAccess", { collection: collectionName, action: "update" }),
73
+ emitEvent({ lobb, ctx }, "auth.canAccess", { collection: collectionName, action: "create" }),
74
+ ]);
75
+ showUpdate = update === true;
76
+ showCreate = create === true;
77
+ });
78
+
79
+ function getOrCreateUpdatedSlot(recordId: string): Changes | undefined {
80
+ if (!changes) return undefined;
81
+ let slot = changes.updated.find((u) => String(u.id) === String(recordId));
82
+ if (!slot) {
83
+ slot = { id: recordId, data: {}, children: {} };
84
+ changes.updated.push(slot);
85
+ }
86
+ return slot;
87
+ }
88
+
89
+ // Derives the displayed rows by applying changes on top of server data.
90
+ // This is the single place responsible for optimistic UI — no handler touches data directly.
91
+ const data = $derived.by(() => {
92
+ if (!changes) return serverData;
93
+
94
+ const removedIds = new Set([
95
+ ...changes.deleted.map((r) => String(r.id)),
96
+ ...changes.unlinked.map((r) => String(r.id)),
97
+ ]);
98
+
99
+ let result = serverData.filter((r: any) => !removedIds.has(String(r.id)));
100
+
101
+ result = result.map((r: any) => {
102
+ const update = changes.updated.find((u) => String(u.id) === String(r.id));
103
+ return update && Object.keys(update.data).length ? { ...r, ...update.data } : r;
104
+ });
105
+
106
+ for (const record of changes.linked) {
107
+ if (!result.some((r: any) => String(r.id) === String(record.id))) {
108
+ result = [...result, record];
109
+ }
110
+ }
111
+
112
+ for (const item of changes.created) {
113
+ result = [...result, { ...item.data, _pending: true }];
114
+ }
115
+
116
+ return result;
117
+ });
118
+
69
119
  const hasRowActions = $derived(
70
120
  loadExtensionComponents(ctx, "listView.entry.actions", undefined, { collectionName }).length > 0
71
121
  );
@@ -89,7 +139,7 @@
89
139
 
90
140
  let selectedRecords = $state([]);
91
141
  let totalCount = $state(0);
92
- let data: TableProps["data"] = $state([]);
142
+ let serverData: TableProps["data"] = $state([]);
93
143
  let loading = $state(true);
94
144
  const columns: TableProps["columns"] = $state(
95
145
  getCollectionColumns(ctx, collectionName),
@@ -108,7 +158,6 @@
108
158
 
109
159
  async function loadData(params: any) {
110
160
  loading = true;
111
- // parsing sort before sending the request
112
161
  const paramsCopy = $state.snapshot(params);
113
162
  const sort: TableProps["sort"] = paramsCopy.sort;
114
163
  const sortStrings: string[] = [];
@@ -118,85 +167,48 @@
118
167
  }
119
168
  }
120
169
  paramsCopy.sort = sortStrings.join(",");
121
-
122
- // sending the request
123
170
  const response = await lobb.findAll(collectionName, paramsCopy);
124
171
  const res = await response.json();
125
-
126
- data = res.data;
172
+ serverData = res.data;
127
173
  totalCount = res.meta.totalCount;
128
-
129
174
  loading = false;
130
175
  }
131
176
 
132
- // Internal handler: updates data optimistically then calls onOperation
133
- function applyOperation(op: RecordOperation) {
134
- if (op.type === "link") {
135
- data = [...data, op.record];
136
- } else if (op.type === "unlink" || op.type === "delete") {
137
- data = data.filter((r: any) => String(r.id) !== String(op.id));
138
- } else if (op.type === "create") {
139
- data = [...data, { ...op.record, _pending: true }];
140
- } else if (op.type === "update") {
141
- data = data.map((r: any) => String(r.id) === String(op.id) ? { ...r, ...op.data } : r);
142
- }
143
- onOperation?.(op);
144
- }
145
-
146
177
  async function handleDelete(entryId: string) {
147
178
  const result = await showDialog("Are you sure?", "This will permanently delete the record.");
148
- if (result) {
149
- if (onOperation) {
150
- applyOperation({ type: "delete", id: entryId });
151
- } else if (parentContext) {
152
- await lobb.updateOne(parentContext.collectionName, String(parentContext.recordId), {}, { [collectionName]: { delete: [entryId] } });
153
- params = { ...params };
154
- } else {
155
- await lobb.deleteOne(collectionName, entryId);
156
- params = { ...params };
157
- }
179
+ if (!result) return;
180
+ if (changes) {
181
+ const record = data.find((r: any) => String(r.id) === String(entryId));
182
+ if (record) changes.deleted.push($state.snapshot(record));
183
+ } else if (parentContext) {
184
+ serverData = serverData.filter((r: any) => String(r.id) !== String(entryId));
185
+ await lobb.updateOne(parentContext.collectionName, String(parentContext.recordId), {}, { [collectionName]: { delete: [entryId] } });
186
+ params = { ...params };
187
+ } else {
188
+ serverData = serverData.filter((r: any) => String(r.id) !== String(entryId));
189
+ await lobb.deleteOne(collectionName, entryId);
190
+ params = { ...params };
158
191
  }
159
192
  }
160
193
 
161
194
  async function handleUnlink(entryId: string) {
162
195
  const result = await showDialog("Are you sure?", "This will unlink the record without deleting it.");
163
- if (result) {
164
- if (onOperation) {
165
- applyOperation({ type: "unlink", id: entryId });
166
- } else {
167
- await lobb.updateOne(parentContext!.collectionName, String(parentContext!.recordId), {}, { [collectionName]: { unlink: [entryId] } });
168
- params = { ...params };
169
- }
196
+ if (!result) return;
197
+ if (changes) {
198
+ const record = data.find((r: any) => String(r.id) === String(entryId));
199
+ if (record) changes.unlinked.push($state.snapshot(record));
200
+ } else {
201
+ serverData = serverData.filter((r: any) => String(r.id) !== String(entryId));
202
+ await lobb.updateOne(parentContext!.collectionName, String(parentContext!.recordId), {}, { [collectionName]: { unlink: [entryId] } });
203
+ params = { ...params };
170
204
  }
171
205
  }
172
206
 
173
- async function getWorkflowTools(
174
- entry: Record<string, any>,
175
- ): Promise<any[]> {
176
- // TODO: instead of firing the events like this. get them all the fire them one by one to get their results
177
- const eventResult = await emitEvent(
178
- { lobb, ctx },
179
- "studio.collections.listView.tools",
180
- {
181
- collectionName,
182
- entry,
183
- },
184
- );
185
-
186
- if (eventResult) {
187
- return eventResult.tools;
188
- }
189
-
190
- return [];
191
- }
192
207
  </script>
193
208
 
194
209
  <div
195
210
  bind:clientWidth={dataTableContainerWidth}
196
- class="
197
- flex flex-col overflow-auto h-full w-full
198
- {unifiedBgColor ? unifiedBgColor : ''}
199
- "
211
+ class="flex flex-col overflow-auto h-full w-full"
200
212
  >
201
213
  {#snippet rowActionsSnippet(entry: Record<string, any>)}
202
214
  <ExtensionsComponents
@@ -209,13 +221,21 @@
209
221
  {/snippet}
210
222
 
211
223
  {#if showHeader}
212
- <Header bind:params {collectionName} bind:selectedRecords {parentContext} {showImport} onOperation={onOperation ? applyOperation : undefined}>
224
+ <Header
225
+ bind:params
226
+ {collectionName}
227
+ bind:selectedRecords
228
+ {showImport}
229
+ {showCreate}
230
+ {parentContext}
231
+ {changes}
232
+ >
213
233
  {#snippet left()}
214
234
  {@render headerLeft?.()}
215
235
  {/snippet}
216
236
  </Header>
217
237
  {/if}
218
- <Tabs {collectionName} {filter} bind:activeTabFilter />
238
+ <Tabs {collectionName} {filter} {tabs} bind:activeTabFilter />
219
239
  <div class="relative flex-1 overflow-auto w-full">
220
240
  {#key activeTabFilter}
221
241
  <div class="h-full w-full" in:fade={{ duration: 120 }}>
@@ -235,21 +255,23 @@
235
255
  showLastColumnBorder={true}
236
256
  bind:sort={params.sort}
237
257
  bind:selectedRecords
238
- {unifiedBgColor}
239
258
  bind:tableWidth={dataTableWidth}
240
259
  {...tableProps}
241
260
  rowActions={hasRowActions ? rowActionsSnippet : undefined}>
242
261
  {#snippet tools(entry)}
243
- <UpdateDetailViewButton
244
- {collectionName}
245
- recordId={entry.id}
246
- variant="ghost"
247
- class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
248
- Icon={Pencil}
249
- onSuccessfullSave={async () => {
250
- params = { ...params };
251
- }}
252
- ></UpdateDetailViewButton>
262
+ {#if showUpdate}
263
+ <UpdateDetailViewButton
264
+ {collectionName}
265
+ recordId={entry.id}
266
+ variant="ghost"
267
+ class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
268
+ Icon={Pencil}
269
+ changes={getOrCreateUpdatedSlot(String(entry.id))}
270
+ onSuccessfullSave={async () => {
271
+ params = { ...params };
272
+ }}
273
+ ></UpdateDetailViewButton>
274
+ {/if}
253
275
  {#if parentContext}
254
276
  <Button
255
277
  class="h-6 w-6 text-muted-foreground hover:bg-transparent"
@@ -270,20 +292,6 @@
270
292
  title="Delete permanently"
271
293
  ></Button>
272
294
  {/if}
273
- {#await getWorkflowTools($state.snapshot(entry))}
274
- <div></div>
275
- {:then workflowTools}
276
- {#each workflowTools as workflowTool}
277
- <Button
278
- variant="ghost"
279
- class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
280
- Icon={icons[
281
- workflowTool.icon as keyof typeof icons
282
- ]}
283
- onclick={workflowTool.onclick}
284
- ></Button>
285
- {/each}
286
- {/await}
287
295
  {/snippet}
288
296
  {#snippet overrideCell(value, column, entry)}
289
297
  <FieldCell
@@ -301,7 +309,6 @@
301
309
  width={dataTableWidth > dataTableContainerWidth
302
310
  ? dataTableContainerWidth
303
311
  : dataTableWidth}
304
- unifiedBgColor={unifiedBgColor ?? "bg-background"}
305
312
  />
306
313
  {/snippet}
307
314
  </Table>
@@ -1,17 +1,19 @@
1
1
  <script lang="ts">
2
2
  import { getStudioContext } from "../../context";
3
+ import type { CollectionTab } from "../../store.types";
3
4
 
4
5
  const { lobb, ctx } = getStudioContext();
5
6
 
6
7
  interface Props {
7
8
  collectionName: string;
8
9
  filter?: any;
10
+ tabs?: CollectionTab[];
9
11
  activeTabFilter?: any;
10
12
  }
11
13
 
12
- let { collectionName, filter, activeTabFilter = $bindable() }: Props = $props();
14
+ let { collectionName, filter, tabs: tabsProp, activeTabFilter = $bindable() }: Props = $props();
13
15
 
14
- const tabs = ctx.meta.collections[collectionName].ui?.tabs;
16
+ const tabs: CollectionTab[] | undefined = $derived(tabsProp ?? ctx.meta.collections[collectionName].ui?.tabs);
15
17
  let activeTab = $state<string | null>(null);
16
18
  let tabCounts = $state<Record<string, number>>({});
17
19
 
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import * as Popover from "../../components/ui/popover/index.js";
2
+ import * as Popover from "../ui/popover/index.js";
3
3
  import Filter from "./filter.svelte";
4
4
  import {
5
5
  Plus,
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import * as Popover from "../../components/ui/popover/index.js";
2
+ import * as Popover from "../ui/popover/index.js";
3
3
  import { ListFilter } from "lucide-svelte";
4
4
  import { buttonVariants } from "../ui/button";
5
5
  import Filter from "./filter.svelte";
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { getStudioContext } from "../../context";
3
-
4
- const { lobb, ctx } = getStudioContext();
3
+ import type { Changes, ChildrenChanges } from "../detailView/utils";
4
+ import type { ParentContext } from "./dataTable.svelte";
5
5
  import { Download, ListRestart, Plus, Trash, Link } from "lucide-svelte";
6
6
  import * as Tooltip from "../ui/tooltip";
7
7
  import LlmButton from "../LlmButton.svelte";
@@ -15,15 +15,17 @@
15
15
  import ExtensionsComponents from "../extensionsComponents.svelte";
16
16
  import { getExtensionUtils } from "../../extensions/extensionUtils";
17
17
  import type { Snippet } from "svelte";
18
- import type { ParentContext, RecordOperation } from "./dataTable.svelte";
18
+
19
+ const { lobb, ctx } = getStudioContext();
19
20
 
20
21
  interface Props {
21
22
  collectionName: string;
22
23
  params: any;
23
24
  selectedRecords: string[];
24
25
  parentContext?: ParentContext;
25
- onOperation?: (op: RecordOperation) => void;
26
+ changes?: ChildrenChanges;
26
27
  showImport?: boolean;
28
+ showCreate?: boolean;
27
29
  left?: Snippet<[]>;
28
30
  }
29
31
 
@@ -32,37 +34,28 @@
32
34
  params = $bindable(),
33
35
  selectedRecords = $bindable(),
34
36
  parentContext,
35
- onOperation,
37
+ changes,
36
38
  showImport = true,
39
+ showCreate = false,
37
40
  left
38
41
  }: Props = $props();
39
42
 
40
- async function handleLink(selected: any) {
41
- if (!parentContext) return;
42
- if (onOperation) {
43
- onOperation({ type: "link", record: selected });
44
- } else {
45
- await lobb.updateOne(
46
- parentContext.collectionName,
47
- String(parentContext.recordId),
48
- {},
49
- { [collectionName]: { link: [selected.id] } },
50
- );
43
+ // Local changes object for the create form (recording mode only)
44
+ let createChanges = $state<Changes>({ data: {}, children: {} });
45
+
46
+ function handleLink(record: any) {
47
+ if (changes) {
48
+ changes.linked.push(record);
49
+ } else if (parentContext) {
50
+ lobb.updateOne(parentContext.collectionName, String(parentContext.recordId), {}, { [collectionName]: { link: [record.id] } });
51
51
  resetTable();
52
52
  }
53
53
  }
54
54
 
55
- async function handleChildCreate(formData: any) {
56
- if (!parentContext) return;
57
- if (onOperation) {
58
- onOperation({ type: "create", record: formData });
55
+ async function handleCreateSuccess(snap: any) {
56
+ if (changes) {
57
+ changes.created.push({ data: snap.data });
59
58
  } else {
60
- await lobb.updateOne(
61
- parentContext.collectionName,
62
- String(parentContext.recordId),
63
- {},
64
- { [collectionName]: { create: [formData] } },
65
- );
66
59
  resetTable();
67
60
  }
68
61
  }
@@ -176,7 +169,7 @@
176
169
  >
177
170
  {headerIsSmall ? "" : "Refresh"}
178
171
  </Button>
179
- {#if showImport}
172
+ {#if showImport && showCreate}
180
173
  <Tooltip.Provider delayDuration={0}>
181
174
  <Tooltip.Root>
182
175
  <Tooltip.Trigger>
@@ -199,34 +192,24 @@
199
192
  refresh={() => { params = { ...params }; }}
200
193
  />
201
194
  {#if parentContext}
202
- {#if parentContext}
203
- <SelectRecord
204
- {collectionName}
205
- variant="outline"
206
- class="h-7 px-3 text-xs font-normal"
207
- Icon={Link}
208
- onSelect={handleLink}
209
- >
210
- {headerIsSmall ? "" : "Link"}
211
- </SelectRecord>
212
- {/if}
213
- <CreateDetailViewButton
195
+ <SelectRecord
214
196
  {collectionName}
215
- variant="default"
197
+ variant="outline"
216
198
  class="h-7 px-3 text-xs font-normal"
217
- Icon={Plus}
218
- rollback={true}
219
- onSuccessfullSave={handleChildCreate}
199
+ Icon={Link}
200
+ onSelect={handleLink}
220
201
  >
221
- {headerIsSmall ? "" : "Create"}
222
- </CreateDetailViewButton>
223
- {:else}
202
+ {headerIsSmall ? "" : "Link"}
203
+ </SelectRecord>
204
+ {/if}
205
+ {#if showCreate}
224
206
  <CreateDetailViewButton
225
207
  {collectionName}
226
208
  variant="default"
227
209
  class="h-7 px-3 text-xs font-normal"
228
210
  Icon={Plus}
229
- onSuccessfullSave={() => (params = { ...params })}
211
+ changes={changes ? createChanges : undefined}
212
+ onSuccessfullSave={handleCreateSuccess}
230
213
  >
231
214
  {headerIsSmall ? "" : "Create"}
232
215
  </CreateDetailViewButton>
@@ -12,10 +12,9 @@
12
12
  collectionName: string;
13
13
  recordId: string;
14
14
  width: number;
15
- unifiedBgColor?: "bg-muted/30" | "bg-background";
16
15
  }
17
16
 
18
- let { collectionName, recordId, width, unifiedBgColor }: Props = $props();
17
+ let { collectionName, recordId, width }: Props = $props();
19
18
 
20
19
  const children = (ctx.meta.collections[collectionName]?.children ?? [])
21
20
  .filter((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic");
@@ -27,13 +26,13 @@
27
26
 
28
27
  <div class="flex" style="width: {width}px;">
29
28
  <div
30
- class="flex justify-center border-r {unifiedBgColor ? unifiedBgColor : 'bg-background'}"
29
+ class="flex justify-center border-r bg-background"
31
30
  style="width: 40px"
32
31
  ></div>
33
32
  <div class="flex-1 flex flex-col">
34
33
  {#each children as child, index}
35
34
  {@const lastRow = children.length - 1 === index}
36
- <div class="overflow-hidden {unifiedBgColor ? unifiedBgColor : 'bg-background'}">
35
+ <div class="overflow-hidden bg-background">
37
36
  <div
38
37
  bind:clientWidth={tableHeaderWidth}
39
38
  class="flex justify-between items-center gap-2 text-sm h-10 {expandedRows[index] || !lastRow ? 'border-b' : ''}"
@@ -75,7 +74,7 @@
75
74
  {#if expandedRows[index]}
76
75
  <div class="flex max-h-96 overflow-auto {lastRow ? '' : 'border-b'}">
77
76
  <div
78
- class="border-r {unifiedBgColor ? unifiedBgColor : ''}"
77
+ class="border-r"
79
78
  style="width: 100vw; max-width: 40px"
80
79
  ></div>
81
80
  <div class="flex-1" style="width: {tableHeaderWidth - 40}px;">
@@ -92,7 +91,6 @@
92
91
  showHeader={false}
93
92
  showFooter={false}
94
93
  showDelete={child.type === "fk"}
95
- {unifiedBgColor}
96
94
  tableProps={{ showLastRowBorder: false, showLastColumnBorder: false, showCheckboxes: false }}
97
95
  />
98
96
  </ExtensionsComponents>
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import * as _ from "lodash-es";
3
3
 
4
- import * as Popover from "../../components/ui/popover/index.js";
4
+ import * as Popover from "../ui/popover/index.js";
5
5
  import { ArrowDown, ArrowUp, GripVertical, Plus, X } from "lucide-svelte";
6
6
  import Button, { buttonVariants } from "../ui/button/button.svelte";
7
7
  import Label from "../ui/label/label.svelte";
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
- import * as Popover from "../../components/ui/popover/index.js";
2
+ import * as Popover from "../ui/popover/index.js";
3
3
  import { ArrowDownWideNarrow } from "lucide-svelte";
4
- import { buttonVariants } from "../../components/ui/button";
4
+ import { buttonVariants } from "../ui/button";
5
5
  import Sort from "./sort.svelte";
6
6
 
7
7
  interface Props {
@@ -38,7 +38,6 @@
38
38
 
39
39
  // other
40
40
  parentWidth?: number;
41
- unifiedBgColor?: "bg-muted/30" | "bg-background";
42
41
  select?: Select;
43
42
  tableWidth?: number;
44
43
  }
@@ -77,7 +76,6 @@
77
76
  tools,
78
77
  rowActions,
79
78
  collapsible,
80
- unifiedBgColor,
81
79
  select,
82
80
  tableWidth = $bindable(),
83
81
  }: TableProps = $props();
@@ -184,7 +182,7 @@
184
182
  flex items-center p-2.5 text-xs h-10
185
183
  border-r border-b gap-2
186
184
  {headerBorderTop ? 'border-t' : ''}
187
- {unifiedBgColor ? unifiedBgColor : 'bg-muted'}
185
+ bg-muted-soft
188
186
  "
189
187
  >
190
188
  <!-- collapsable toggle -->
@@ -209,7 +207,7 @@
209
207
  class="
210
208
  sticky top-0 z-10
211
209
  flex items-center p-2.5 text-xs h-10
212
- {unifiedBgColor ? unifiedBgColor : 'bg-muted'}
210
+ bg-muted-soft
213
211
  {lastColumn && !showLastColumnBorder ? '' : 'border-r'}
214
212
  border-b gap-2
215
213
  {headerBorderTop ? 'border-t' : ''}
@@ -236,7 +234,7 @@
236
234
  class="
237
235
  sticky top-0 right-0 z-20
238
236
  flex items-center p-2.5 h-10
239
- {unifiedBgColor ? unifiedBgColor : 'bg-muted'}
237
+ bg-muted-soft
240
238
  border-l border-b
241
239
  {headerBorderTop ? 'border-t' : ''}
242
240
  "
@@ -251,7 +249,7 @@
251
249
  class="
252
250
  sticky left-0
253
251
  flex items-center p-2.5 text-xs h-10
254
- {unifiedBgColor ? unifiedBgColor : 'bg-background'}
252
+ bg-background
255
253
  border-r gap-2
256
254
  "
257
255
  >
@@ -298,7 +296,7 @@
298
296
  class="
299
297
  flex items-center p-2.5 text-xs h-10 text-nowrap overflow-clip
300
298
  {select ? 'cursor-pointer' : ''}
301
- {unifiedBgColor ? unifiedBgColor : 'bg-background'}
299
+ bg-background
302
300
  {lastColumn && !showLastColumnBorder ? '' : 'border-r'}
303
301
  "
304
302
  >
@@ -315,7 +313,7 @@
315
313
  sticky right-0 z-10
316
314
  flex items-center p-2.5 text-xs h-10
317
315
  border-l gap-2
318
- {unifiedBgColor ? unifiedBgColor : 'bg-background'}
316
+ bg-background
319
317
  "
320
318
  >
321
319
  {@render rowActions?.(entry, index)}
@@ -336,8 +334,8 @@
336
334
  {expandedRows[index] ? '' : 'height: 0px;'}
337
335
  "
338
336
  class="
339
- sticky left-0 top-0 overflow-auto bg-muted/30
340
- {unifiedBgColor ? unifiedBgColor : ''}
337
+ sticky left-0 top-0 overflow-auto bg-muted-soft
338
+
341
339
  {expandedRows[index] ? 'border-t' : ''}
342
340
  "
343
341
  >