@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,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>
@@ -0,0 +1,8 @@
1
+ interface Props {
2
+ collectionName: string;
3
+ entry: Record<string, any>;
4
+ fieldsErrors?: Record<string, string[]>;
5
+ }
6
+ declare const DetailView: import("svelte").Component<Props, {}, "entry">;
7
+ type DetailView = ReturnType<typeof DetailView>;
8
+ export default DetailView;
@@ -86,7 +86,7 @@
86
86
  {:else if field.label === "id"}
87
87
  <Input
88
88
  placeholder="AUTO GENERATED"
89
- class="bg-muted/30 text-xs"
89
+ class="bg-muted-soft text-xs"
90
90
  bind:value
91
91
  />
92
92
  {:else if fieldRelationTarget && entry}
@@ -126,7 +126,7 @@
126
126
  >
127
127
  <Select.Trigger
128
128
  class="
129
- h-9 w-full bg-muted/30 pr-8
129
+ h-9 w-full bg-muted-soft pr-8
130
130
  {destructive ? 'border-destructive bg-destructive/10' : ''}
131
131
  "
132
132
  >
@@ -158,7 +158,7 @@
158
158
  placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
159
159
  type="text"
160
160
  class="
161
- bg-muted/30 text-xs
161
+ bg-muted-soft text-xs
162
162
  {destructive ? 'border-destructive bg-destructive/10' : ''}
163
163
  "
164
164
  bind:value
@@ -168,7 +168,7 @@
168
168
  placeholder={ui?.placeholder ? ui.placeholder : value === "" ? "EMPTY STRING" : "NULL"}
169
169
  rows={5}
170
170
  class="
171
- bg-muted/30 text-xs
171
+ bg-muted-soft text-xs
172
172
  {destructive ? 'border-destructive bg-destructive/10' : ''}
173
173
  "
174
174
  bind:value
@@ -178,7 +178,7 @@
178
178
  type="date"
179
179
  placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
180
180
  class="
181
- dateInput block w-full bg-muted/30 pr-9 text-xs
181
+ dateInput block w-full bg-muted-soft pr-9 text-xs
182
182
  {destructive ? 'border-destructive bg-destructive/10' : ''}
183
183
  "
184
184
  bind:value={
@@ -197,7 +197,7 @@
197
197
  type="time"
198
198
  placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
199
199
  class="
200
- dateInput block w-full bg-muted/30 pr-9 text-xs
200
+ dateInput block w-full bg-muted-soft pr-9 text-xs
201
201
  {destructive ? 'border-destructive bg-destructive/10' : ''}
202
202
  "
203
203
  bind:value={
@@ -215,7 +215,7 @@
215
215
  type="datetime-local"
216
216
  placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
217
217
  class="
218
- dateInput block w-full bg-muted/30 pr-9 text-xs
218
+ dateInput block w-full bg-muted-soft pr-9 text-xs
219
219
  {destructive ? 'border-destructive bg-destructive/10' : ''}
220
220
  "
221
221
  bind:value={
@@ -236,7 +236,7 @@
236
236
  <Select.Trigger
237
237
  placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
238
238
  class="
239
- bg-muted/30 pr-9
239
+ bg-muted-soft pr-9
240
240
  {destructive ? 'border-destructive bg-destructive/10' : ''}
241
241
  "
242
242
  >
@@ -263,7 +263,7 @@
263
263
  type="number"
264
264
  step="any"
265
265
  class="
266
- bg-muted/30 text-xs
266
+ bg-muted-soft text-xs
267
267
  {destructive ? 'border-destructive bg-destructive/10' : ''}
268
268
  "
269
269
  bind:value
@@ -273,7 +273,7 @@
273
273
  placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
274
274
  type="number"
275
275
  class="
276
- bg-muted/30 text-xs
276
+ bg-muted-soft text-xs
277
277
  {destructive ? 'border-destructive bg-destructive/10' : ''}
278
278
  "
279
279
  bind:value
@@ -49,7 +49,7 @@
49
49
  placeholder={field.placeholder ? field.placeholder : "NULL"}
50
50
  type="text"
51
51
  class="
52
- bg-muted/30 text-xs
52
+ bg-muted-soft text-xs
53
53
  {destructive ? 'border-destructive bg-destructive/10' : ''}
54
54
  "
55
55
  bind:value
@@ -64,7 +64,7 @@
64
64
  <Select.Trigger
65
65
  placeholder={field.placeholder ? field.placeholder : "NULL"}
66
66
  class="
67
- h-9 w-full bg-muted/30 pr-8
67
+ h-9 w-full bg-muted-soft pr-8
68
68
  {destructive ? 'border-destructive bg-destructive/10' : ''}
69
69
  "
70
70
  >
@@ -84,7 +84,7 @@
84
84
  placeholder={field.placeholder ? field.placeholder : value === "" ? "EMPTY STRING" : "NULL"}
85
85
  rows={5}
86
86
  class="
87
- bg-muted/30 text-xs
87
+ bg-muted-soft text-xs
88
88
  {destructive ? 'border-destructive bg-destructive/10' : ''}
89
89
  "
90
90
  bind:value
@@ -98,7 +98,7 @@
98
98
  <Input
99
99
  type="date"
100
100
  class="
101
- dateInput block w-full bg-muted/30 pr-9 text-xs
101
+ dateInput block w-full bg-muted-soft pr-9 text-xs
102
102
  {destructive ? 'border-destructive bg-destructive/10' : ''}
103
103
  "
104
104
  bind:value={
@@ -116,7 +116,7 @@
116
116
  <Input
117
117
  type="time"
118
118
  class="
119
- dateInput block w-full bg-muted/30 pr-9 text-xs
119
+ dateInput block w-full bg-muted-soft pr-9 text-xs
120
120
  {destructive ? 'border-destructive bg-destructive/10' : ''}
121
121
  "
122
122
  bind:value={
@@ -133,7 +133,7 @@
133
133
  <Input
134
134
  type="datetime-local"
135
135
  class="
136
- dateInput block w-full bg-muted/30 pr-9 text-xs
136
+ dateInput block w-full bg-muted-soft pr-9 text-xs
137
137
  {destructive ? 'border-destructive bg-destructive/10' : ''}
138
138
  "
139
139
  bind:value={
@@ -154,7 +154,7 @@
154
154
  <Select.Root type="single" bind:value>
155
155
  <Select.Trigger
156
156
  class="
157
- bg-muted/30 pr-9
157
+ bg-muted-soft pr-9
158
158
  {destructive ? 'border-destructive bg-destructive/10' : ''}
159
159
  "
160
160
  >
@@ -21,7 +21,7 @@
21
21
  <Input
22
22
  type="password"
23
23
  placeholder="••••••"
24
- class="bg-muted/30 text-xs {destructive ? 'border-destructive bg-destructive/10' : ''}"
24
+ class="bg-muted-soft text-xs {destructive ? 'border-destructive bg-destructive/10' : ''}"
25
25
  value={displayValue}
26
26
  oninput={onInput}
27
27
  />
@@ -22,67 +22,70 @@
22
22
  import Button from "../../ui/button/button.svelte";
23
23
  import { getStudioContext } from "../../../context";
24
24
  import { toast } from "svelte-sonner";
25
- import ExtensionsComponents from "../../extensionsComponents.svelte";
26
- import { getExtensionUtils } from "../../../extensions/extensionUtils";
27
25
  import { untrack } from "svelte";
28
26
 
29
27
  const { lobb, ctx } = getStudioContext();
30
28
  import { getChangedProperties } from "../../../utils";
31
- import { getField, getFieldIcon } from "../../dataTable/utils";
32
29
  import DetailViewChildren from "./detailViewChildren.svelte";
33
30
  import type { Snippet } from "svelte";
34
31
  import { getDefaultEntry } from "../utils";
35
32
  import type { Changes, ChildrenChanges } from "../utils";
36
- import FieldInput from "../fieldInput.svelte";
33
+ import DetailView from "../detailView.svelte";
37
34
  import Drawer from "../../drawer.svelte";
38
35
 
39
36
  let {
40
37
  collectionName,
41
- values = {},
38
+ values: passedValues = {} as Record<string, any>,
42
39
  showRelatedRecords = true,
43
40
  onCancel,
44
41
  onSuccessfullSave,
45
42
  title,
46
43
  submitButton,
47
44
  recordId,
48
- changes = $bindable<Changes | undefined>(undefined),
45
+ changes: passedChanges = $bindable<Changes | undefined>(undefined),
49
46
  }: UpdateDetailViewProp = $props();
50
47
 
51
- // Internal changes — used when not in recording mode, passed down to children
52
- let _changes = $state<Changes>({ data: {}, children: {} });
53
-
54
48
  // Recording mode = changes was passed from a parent component
55
- const isRecordingMode = $derived(changes !== undefined);
49
+ const isRecordingMode = passedChanges !== undefined;
50
+ if (!isRecordingMode) passedChanges = { data: {}, children: {} };
51
+ const changes = passedChanges as Changes;
56
52
 
57
53
  const fieldNames = Object.keys(ctx.meta.collections[collectionName].fields);
58
- let entry: Record<string, any> = $state(
59
- getDefaultEntry(ctx, fieldNames, collectionName, values),
60
- );
61
- const initialEntry = $state.snapshot(entry);
54
+ let values = $state(getDefaultEntry(ctx, fieldNames, collectionName, passedValues));
55
+ const initialValues = $state.snapshot(values);
62
56
  let fieldsErrors: Record<string, any> = $state({});
63
57
 
64
- // Tracks field edits into the active changes object.
58
+ const hasChanges = $derived(
59
+ Object.keys(changes.data).length > 0 ||
60
+ Object.values(changes.children).some(
61
+ (ch: ChildrenChanges) => ch.created.length || ch.updated.length || ch.deleted.length || ch.linked.length || ch.unlinked.length,
62
+ ),
63
+ );
64
+
65
+ // Tracks top-level field edits into changes.data.
65
66
  // Child ops (create/link/unlink/delete) are written directly by DataTable into changes.children.
66
67
  $effect(() => {
67
- const currentEntrySnap = $state.snapshot(entry);
68
+ const currentEntrySnap = $state.snapshot(values);
68
69
 
69
70
  untrack(() => {
70
- const target = changes ?? _changes;
71
- target.data = getChangedProperties(initialEntry, currentEntrySnap);
72
-
73
- if (!isRecordingMode) {
74
- console.log(`[${collectionName}] changes:`, $state.snapshot(target));
75
- }
71
+ changes.data = getChangedProperties(initialValues, currentEntrySnap);
76
72
  });
77
73
  });
78
74
 
75
+ // Separate logging effect — needs its own $effect so it tracks mutations to changes.children.
76
+ $effect(() => {
77
+ if (!isRecordingMode) {
78
+ console.log(`[${collectionName}] changes:`, $state.snapshot(changes));
79
+ }
80
+ });
81
+
79
82
  function buildApiChildren(children: Record<string, ChildrenChanges>): Record<string, any> | undefined {
80
83
  const result: Record<string, any> = {};
81
84
  for (const [collection, ops] of Object.entries(children)) {
82
85
  const hasOps = ops.created.length || ops.deleted.length || ops.linked.length || ops.unlinked.length;
83
86
  if (!hasOps) continue;
84
87
  result[collection] = {
85
- ...(ops.created.length ? { create: ops.created.map((c) => c.data) } : {}),
88
+ ...(ops.created.length ? { create: ops.created.map((op) => op.data) } : {}),
86
89
  ...(ops.deleted.length ? { delete: ops.deleted.map((r) => r.id) } : {}),
87
90
  ...(ops.linked.length ? { link: ops.linked.map((r) => r.id) } : {}),
88
91
  ...(ops.unlinked.length ? { unlink: ops.unlinked.map((r) => r.id) } : {}),
@@ -92,7 +95,7 @@
92
95
  }
93
96
 
94
97
  function handleCancel() {
95
- if (changes !== undefined) {
98
+ if (isRecordingMode) {
96
99
  changes.data = {};
97
100
  changes.children = {};
98
101
  }
@@ -100,8 +103,7 @@
100
103
  }
101
104
 
102
105
  async function handleSave() {
103
- const target = changes ?? _changes;
104
- const snap = $state.snapshot(target);
106
+ const snap = $state.snapshot(changes);
105
107
  const { id: _id, ...data } = snap.data;
106
108
  const children = buildApiChildren(snap.children);
107
109
 
@@ -121,6 +123,7 @@
121
123
  fieldsErrors = result.details;
122
124
  return;
123
125
  } else if (result.message) {
126
+ toast.error(result.message);
124
127
  return;
125
128
  }
126
129
  }
@@ -128,7 +131,7 @@
128
131
 
129
132
  // Real mode: also fire separate update requests for edited children
130
133
  if (!isRecordingMode) {
131
- for (const [collection, ops] of Object.entries(snap.children)) {
134
+ for (const [collection, ops] of Object.entries(snap.children) as [string, ChildrenChanges][]) {
132
135
  for (const updated of ops.updated) {
133
136
  await lobb.updateOne(collection, String(updated.id), updated.data);
134
137
  }
@@ -139,14 +142,6 @@
139
142
  toast.success(`The record was successfully updated`);
140
143
  onCancel?.();
141
144
  }
142
-
143
- const activeChanges = $derived(changes ?? _changes);
144
- const hasChanges = $derived(
145
- Object.keys(activeChanges.data).length > 0 ||
146
- Object.values(activeChanges.children).some(
147
- (c) => c.created.length || c.updated.length || c.deleted.length || c.linked.length || c.unlinked.length,
148
- ),
149
- );
150
145
  </script>
151
146
 
152
147
  <Drawer onHide={handleCancel}>
@@ -169,41 +164,9 @@
169
164
  </div>
170
165
  </div>
171
166
  <div class="flex-1 overflow-y-auto">
172
- <div class="flex flex-col gap-4 p-4">
173
- {#each fieldNames as fieldName}
174
- {#if !ctx.meta.collections[collectionName].fields[fieldName]?.ui?.hidden}
175
- {@const field = getField(ctx, fieldName, collectionName)}
176
- {@const FieldIcon = getFieldIcon(ctx, fieldName, collectionName)}
177
- <div class="flex flex-col gap-2">
178
- <div class="flex flex-1 items-end justify-between gap-2 text-xs">
179
- <div class="flex gap-2">
180
- <div class="h-fit">{field.label}</div>
181
- <div class="flex h-fit items-center gap-1 text-[0.7rem] text-muted-foreground">
182
- <FieldIcon size="12" />
183
- {field.type}
184
- </div>
185
- </div>
186
- <div>
187
- <ExtensionsComponents
188
- name="dvFields.topRight.{collectionName}.{fieldName}"
189
- utils={getExtensionUtils(lobb, ctx)}
190
- bind:value={entry[fieldName]}
191
- />
192
- </div>
193
- </div>
194
- <FieldInput
195
- {collectionName}
196
- {fieldName}
197
- bind:value={entry[fieldName]}
198
- bind:entry
199
- errorMessages={fieldsErrors[fieldName]}
200
- />
201
- </div>
202
- {/if}
203
- {/each}
204
- </div>
167
+ <DetailView {collectionName} bind:entry={values} {fieldsErrors} />
205
168
  {#if showRelatedRecords}
206
- <DetailViewChildren {collectionName} {entry} {activeChanges} />
169
+ <DetailViewChildren {collectionName} entry={values} activeChanges={changes} />
207
170
  {/if}
208
171
  </div>
209
172
  <div class="flex h-12 items-center justify-end gap-2 border-t px-4">
@@ -96,7 +96,7 @@
96
96
  });
97
97
  </script>
98
98
 
99
- <div class={cn("w-full resize-y rounded-md border bg-muted/30 shadow-sm", className)}>
99
+ <div class={cn("w-full resize-y rounded-md border bg-muted-soft shadow-sm", className)}>
100
100
  <div
101
101
  bind:this={editorContainer}
102
102
  class="editor pl-2"
@@ -26,7 +26,9 @@
26
26
 
27
27
  {#if Components.length}
28
28
  {#each Components as Component}
29
- <Component bind:value {...props} />
29
+ <Component bind:value {...props}>
30
+ {#if children}{@render children()}{/if}
31
+ </Component>
30
32
  {/each}
31
33
  {:else}
32
34
  {@render children?.()}
@@ -85,7 +85,7 @@
85
85
  placeholder={"NULL"}
86
86
  type="number"
87
87
  class="
88
- bg-muted/30 text-xs
88
+ bg-muted-soft text-xs
89
89
  {destructive ? 'border-destructive bg-destructive/10' : ''}
90
90
  "
91
91
  bind:value={
@@ -98,7 +98,7 @@
98
98
  <div class="relative z-10">
99
99
  <Input
100
100
  placeholder={"PARENT ID"}
101
- class="bg-muted/30 text-xs"
101
+ class="bg-muted-soft text-xs"
102
102
  disabled={true}
103
103
  />
104
104
  </div>