@lobb-js/studio 0.29.0 → 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 (109) hide show
  1. package/README.md +1 -0
  2. package/dist/actions.d.ts +2 -0
  3. package/dist/components/Studio.svelte +39 -43
  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/dataTable/dataTable.svelte +35 -20
  9. package/dist/components/dataTable/dataTable.svelte.d.ts +2 -1
  10. package/dist/components/dataTable/dataTableTabs.svelte +4 -2
  11. package/dist/components/dataTable/dataTableTabs.svelte.d.ts +2 -0
  12. package/dist/components/dataTable/header.svelte +15 -11
  13. package/dist/components/dataTable/header.svelte.d.ts +1 -0
  14. package/dist/components/dataTable/listViewChildren.svelte +4 -6
  15. package/dist/components/dataTable/listViewChildren.svelte.d.ts +0 -1
  16. package/dist/components/dataTable/table.svelte +8 -10
  17. package/dist/components/dataTable/table.svelte.d.ts +0 -1
  18. package/dist/components/dataTableDrawer/dataTableDrawer.svelte +4 -1
  19. package/dist/components/dataTableDrawer/dataTableDrawer.svelte.d.ts +2 -0
  20. package/dist/components/detailView/create/children.svelte +1 -1
  21. package/dist/components/detailView/create/createDetailView.svelte +19 -61
  22. package/dist/components/detailView/create/createManyView.svelte +2 -4
  23. package/dist/components/detailView/detailView.svelte +81 -0
  24. package/dist/components/detailView/detailView.svelte.d.ts +8 -0
  25. package/dist/components/detailView/fieldInput.svelte +10 -10
  26. package/dist/components/detailView/fieldInputReplacement.svelte +7 -7
  27. package/dist/components/detailView/passwordInput.svelte +1 -1
  28. package/dist/components/detailView/update/updateDetailView.svelte +32 -69
  29. package/dist/components/diffViewer.svelte +1 -1
  30. package/dist/components/extensionsComponents.svelte +3 -1
  31. package/dist/components/foreingKeyInput.svelte +2 -2
  32. package/dist/components/importButton.svelte +12 -9
  33. package/dist/components/landing.svelte +7 -0
  34. package/dist/components/landing.svelte.d.ts +6 -14
  35. package/dist/components/miniSidebar.svelte +86 -15
  36. package/dist/components/miniSidebar.svelte.d.ts +2 -17
  37. package/dist/components/polymorphicInput.svelte +1 -1
  38. package/dist/components/rangeCalendarButton.svelte +10 -10
  39. package/dist/components/richTextEditor.svelte +1 -1
  40. package/dist/components/routes/collections/collections.svelte +32 -10
  41. package/dist/components/routes/data_model/dataModel.svelte +6 -28
  42. package/dist/components/routes/data_model/dataModel.svelte.d.ts +17 -2
  43. package/dist/components/routes/extensions/publicExtension.svelte +19 -0
  44. package/dist/components/routes/extensions/publicExtension.svelte.d.ts +13 -0
  45. package/dist/components/routes/home.svelte +2 -2
  46. package/dist/components/routes/workflows/workflows.svelte +4 -4
  47. package/dist/components/sidebar/sidebar.svelte +1 -1
  48. package/dist/components/sidebar/sidebarElements.svelte +4 -4
  49. package/dist/components/singletone.svelte +4 -6
  50. package/dist/components/ui/button/button.svelte +2 -3
  51. package/dist/components/workflowEditor.svelte +2 -2
  52. package/dist/eventSystem.d.ts +1 -1
  53. package/dist/eventSystem.js +7 -5
  54. package/dist/extensions/extension.types.d.ts +38 -14
  55. package/dist/extensions/extensionUtils.js +4 -2
  56. package/dist/index.d.ts +3 -1
  57. package/dist/index.js +3 -1
  58. package/dist/store.types.d.ts +1 -1
  59. package/dist/studioLifecycle.svelte.d.ts +2 -0
  60. package/dist/studioLifecycle.svelte.js +15 -0
  61. package/package.json +3 -4
  62. package/src/app.css +3 -0
  63. package/src/lib/actions.ts +2 -0
  64. package/src/lib/components/Studio.svelte +39 -43
  65. package/src/lib/components/StudioRoot.svelte +19 -0
  66. package/src/lib/components/breadCrumbs.svelte +5 -4
  67. package/src/lib/components/codeEditor.svelte +1 -1
  68. package/src/lib/components/dataTable/dataTable.svelte +35 -20
  69. package/src/lib/components/dataTable/dataTableTabs.svelte +4 -2
  70. package/src/lib/components/dataTable/header.svelte +15 -11
  71. package/src/lib/components/dataTable/listViewChildren.svelte +4 -6
  72. package/src/lib/components/dataTable/table.svelte +8 -10
  73. package/src/lib/components/dataTableDrawer/dataTableDrawer.svelte +4 -1
  74. package/src/lib/components/detailView/create/children.svelte +1 -1
  75. package/src/lib/components/detailView/create/createDetailView.svelte +19 -61
  76. package/src/lib/components/detailView/create/createManyView.svelte +2 -4
  77. package/src/lib/components/detailView/detailView.svelte +81 -0
  78. package/src/lib/components/detailView/fieldInput.svelte +10 -10
  79. package/src/lib/components/detailView/fieldInputReplacement.svelte +7 -7
  80. package/src/lib/components/detailView/passwordInput.svelte +1 -1
  81. package/src/lib/components/detailView/update/updateDetailView.svelte +32 -69
  82. package/src/lib/components/diffViewer.svelte +1 -1
  83. package/src/lib/components/extensionsComponents.svelte +3 -1
  84. package/src/lib/components/foreingKeyInput.svelte +2 -2
  85. package/src/lib/components/importButton.svelte +12 -9
  86. package/src/lib/components/landing.svelte +7 -0
  87. package/src/lib/components/miniSidebar.svelte +86 -15
  88. package/src/lib/components/polymorphicInput.svelte +1 -1
  89. package/src/lib/components/rangeCalendarButton.svelte +10 -10
  90. package/src/lib/components/richTextEditor.svelte +1 -1
  91. package/src/lib/components/routes/collections/collections.svelte +32 -10
  92. package/src/lib/components/routes/data_model/dataModel.svelte +6 -28
  93. package/src/lib/components/routes/extensions/publicExtension.svelte +19 -0
  94. package/src/lib/components/routes/home.svelte +2 -2
  95. package/src/lib/components/routes/workflows/workflows.svelte +4 -4
  96. package/src/lib/components/sidebar/sidebar.svelte +1 -1
  97. package/src/lib/components/sidebar/sidebarElements.svelte +4 -4
  98. package/src/lib/components/singletone.svelte +4 -6
  99. package/src/lib/components/ui/button/button.svelte +2 -3
  100. package/src/lib/components/workflowEditor.svelte +2 -2
  101. package/src/lib/eventSystem.ts +8 -7
  102. package/src/lib/extensions/extension.types.ts +39 -6
  103. package/src/lib/extensions/extensionUtils.ts +4 -2
  104. package/src/lib/index.ts +3 -1
  105. package/src/lib/store.types.ts +1 -1
  106. package/src/lib/studioLifecycle.svelte.ts +17 -0
  107. package/dist/components/routes/data_model/syncManager.svelte +0 -94
  108. package/dist/components/routes/data_model/syncManager.svelte.d.ts +0 -3
  109. package/src/lib/components/routes/data_model/syncManager.svelte +0 -94
@@ -24,8 +24,11 @@
24
24
  import type { Changes, ChildrenChanges } from "../detailView/utils";
25
25
  import ExtensionsComponents from "../extensionsComponents.svelte";
26
26
  import { getExtensionUtils, loadExtensionComponents } from "../../extensions/extensionUtils";
27
+ import { emitEvent } from "../../eventSystem";
28
+ import { onMount } from "svelte";
27
29
  import Tabs from "./dataTableTabs.svelte";
28
30
  import { fade } from "svelte/transition";
31
+ import type { CollectionTab } from "../../store.types";
29
32
 
30
33
  const { lobb, ctx } = getStudioContext();
31
34
 
@@ -38,9 +41,9 @@
38
41
  showHeader?: boolean;
39
42
  showFooter?: boolean;
40
43
  showImport?: boolean;
41
- unifiedBgColor?: "bg-muted/30" | "bg-background";
42
44
  showDelete?: boolean;
43
45
  tableProps?: Partial<TableProps>;
46
+ tabs?: CollectionTab[];
44
47
  headerLeft?: Snippet<[]>;
45
48
  }
46
49
 
@@ -53,12 +56,26 @@
53
56
  showHeader = true,
54
57
  showFooter = true,
55
58
  showImport = true,
56
- unifiedBgColor,
57
59
  showDelete = false,
58
60
  tableProps,
61
+ tabs,
59
62
  headerLeft,
60
63
  }: Props = $props();
61
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
+
62
79
  function getOrCreateUpdatedSlot(recordId: string): Changes | undefined {
63
80
  if (!changes) return undefined;
64
81
  let slot = changes.updated.find((u) => String(u.id) === String(recordId));
@@ -191,10 +208,7 @@
191
208
 
192
209
  <div
193
210
  bind:clientWidth={dataTableContainerWidth}
194
- class="
195
- flex flex-col overflow-auto h-full w-full
196
- {unifiedBgColor ? unifiedBgColor : ''}
197
- "
211
+ class="flex flex-col overflow-auto h-full w-full"
198
212
  >
199
213
  {#snippet rowActionsSnippet(entry: Record<string, any>)}
200
214
  <ExtensionsComponents
@@ -212,6 +226,7 @@
212
226
  {collectionName}
213
227
  bind:selectedRecords
214
228
  {showImport}
229
+ {showCreate}
215
230
  {parentContext}
216
231
  {changes}
217
232
  >
@@ -220,7 +235,7 @@
220
235
  {/snippet}
221
236
  </Header>
222
237
  {/if}
223
- <Tabs {collectionName} {filter} bind:activeTabFilter />
238
+ <Tabs {collectionName} {filter} {tabs} bind:activeTabFilter />
224
239
  <div class="relative flex-1 overflow-auto w-full">
225
240
  {#key activeTabFilter}
226
241
  <div class="h-full w-full" in:fade={{ duration: 120 }}>
@@ -240,22 +255,23 @@
240
255
  showLastColumnBorder={true}
241
256
  bind:sort={params.sort}
242
257
  bind:selectedRecords
243
- {unifiedBgColor}
244
258
  bind:tableWidth={dataTableWidth}
245
259
  {...tableProps}
246
260
  rowActions={hasRowActions ? rowActionsSnippet : undefined}>
247
261
  {#snippet tools(entry)}
248
- <UpdateDetailViewButton
249
- {collectionName}
250
- recordId={entry.id}
251
- variant="ghost"
252
- class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
253
- Icon={Pencil}
254
- changes={getOrCreateUpdatedSlot(String(entry.id))}
255
- onSuccessfullSave={async () => {
256
- params = { ...params };
257
- }}
258
- ></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}
259
275
  {#if parentContext}
260
276
  <Button
261
277
  class="h-6 w-6 text-muted-foreground hover:bg-transparent"
@@ -293,7 +309,6 @@
293
309
  width={dataTableWidth > dataTableContainerWidth
294
310
  ? dataTableContainerWidth
295
311
  : dataTableWidth}
296
- unifiedBgColor={unifiedBgColor ?? "bg-background"}
297
312
  />
298
313
  {/snippet}
299
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
 
@@ -25,6 +25,7 @@
25
25
  parentContext?: ParentContext;
26
26
  changes?: ChildrenChanges;
27
27
  showImport?: boolean;
28
+ showCreate?: boolean;
28
29
  left?: Snippet<[]>;
29
30
  }
30
31
 
@@ -35,6 +36,7 @@
35
36
  parentContext,
36
37
  changes,
37
38
  showImport = true,
39
+ showCreate = false,
38
40
  left
39
41
  }: Props = $props();
40
42
 
@@ -167,7 +169,7 @@
167
169
  >
168
170
  {headerIsSmall ? "" : "Refresh"}
169
171
  </Button>
170
- {#if showImport}
172
+ {#if showImport && showCreate}
171
173
  <Tooltip.Provider delayDuration={0}>
172
174
  <Tooltip.Root>
173
175
  <Tooltip.Trigger>
@@ -200,15 +202,17 @@
200
202
  {headerIsSmall ? "" : "Link"}
201
203
  </SelectRecord>
202
204
  {/if}
203
- <CreateDetailViewButton
204
- {collectionName}
205
- variant="default"
206
- class="h-7 px-3 text-xs font-normal"
207
- Icon={Plus}
208
- changes={changes ? createChanges : undefined}
209
- onSuccessfullSave={handleCreateSuccess}
210
- >
211
- {headerIsSmall ? "" : "Create"}
212
- </CreateDetailViewButton>
205
+ {#if showCreate}
206
+ <CreateDetailViewButton
207
+ {collectionName}
208
+ variant="default"
209
+ class="h-7 px-3 text-xs font-normal"
210
+ Icon={Plus}
211
+ changes={changes ? createChanges : undefined}
212
+ onSuccessfullSave={handleCreateSuccess}
213
+ >
214
+ {headerIsSmall ? "" : "Create"}
215
+ </CreateDetailViewButton>
216
+ {/if}
213
217
  </div>
214
218
  </div>
@@ -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>
@@ -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
  >
@@ -4,6 +4,7 @@
4
4
  import DataTable from "../dataTable/dataTable.svelte";
5
5
  import Drawer from "../drawer.svelte";
6
6
  import type { TableProps } from "../dataTable/table.svelte";
7
+ import type { CollectionTab } from "../../store.types";
7
8
 
8
9
  interface Props {
9
10
  collectionName: string;
@@ -13,6 +14,7 @@
13
14
  showFooter?: boolean;
14
15
  tableProps?: Partial<TableProps>;
15
16
  position?: "side" | "bottom";
17
+ tabs?: CollectionTab[];
16
18
  onClose?: () => void;
17
19
  }
18
20
 
@@ -24,6 +26,7 @@
24
26
  showFooter = true,
25
27
  tableProps,
26
28
  position = "side",
29
+ tabs,
27
30
  onClose,
28
31
  }: Props = $props();
29
32
  </script>
@@ -47,7 +50,7 @@
47
50
  {showHeader}
48
51
  {showFooter}
49
52
  {tableProps}
50
- unifiedBgColor="bg-background"
53
+ {tabs}
51
54
  />
52
55
  </div>
53
56
  </Drawer>
@@ -37,7 +37,7 @@
37
37
  parentCollectionName={collectionName}
38
38
  collectionName={child.collection}
39
39
  parentRecord={{ id: entry.id, collectionName }}
40
- class="bg-muted/30 border rounded-md overflow-hidden"
40
+ class="bg-muted-soft border rounded-md overflow-hidden"
41
41
  bind:value={entry[child.collection]}
42
42
  >
43
43
  <CreateManyView
@@ -24,53 +24,46 @@
24
24
  import { untrack } from "svelte";
25
25
 
26
26
  const { lobb, ctx } = getStudioContext();
27
- import ExtensionsComponents from "../../extensionsComponents.svelte";
28
- import { getExtensionUtils } from "../../../extensions/extensionUtils";
29
- import { getField, getFieldIcon } from "../../dataTable/utils";
30
27
  import Children from "./children.svelte";
31
28
  import { buildChildren, getDefaultEntry } from "../utils";
32
29
  import type { Changes } from "../utils";
33
30
  import { getChangedProperties } from "../../../utils";
34
31
  import type { Snippet } from "svelte";
35
- import FieldInput from "../fieldInput.svelte";
32
+ import DetailView from "../detailView.svelte";
36
33
  import Drawer from "../../drawer.svelte";
37
34
 
38
35
  let {
39
36
  collectionName,
40
- values = {},
37
+ values: passedValues = {} as Record<string, any>,
41
38
  showRelatedRecords = true,
42
39
  onCancel,
43
40
  onSuccessfullSave,
44
41
  title,
45
42
  submitButton,
46
- changes = $bindable<Changes | undefined>(undefined),
43
+ changes: passedChanges = $bindable<Changes | undefined>(undefined),
47
44
  }: CreateDetailViewProp = $props();
48
45
 
49
- let _changes = $state<Changes>({ data: {}, children: {} });
50
- const isRecordingMode = $derived(changes !== undefined);
46
+ const isRecordingMode = passedChanges !== undefined;
47
+ if (!isRecordingMode) passedChanges = { data: {}, children: {} };
48
+ const changes = passedChanges as Changes;
51
49
 
52
50
  const fieldNames = Object.keys(ctx.meta.collections[collectionName].fields);
53
- let entry: Record<string, any> = $state(
54
- getDefaultEntry(ctx, fieldNames, collectionName, values),
55
- );
56
- const initialEntry = $state.snapshot(entry);
51
+ let values = $state(getDefaultEntry(ctx, fieldNames, collectionName, passedValues));
57
52
  let fieldsErrors: Record<string, any> = $state({});
58
53
 
59
54
  const childCollections = ctx.meta.relations
60
55
  .filter((r) => r.to.collection === collectionName)
61
56
  .map((r) => (r as any).from.collection);
62
57
 
63
- const subCollections = childCollections;
64
58
  const subCollectionsValues: Record<string, any> = {};
65
- for (const col of subCollections) {
66
- if (values[col]) subCollectionsValues[col] = values[col];
59
+ for (const col of childCollections) {
60
+ if (passedValues[col]) subCollectionsValues[col] = passedValues[col];
67
61
  }
68
62
 
69
63
  $effect(() => {
70
- const snap = $state.snapshot(entry);
64
+ const snap = $state.snapshot(values);
71
65
 
72
66
  untrack(() => {
73
- const target = changes ?? _changes;
74
67
  const data: Record<string, any> = {};
75
68
  const children: Record<string, any> = {};
76
69
 
@@ -88,17 +81,17 @@
88
81
  }
89
82
  }
90
83
 
91
- target.data = data;
92
- target.children = children;
84
+ changes.data = data;
85
+ changes.children = children;
93
86
 
94
87
  if (!isRecordingMode) {
95
- console.log(`[${collectionName}] changes:`, $state.snapshot(target));
88
+ console.log(`[${collectionName}] changes:`, $state.snapshot(changes));
96
89
  }
97
90
  });
98
91
  });
99
92
 
100
93
  function handleCancel() {
101
- if (changes !== undefined) {
94
+ if (isRecordingMode) {
102
95
  changes.data = {};
103
96
  changes.children = {};
104
97
  }
@@ -106,14 +99,13 @@
106
99
  }
107
100
 
108
101
  async function handleSave() {
109
- const target = changes ?? _changes;
110
- const snap = $state.snapshot(target);
102
+ const snap = $state.snapshot(changes);
111
103
 
112
104
  const children = buildChildren(ctx, collectionName, { ...snap.data, ...Object.fromEntries(
113
105
  Object.entries(snap.children).map(([col, ops]) => [
114
106
  col,
115
107
  [
116
- ...(ops.created.map((c) => c.data)),
108
+ ...(ops.created.map((op) => op.data)),
117
109
  ...(ops.linked.map((id) => ({ id }))),
118
110
  ]
119
111
  ])
@@ -135,6 +127,7 @@
135
127
  fieldsErrors = result.details;
136
128
  return;
137
129
  } else if (result.message) {
130
+ toast.error(result.message);
138
131
  return;
139
132
  }
140
133
  }
@@ -166,44 +159,9 @@
166
159
  </div>
167
160
  </div>
168
161
  <div class="flex-1 overflow-y-auto">
169
- <div class="flex flex-col gap-4 p-4">
170
- {#each fieldNames as fieldName}
171
- {#if !ctx.meta.collections[collectionName].fields[fieldName]?.ui?.hidden}
172
- {@const field = getField(ctx, fieldName, collectionName)}
173
- {@const FieldIcon = getFieldIcon(ctx, fieldName, collectionName)}
174
- <div class="flex flex-col gap-2">
175
- <div class="flex flex-1 items-end justify-between gap-2 text-xs">
176
- <div class="flex gap-2">
177
- <div class="h-fit">{field.label}</div>
178
- <div class="flex h-fit items-center gap-1 text-[0.7rem] text-muted-foreground">
179
- <FieldIcon size="12" />
180
- {field.type}
181
- </div>
182
- </div>
183
- <div>
184
- <ExtensionsComponents
185
- name="dvFields.topRight.{collectionName}.{fieldName}"
186
- utils={getExtensionUtils(lobb, ctx)}
187
- bind:value={entry[fieldName]}
188
- />
189
- </div>
190
- </div>
191
- <FieldInput
192
- {collectionName}
193
- {fieldName}
194
- bind:value={
195
- () => entry[fieldName],
196
- (v) => (entry = { ...entry, [fieldName]: v })
197
- }
198
- bind:entry
199
- errorMessages={fieldsErrors[fieldName]}
200
- />
201
- </div>
202
- {/if}
203
- {/each}
204
- </div>
162
+ <DetailView {collectionName} bind:entry={values} {fieldsErrors} />
205
163
  {#if showRelatedRecords}
206
- <Children {collectionName} values={subCollectionsValues} bind:entry />
164
+ <Children {collectionName} values={subCollectionsValues} bind:entry={values} />
207
165
  {/if}
208
166
  </div>
209
167
  <div class="flex h-12 items-center justify-end gap-2 border-t px-4">
@@ -112,7 +112,7 @@
112
112
  >
113
113
  <div
114
114
  class="
115
- flex items-center justify-between px-2 h-10 bg-muted/30
115
+ flex items-center justify-between px-2 h-10 bg-muted-soft
116
116
  {expanded ? 'border-b' : ''}
117
117
  "
118
118
  >
@@ -165,13 +165,12 @@
165
165
  </div>
166
166
  </div>
167
167
  {#if expanded}
168
- <div bind:clientWidth={tableWidth} class="bg-muted/30 overflow-auto">
168
+ <div bind:clientWidth={tableWidth} class="bg-muted-soft overflow-auto">
169
169
  <Table
170
170
  data={entries}
171
171
  {columns}
172
172
  selectByColumn="id"
173
173
  showCollapsible={doesCollectionHasChildren}
174
- unifiedBgColor="bg-muted/30"
175
174
  >
176
175
  {#snippet tools(entry, index)}
177
176
  <Button
@@ -240,7 +239,6 @@
240
239
  {collectionName}
241
240
  recordId={entry.id}
242
241
  width={tableWidth}
243
- unifiedBgColor="bg-muted/30"
244
242
  />
245
243
  {:else}
246
244
  <SubRecords
@@ -0,0 +1,81 @@
1
+ <script lang="ts">
2
+ import { CircleHelp } from "lucide-svelte";
3
+ import * as Tooltip from "../ui/tooltip";
4
+ import { getStudioContext } from "../../context";
5
+ import ExtensionsComponents from "../extensionsComponents.svelte";
6
+ import { getExtensionUtils } from "../../extensions/extensionUtils";
7
+ import { getField, getFieldIcon } from "../dataTable/utils";
8
+ import FieldInput from "./fieldInput.svelte";
9
+
10
+ interface Props {
11
+ collectionName: string;
12
+ entry: Record<string, any>;
13
+ fieldsErrors?: Record<string, string[]>;
14
+ }
15
+
16
+ let {
17
+ collectionName,
18
+ entry = $bindable(),
19
+ fieldsErrors = {},
20
+ }: Props = $props();
21
+
22
+ const { lobb, ctx } = getStudioContext();
23
+ const fieldNames = $derived(
24
+ Object.keys(ctx.meta.collections[collectionName].fields),
25
+ );
26
+ </script>
27
+
28
+ <div class="flex flex-col gap-4 p-4">
29
+ {#each fieldNames as fieldName}
30
+ {#if !ctx.meta.collections[collectionName].fields[fieldName]?.ui?.hidden}
31
+ {@const field = getField(ctx, fieldName, collectionName)}
32
+ {@const FieldIcon = getFieldIcon(ctx, fieldName, collectionName)}
33
+ {@const description = ctx.meta.collections[collectionName].fields[fieldName]?.description}
34
+ <div class="flex flex-col gap-2">
35
+ <div class="flex flex-1 items-end justify-between gap-2 text-xs">
36
+ <div class="flex items-center gap-1.5">
37
+ <ExtensionsComponents
38
+ name="detailView.field.label"
39
+ utils={getExtensionUtils(lobb, ctx)}
40
+ {collectionName}
41
+ {fieldName}
42
+ bind:value={entry[fieldName]}
43
+ >
44
+ <div class="flex items-center gap-1.5">
45
+ <div class="h-fit">{field.label}</div>
46
+ <div class="flex h-fit items-center gap-1 text-[0.7rem] text-muted-foreground">
47
+ <FieldIcon size="12" />
48
+ {field.type}
49
+ </div>
50
+ </div>
51
+ </ExtensionsComponents>
52
+ {#if description}
53
+ <Tooltip.Root>
54
+ <Tooltip.Trigger>
55
+ <CircleHelp size="12" class="text-muted-foreground" />
56
+ </Tooltip.Trigger>
57
+ <Tooltip.Content class="max-w-64 text-xs">
58
+ {description}
59
+ </Tooltip.Content>
60
+ </Tooltip.Root>
61
+ {/if}
62
+ </div>
63
+ <div>
64
+ <ExtensionsComponents
65
+ name="dvFields.topRight.{collectionName}.{fieldName}"
66
+ utils={getExtensionUtils(lobb, ctx)}
67
+ bind:value={entry[fieldName]}
68
+ />
69
+ </div>
70
+ </div>
71
+ <FieldInput
72
+ {collectionName}
73
+ {fieldName}
74
+ bind:value={entry[fieldName]}
75
+ bind:entry
76
+ errorMessages={fieldsErrors[fieldName]}
77
+ />
78
+ </div>
79
+ {/if}
80
+ {/each}
81
+ </div>