@lobb-js/studio 0.7.1 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/package.json +3 -2
  2. package/src/App.svelte +5 -0
  3. package/src/app.css +124 -0
  4. package/src/lib/components/LlmButton.svelte +137 -0
  5. package/src/lib/components/Studio.svelte +129 -0
  6. package/src/lib/components/alertView.svelte +20 -0
  7. package/src/lib/components/breadCrumbs.svelte +60 -0
  8. package/src/lib/components/codeEditor.svelte +152 -0
  9. package/src/lib/components/combobox.svelte +92 -0
  10. package/src/lib/components/confirmationDialog/confirmationDialog.svelte +33 -0
  11. package/src/lib/components/confirmationDialog/store.svelte.ts +28 -0
  12. package/src/lib/components/createManyButton.svelte +109 -0
  13. package/src/lib/components/dataTable/childRecords.svelte +142 -0
  14. package/src/lib/components/dataTable/dataTable.svelte +225 -0
  15. package/src/lib/components/dataTable/fieldCell.svelte +77 -0
  16. package/src/lib/components/dataTable/filter.svelte +284 -0
  17. package/src/lib/components/dataTable/filterButton.svelte +39 -0
  18. package/src/lib/components/dataTable/footer.svelte +84 -0
  19. package/src/lib/components/dataTable/header.svelte +155 -0
  20. package/src/lib/components/dataTable/sort.svelte +173 -0
  21. package/src/lib/components/dataTable/sortButton.svelte +36 -0
  22. package/src/lib/components/dataTable/table.svelte +337 -0
  23. package/src/lib/components/dataTable/utils.ts +127 -0
  24. package/src/lib/components/detailView/create/children.svelte +70 -0
  25. package/src/lib/components/detailView/create/createDetailView.svelte +228 -0
  26. package/src/lib/components/detailView/create/createDetailViewButton.svelte +37 -0
  27. package/src/lib/components/detailView/create/createManyView.svelte +252 -0
  28. package/src/lib/components/detailView/create/subRecords.svelte +50 -0
  29. package/src/lib/components/detailView/detailViewForm.svelte +104 -0
  30. package/src/lib/components/detailView/fieldCustomInput.svelte +26 -0
  31. package/src/lib/components/detailView/fieldInput.svelte +258 -0
  32. package/src/lib/components/detailView/fieldInputReplacement.svelte +199 -0
  33. package/src/lib/components/detailView/store.svelte.ts +59 -0
  34. package/src/lib/components/detailView/update/children.svelte +96 -0
  35. package/src/lib/components/detailView/update/updateDetailView.svelte +176 -0
  36. package/src/lib/components/detailView/update/updateDetailViewButton.svelte +56 -0
  37. package/src/lib/components/detailView/utils.ts +176 -0
  38. package/src/lib/components/diffViewer.svelte +105 -0
  39. package/src/lib/components/drawer.svelte +28 -0
  40. package/src/lib/components/extensionsComponents.svelte +33 -0
  41. package/src/lib/components/foreingKeyInput.svelte +80 -0
  42. package/src/lib/components/header.svelte +45 -0
  43. package/src/lib/components/loadingTypesForMonacoEditor.ts +36 -0
  44. package/src/lib/components/miniSidebar.svelte +226 -0
  45. package/src/lib/components/rangeCalendarButton.svelte +257 -0
  46. package/src/lib/components/richTextEditor.svelte +284 -0
  47. package/src/lib/components/routes/collections/collection.svelte +57 -0
  48. package/src/lib/components/routes/collections/collections.svelte +45 -0
  49. package/src/lib/components/routes/data_model/dataModel.svelte +40 -0
  50. package/src/lib/components/routes/data_model/flow.css +22 -0
  51. package/src/lib/components/routes/data_model/flow.svelte +84 -0
  52. package/src/lib/components/routes/data_model/syncManager.svelte +94 -0
  53. package/src/lib/components/routes/data_model/utils.ts +35 -0
  54. package/src/lib/components/routes/extensions/extension.svelte +19 -0
  55. package/src/lib/components/routes/home.svelte +40 -0
  56. package/src/lib/components/routes/workflows/workflows.svelte +136 -0
  57. package/src/lib/components/selectRecord.svelte +130 -0
  58. package/src/lib/components/setServerPage.svelte +50 -0
  59. package/src/lib/components/sidebar/index.ts +4 -0
  60. package/src/lib/components/sidebar/sidebar.svelte +149 -0
  61. package/src/lib/components/sidebar/sidebarElements.svelte +144 -0
  62. package/src/lib/components/sidebar/sidebarTrigger.svelte +33 -0
  63. package/src/lib/components/singletone.svelte +71 -0
  64. package/src/lib/components/ui/accordion/accordion-content.svelte +22 -0
  65. package/src/lib/components/ui/accordion/accordion-item.svelte +12 -0
  66. package/src/lib/components/ui/accordion/accordion-trigger.svelte +31 -0
  67. package/src/lib/components/ui/accordion/index.ts +17 -0
  68. package/src/lib/components/ui/alert/alert-description.svelte +16 -0
  69. package/src/lib/components/ui/alert/alert-title.svelte +24 -0
  70. package/src/lib/components/ui/alert/alert.svelte +39 -0
  71. package/src/lib/components/ui/alert/index.ts +14 -0
  72. package/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte +13 -0
  73. package/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte +17 -0
  74. package/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte +26 -0
  75. package/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte +16 -0
  76. package/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte +20 -0
  77. package/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte +20 -0
  78. package/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte +19 -0
  79. package/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte +18 -0
  80. package/src/lib/components/ui/alert-dialog/index.ts +40 -0
  81. package/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +23 -0
  82. package/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte +16 -0
  83. package/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte +31 -0
  84. package/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte +23 -0
  85. package/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte +23 -0
  86. package/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte +27 -0
  87. package/src/lib/components/ui/breadcrumb/breadcrumb.svelte +15 -0
  88. package/src/lib/components/ui/breadcrumb/index.ts +25 -0
  89. package/src/lib/components/ui/button/button.svelte +110 -0
  90. package/src/lib/components/ui/button/index.ts +17 -0
  91. package/src/lib/components/ui/checkbox/checkbox.svelte +35 -0
  92. package/src/lib/components/ui/checkbox/index.ts +6 -0
  93. package/src/lib/components/ui/command/command-dialog.svelte +35 -0
  94. package/src/lib/components/ui/command/command-empty.svelte +12 -0
  95. package/src/lib/components/ui/command/command-group.svelte +31 -0
  96. package/src/lib/components/ui/command/command-input.svelte +25 -0
  97. package/src/lib/components/ui/command/command-item.svelte +19 -0
  98. package/src/lib/components/ui/command/command-link-item.svelte +19 -0
  99. package/src/lib/components/ui/command/command-list.svelte +16 -0
  100. package/src/lib/components/ui/command/command-separator.svelte +12 -0
  101. package/src/lib/components/ui/command/command-shortcut.svelte +20 -0
  102. package/src/lib/components/ui/command/command.svelte +21 -0
  103. package/src/lib/components/ui/command/index.ts +40 -0
  104. package/src/lib/components/ui/dialog/dialog-content.svelte +38 -0
  105. package/src/lib/components/ui/dialog/dialog-description.svelte +16 -0
  106. package/src/lib/components/ui/dialog/dialog-footer.svelte +20 -0
  107. package/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
  108. package/src/lib/components/ui/dialog/dialog-overlay.svelte +19 -0
  109. package/src/lib/components/ui/dialog/dialog-title.svelte +16 -0
  110. package/src/lib/components/ui/dialog/index.ts +37 -0
  111. package/src/lib/components/ui/input/index.ts +7 -0
  112. package/src/lib/components/ui/input/input.svelte +46 -0
  113. package/src/lib/components/ui/label/index.ts +7 -0
  114. package/src/lib/components/ui/label/label.svelte +19 -0
  115. package/src/lib/components/ui/popover/index.ts +17 -0
  116. package/src/lib/components/ui/popover/popover-content.svelte +28 -0
  117. package/src/lib/components/ui/range-calendar/index.ts +30 -0
  118. package/src/lib/components/ui/range-calendar/range-calendar-cell.svelte +19 -0
  119. package/src/lib/components/ui/range-calendar/range-calendar-day.svelte +35 -0
  120. package/src/lib/components/ui/range-calendar/range-calendar-grid-body.svelte +12 -0
  121. package/src/lib/components/ui/range-calendar/range-calendar-grid-head.svelte +12 -0
  122. package/src/lib/components/ui/range-calendar/range-calendar-grid-row.svelte +12 -0
  123. package/src/lib/components/ui/range-calendar/range-calendar-grid.svelte +16 -0
  124. package/src/lib/components/ui/range-calendar/range-calendar-head-cell.svelte +16 -0
  125. package/src/lib/components/ui/range-calendar/range-calendar-header.svelte +16 -0
  126. package/src/lib/components/ui/range-calendar/range-calendar-heading.svelte +16 -0
  127. package/src/lib/components/ui/range-calendar/range-calendar-months.svelte +20 -0
  128. package/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte +27 -0
  129. package/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte +27 -0
  130. package/src/lib/components/ui/range-calendar/range-calendar.svelte +57 -0
  131. package/src/lib/components/ui/select/index.ts +34 -0
  132. package/src/lib/components/ui/select/select-content.svelte +38 -0
  133. package/src/lib/components/ui/select/select-group-heading.svelte +16 -0
  134. package/src/lib/components/ui/select/select-item.svelte +37 -0
  135. package/src/lib/components/ui/select/select-scroll-down-button.svelte +19 -0
  136. package/src/lib/components/ui/select/select-scroll-up-button.svelte +19 -0
  137. package/src/lib/components/ui/select/select-separator.svelte +13 -0
  138. package/src/lib/components/ui/select/select-trigger.svelte +24 -0
  139. package/src/lib/components/ui/separator/index.ts +7 -0
  140. package/src/lib/components/ui/separator/separator.svelte +22 -0
  141. package/src/lib/components/ui/skeleton/index.ts +7 -0
  142. package/src/lib/components/ui/skeleton/skeleton.svelte +22 -0
  143. package/src/lib/components/ui/sonner/index.ts +1 -0
  144. package/src/lib/components/ui/sonner/sonner.svelte +20 -0
  145. package/src/lib/components/ui/switch/index.ts +7 -0
  146. package/src/lib/components/ui/switch/switch.svelte +27 -0
  147. package/src/lib/components/ui/textarea/index.ts +7 -0
  148. package/src/lib/components/ui/textarea/textarea.svelte +22 -0
  149. package/src/lib/components/ui/tooltip/index.ts +18 -0
  150. package/src/lib/components/ui/tooltip/tooltip-content.svelte +21 -0
  151. package/src/lib/components/workflowEditor.svelte +188 -0
  152. package/src/lib/context.ts +22 -0
  153. package/src/lib/eventSystem.ts +40 -0
  154. package/src/lib/extensions/extension.types.ts +92 -0
  155. package/src/lib/extensions/extensionUtils.ts +156 -0
  156. package/src/lib/index.ts +24 -0
  157. package/src/lib/store.svelte.ts +13 -0
  158. package/src/lib/store.types.ts +28 -0
  159. package/src/lib/utils.ts +68 -0
  160. package/src/main.ts +18 -0
  161. package/src/stories/detailView/detailViewForm.stories.svelte +79 -0
  162. package/src/vite-env.d.ts +2 -0
@@ -0,0 +1,104 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { Icon as IconType } from 'lucide-svelte';
4
+ import FieldInputReplacement, { type CustomFieldSnippet } from "./fieldInputReplacement.svelte";
5
+ import type { HTMLInputTypeAttribute } from "svelte/elements";
6
+
7
+ export interface BaseField {
8
+ key: string;
9
+ label: string;
10
+ icon?: typeof IconType;
11
+ errorMessages?: string[];
12
+ disabled?: boolean;
13
+ customFieldSnippet?: CustomFieldSnippet;
14
+ }
15
+
16
+ export interface InputField extends BaseField {
17
+ type: 'input';
18
+ placeholder?: string;
19
+ inputType?: HTMLInputTypeAttribute;
20
+ inputStep?: number | "any" | undefined;
21
+ }
22
+
23
+ export interface TextAreaField extends BaseField {
24
+ type: 'textarea';
25
+ placeholder?: string;
26
+ }
27
+
28
+ export interface SelectField extends BaseField {
29
+ type: 'select';
30
+ options: string[];
31
+ placeholder?: string;
32
+ }
33
+
34
+ export interface DateField extends BaseField {
35
+ type: 'date';
36
+ }
37
+
38
+ export interface timeField extends BaseField {
39
+ type: 'time';
40
+ }
41
+
42
+ export interface DateTimeField extends BaseField {
43
+ type: 'datetime';
44
+ }
45
+
46
+ export interface BooleanField extends BaseField {
47
+ type: 'switch';
48
+ }
49
+
50
+ export type DetailFormField = InputField | TextAreaField | SelectField | DateField | timeField | DateTimeField | BooleanField;
51
+
52
+ interface DetailViewFormProps {
53
+ fields: DetailFormField[];
54
+ value?: Record<string, unknown>;
55
+ topRight?: Snippet<[]>;
56
+ }
57
+
58
+ let { value = $bindable({}), fields, topRight }: DetailViewFormProps = $props();
59
+ </script>
60
+
61
+ <div class="flex flex-col gap-4 p-4">
62
+ {#each fields as field}
63
+ <div
64
+ class="flex flex-col gap-2"
65
+ >
66
+ <div
67
+ class="flex flex-1 items-end justify-between gap-2 text-xs"
68
+ >
69
+ <div class="flex gap-2">
70
+ <div class="h-fit">{field.label}</div>
71
+ <div
72
+ class="flex h-fit items-center gap-1 text-[0.7rem] text-muted-foreground"
73
+ >
74
+ {#if field.icon}
75
+ <field.icon size="12" />
76
+ {/if}
77
+ {field.type}
78
+ </div>
79
+ </div>
80
+ <div>
81
+ {@render topRight?.()}
82
+ <!-- TODO: WHEN YOU USE THIS COMPONENT IN THE CREATE AND UPDATE DETAIL VIEW ADD THIS PART TO IT -->
83
+ <!-- <ExtensionsComponents
84
+ name="detailView.fields.topRight.{collectionName}.{fieldName}"
85
+ utils={getExtensionUtils()}
86
+ bind:value={value[field.key]}
87
+ /> -->
88
+ </div>
89
+ </div>
90
+ <FieldInputReplacement
91
+ field={field}
92
+ bind:value={
93
+ () => value[field.key],
94
+ (v) =>
95
+ (value = {
96
+ ...value,
97
+ [field.key]: v,
98
+ })
99
+ }
100
+ customFieldInput={field.customFieldSnippet}
101
+ ></FieldInputReplacement>
102
+ </div>
103
+ {/each}
104
+ </div>
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ import CodeEditor from "../codeEditor.svelte";
3
+ import RichTextEditor from "../richTextEditor.svelte";
4
+
5
+ interface Props {
6
+ value: any;
7
+ type: string;
8
+ args: any;
9
+ field: any;
10
+ destructive?: boolean;
11
+ }
12
+
13
+ let {
14
+ value = $bindable(),
15
+ type,
16
+ args,
17
+ field,
18
+ destructive,
19
+ }: Props = $props();
20
+ </script>
21
+
22
+ {#if type === "code"}
23
+ <CodeEditor name={field.key} {...args} bind:value />
24
+ {:else if type === "richtext"}
25
+ <RichTextEditor name={field.key} bind:value />
26
+ {/if}
@@ -0,0 +1,258 @@
1
+ <script lang="ts">
2
+ import { getStudioContext } from "../../context";
3
+ import { getFieldRelation } from "../../utils";
4
+ import { Ban, Check, CircleAlert, X } from "lucide-svelte";
5
+ import { getField } from "../dataTable/utils";
6
+ import Button from "../ui/button/button.svelte";
7
+ import FieldCustomInput from "./fieldCustomInput.svelte";
8
+ import Input from "../ui/input/input.svelte";
9
+ import * as Select from "../../components/ui/select/index";
10
+ import Textarea from "../ui/textarea/textarea.svelte";
11
+ import ForeingKeyInput from "../foreingKeyInput.svelte";
12
+ import ExtensionsComponents from "../extensionsComponents.svelte";
13
+ import { getExtensionUtils } from "../../extensions/extensionUtils";
14
+
15
+ const { ctx, lobb } = getStudioContext();
16
+
17
+ interface Props {
18
+ collectionName: string;
19
+ fieldName: string;
20
+ value: any;
21
+ errorMessages?: string[];
22
+ entry?: Record<string, any>;
23
+ }
24
+
25
+ let {
26
+ collectionName,
27
+ fieldName,
28
+ value = $bindable(),
29
+ errorMessages = [],
30
+ entry,
31
+ }: Props = $props();
32
+
33
+ const ui_input =
34
+ ctx.meta.collections[collectionName].fields[fieldName].ui?.input;
35
+ const ui =
36
+ ctx.meta.collections[collectionName].fields[fieldName].ui;
37
+ const field = getField(ctx, fieldName, collectionName);
38
+ const fieldRelation = getFieldRelation(ctx, collectionName, fieldName);
39
+ const isDisabled = field.key === 'id' || Boolean(ui?.disabled)
40
+ const disabledClasses = "pointer-events-none opacity-50";
41
+ const destructive: boolean = $derived(Boolean(errorMessages.length));
42
+
43
+ </script>
44
+
45
+ <div class="{isDisabled ? "cursor-not-allowed" : ''}">
46
+ <div
47
+ class="
48
+ relative flex flex-col gap-2
49
+ {isDisabled ? disabledClasses : ''}
50
+ "
51
+ style="flex: 2;"
52
+ >
53
+ <Button
54
+ onclick={() => (value = null)}
55
+ variant="outline"
56
+ class="absolute right-0 top-0 z-10 mr-1.5 mt-1.5 aspect-square h-6 w-6 p-0"
57
+ Icon={Ban}
58
+ tabindex={-1}
59
+ ></Button>
60
+ {#if ui_input}
61
+ <FieldCustomInput
62
+ bind:value
63
+ type={ui_input.type}
64
+ args={ui_input.args}
65
+ {field}
66
+ {destructive}
67
+ />
68
+ {:else if field.label === "id"}
69
+ <Input
70
+ placeholder="AUTO GENERATED"
71
+ class="bg-muted/30 text-xs"
72
+ bind:value
73
+ />
74
+ {:else if fieldRelation && entry}
75
+ <ExtensionsComponents
76
+ name="detailView.fields.foreignKey.{fieldRelation.to.collection}"
77
+ utils={getExtensionUtils(lobb, ctx)}
78
+ parentCollectionName={collectionName}
79
+ collectionName={fieldRelation.to.collection}
80
+ bind:value
81
+ {entry}
82
+ fieldName={field.key}
83
+ {destructive}
84
+ >
85
+ <ForeingKeyInput
86
+ parentCollectionName={collectionName}
87
+ collectionName={fieldRelation.to.collection}
88
+ bind:value
89
+ {entry}
90
+ fieldName={field.key}
91
+ {destructive}
92
+ />
93
+ </ExtensionsComponents>
94
+ {:else if field.type === "string"}
95
+ <!-- if the string has a validator of type enum -->
96
+ {#if field.validators && field.validators.enum}
97
+ <Select.Root
98
+ type="single"
99
+ onValueChange={(newValue) => {
100
+ value = newValue;
101
+ }}
102
+ >
103
+ <Select.Trigger
104
+ placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
105
+ class="
106
+ h-9 w-full bg-muted/30 pr-8
107
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
108
+ "
109
+ >
110
+ {value ? value : "NULL"}
111
+ </Select.Trigger>
112
+ <Select.Content>
113
+ <Select.Group>
114
+ {#each field.validators.enum as option}
115
+ <Select.Item value={option} label={option} />
116
+ {/each}
117
+ </Select.Group>
118
+ </Select.Content>
119
+ </Select.Root>
120
+ {:else}
121
+ <Input
122
+ placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
123
+ type="text"
124
+ class="
125
+ bg-muted/30 text-xs
126
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
127
+ "
128
+ bind:value
129
+ />
130
+ {/if}
131
+ {:else if field.type === "text"}
132
+ <Textarea
133
+ placeholder={ui?.placeholder ? ui.placeholder : value === "" ? "EMPTY STRING" : "NULL"}
134
+ rows={5}
135
+ class="
136
+ bg-muted/30 text-xs
137
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
138
+ "
139
+ bind:value
140
+ />
141
+ {:else if field.type === "date"}
142
+ <Input
143
+ type="date"
144
+ placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
145
+ class="
146
+ dateInput block w-full bg-muted/30 pr-9 text-xs
147
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
148
+ "
149
+ bind:value={
150
+ () => {
151
+ if (!value) {
152
+ return;
153
+ }
154
+ const date = new Date(value);
155
+ return date.toISOString().split("T")[0];
156
+ },
157
+ (v) => (value = v)
158
+ }
159
+ />
160
+ {:else if field.type === "time"}
161
+ <Input
162
+ type="time"
163
+ placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
164
+ class="
165
+ dateInput block w-full bg-muted/30 pr-9 text-xs
166
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
167
+ "
168
+ bind:value={
169
+ () => {
170
+ if (!value) {
171
+ return;
172
+ }
173
+ return value;
174
+ },
175
+ (v) => (value = v)
176
+ }
177
+ />
178
+ {:else if field.type === "datetime"}
179
+ <Input
180
+ type="datetime-local"
181
+ placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
182
+ class="
183
+ dateInput block w-full bg-muted/30 pr-9 text-xs
184
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
185
+ "
186
+ bind:value={
187
+ () => {
188
+ if (!value) {
189
+ return;
190
+ }
191
+ const date = new Date(value);
192
+ return date.toISOString().slice(0, 16);
193
+ },
194
+ (v) => {
195
+ value = v;
196
+ }
197
+ }
198
+ />
199
+ {:else if field.type === "bool"}
200
+ <Select.Root type="single" bind:value>
201
+ <Select.Trigger
202
+ placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
203
+ class="
204
+ bg-muted/30 pr-9
205
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
206
+ "
207
+ >
208
+ {String(value).toUpperCase()}
209
+ </Select.Trigger>
210
+ <Select.Content>
211
+ <Select.Item value={"true"} class="flex gap-1.5">
212
+ <Check size="15" />
213
+ TRUE
214
+ </Select.Item>
215
+ <Select.Item value={"false"} class="flex gap-1.5">
216
+ <X size="15" />
217
+ FALSE
218
+ </Select.Item>
219
+ <Select.Item value={"null"} class="flex gap-1.5">
220
+ <Ban size="15" />
221
+ NULL
222
+ </Select.Item>
223
+ </Select.Content>
224
+ </Select.Root>
225
+ {:else if field.type === "decimal"}
226
+ <Input
227
+ placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
228
+ type="number"
229
+ step="any"
230
+ class="
231
+ bg-muted/30 text-xs
232
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
233
+ "
234
+ bind:value
235
+ />
236
+ {:else}
237
+ <Input
238
+ placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
239
+ type="number"
240
+ class="
241
+ bg-muted/30 text-xs
242
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
243
+ "
244
+ bind:value
245
+ />
246
+ {/if}
247
+ {#if errorMessages}
248
+ {#each errorMessages as message}
249
+ <div class="flex gap-1 text-destructive">
250
+ <CircleAlert size="15" class="translate-y-[0.025rem]" />
251
+ <div class="text-[0.7rem]">
252
+ {message}
253
+ </div>
254
+ </div>
255
+ {/each}
256
+ {/if}
257
+ </div>
258
+ </div>
@@ -0,0 +1,199 @@
1
+ <script lang="ts">
2
+ import { Ban, Check, CircleAlert, X } from "lucide-svelte";
3
+ import Button from "../ui/button/button.svelte";
4
+ import Input from "../ui/input/input.svelte";
5
+ import * as Select from "../../components/ui/select/index";
6
+ import Textarea from "../ui/textarea/textarea.svelte";
7
+ import type { EntryField } from "./detailViewForm.svelte";
8
+ import type { Snippet } from "svelte";
9
+ import AlertView from "../alertView.svelte";
10
+
11
+ export type CustomFieldSnippet = Snippet<[EntryField, unknown]>;
12
+
13
+ interface Props {
14
+ field: EntryField;
15
+ value: unknown;
16
+ customFieldInput?: CustomFieldSnippet;
17
+ }
18
+
19
+ let {
20
+ field,
21
+ value = $bindable(),
22
+ customFieldInput,
23
+ }: Props = $props();
24
+
25
+ const disabledClasses = "pointer-events-none opacity-50";
26
+ const destructive: boolean = $derived(Boolean(field.errorMessages && field.errorMessages.length));
27
+ </script>
28
+
29
+ <div class="{field.disabled ? "cursor-not-allowed" : ''}">
30
+ <div
31
+ class="
32
+ relative flex flex-col gap-2
33
+ {field.disabled ? disabledClasses : ''}
34
+ "
35
+ style="flex: 2;"
36
+ >
37
+ <Button
38
+ onclick={() => (value = null)}
39
+ variant="outline"
40
+ class="absolute right-0 top-0 z-10 mr-1.5 mt-1.5 aspect-square h-6 w-6 p-0"
41
+ Icon={Ban}
42
+ tabindex={-1}
43
+ ></Button>
44
+ {#if customFieldInput}
45
+ {@render customFieldInput(field, value)}
46
+ {:else if field.type === "input"}
47
+ <!-- if the string has a validator of type enum -->
48
+ <Input
49
+ placeholder={field.placeholder ? field.placeholder : "NULL"}
50
+ type="text"
51
+ class="
52
+ bg-muted/30 text-xs
53
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
54
+ "
55
+ bind:value
56
+ />
57
+ {:else if field.type === "select"}
58
+ <Select.Root
59
+ type="single"
60
+ onValueChange={(newValue) => {
61
+ value = newValue;
62
+ }}
63
+ >
64
+ <Select.Trigger
65
+ placeholder={field.placeholder ? field.placeholder : "NULL"}
66
+ class="
67
+ h-9 w-full bg-muted/30 pr-8
68
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
69
+ "
70
+ >
71
+ {value ? value : "NULL"}
72
+ </Select.Trigger>
73
+ <Select.Content>
74
+ <Select.Group>
75
+ {#each field.options as option}
76
+ <Select.Item value={option} label={option} />
77
+ {/each}
78
+ </Select.Group>
79
+ </Select.Content>
80
+ </Select.Root>
81
+ {:else if field.type === "textarea"}
82
+ {#if typeof value === 'string' || typeof value === 'undefined' || value === null}
83
+ <Textarea
84
+ placeholder={field.placeholder ? field.placeholder : value === "" ? "EMPTY STRING" : "NULL"}
85
+ rows={5}
86
+ class="
87
+ bg-muted/30 text-xs
88
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
89
+ "
90
+ bind:value
91
+ />
92
+ {:else}
93
+ <AlertView title="Couldnt render component">
94
+ The value passed to the Textarea in (fieldInputReplacement) is something different from these (string, undefined, null)
95
+ </AlertView>
96
+ {/if}
97
+ {:else if field.type === "date"}
98
+ <Input
99
+ type="date"
100
+ class="
101
+ dateInput block w-full bg-muted/30 pr-9 text-xs
102
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
103
+ "
104
+ bind:value={
105
+ () => {
106
+ if (typeof value !== "string") {
107
+ return;
108
+ }
109
+ const date = new Date(value);
110
+ return date.toISOString().split("T")[0];
111
+ },
112
+ (v) => (value = v)
113
+ }
114
+ />
115
+ {:else if field.type === "time"}
116
+ <Input
117
+ type="time"
118
+ class="
119
+ dateInput block w-full bg-muted/30 pr-9 text-xs
120
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
121
+ "
122
+ bind:value={
123
+ () => {
124
+ if (typeof value !== "string") {
125
+ return;
126
+ }
127
+ return value;
128
+ },
129
+ (v) => (value = v)
130
+ }
131
+ />
132
+ {:else if field.type === "datetime"}
133
+ <Input
134
+ type="datetime-local"
135
+ class="
136
+ dateInput block w-full bg-muted/30 pr-9 text-xs
137
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
138
+ "
139
+ bind:value={
140
+ () => {
141
+ if (typeof value !== "string") {
142
+ return;
143
+ }
144
+ const date = new Date(value);
145
+ return date.toISOString().slice(0, 16);
146
+ },
147
+ (v) => {
148
+ value = v;
149
+ }
150
+ }
151
+ />
152
+ {:else if field.type === "switch"}
153
+ {#if typeof value === "string" || typeof value === "undefined"}
154
+ <Select.Root type="single" bind:value>
155
+ <Select.Trigger
156
+ class="
157
+ bg-muted/30 pr-9
158
+ {destructive ? 'border-destructive bg-destructive/10' : ''}
159
+ "
160
+ >
161
+ {String(value).toUpperCase()}
162
+ </Select.Trigger>
163
+ <Select.Content>
164
+ <Select.Item value={"true"} class="flex gap-1.5">
165
+ <Check size="15" />
166
+ TRUE
167
+ </Select.Item>
168
+ <Select.Item value={"false"} class="flex gap-1.5">
169
+ <X size="15" />
170
+ FALSE
171
+ </Select.Item>
172
+ <Select.Item value={"null"} class="flex gap-1.5">
173
+ <Ban size="15" />
174
+ NULL
175
+ </Select.Item>
176
+ </Select.Content>
177
+ </Select.Root>
178
+ {:else}
179
+ <AlertView title="Couldnt render component">
180
+ The value passed to the switch element in (fieldInputReplacement) is not one of these (string, undefined)
181
+ </AlertView>
182
+ {/if}
183
+ {:else}
184
+ <AlertView title="Couldnt render component">
185
+ For some reason couldnt render the (fieldInputReplacement)
186
+ </AlertView>
187
+ {/if}
188
+ {#if field.errorMessages}
189
+ {#each field.errorMessages as message}
190
+ <div class="flex gap-1 text-destructive">
191
+ <CircleAlert size="15" class="translate-y-[0.025rem]" />
192
+ <div class="text-[0.7rem]">
193
+ {message}
194
+ </div>
195
+ </div>
196
+ {/each}
197
+ {/if}
198
+ </div>
199
+ </div>
@@ -0,0 +1,59 @@
1
+ import type { CreateDetailViewProp } from "./create/createDetailView.svelte";
2
+ import type { UpdateDetailViewProp } from "./update/updateDetailView.svelte";
3
+ import CreateDetailView from "./create/createDetailView.svelte";
4
+ import UpdateDetailView from "./update/updateDetailView.svelte";
5
+ import type { StudioContext } from "../../context";
6
+ import { getCollectionParamsFields } from "../dataTable/utils";
7
+ import { createStudioContextMap } from "../../context";
8
+ import { mount, unmount } from "svelte";
9
+
10
+ export function openCreateDetailView(studioContext: StudioContext, props: CreateDetailViewProp) {
11
+ const targetElement = document.querySelector('main');
12
+
13
+ if (!targetElement) {
14
+ throw new Error("main html element doesn't exist for some reason")
15
+ }
16
+
17
+ const mountedCreateDetailView = mount(CreateDetailView, {
18
+ target: targetElement,
19
+ context: createStudioContextMap(studioContext),
20
+ props: {
21
+ ...props,
22
+ onCancel: async () => {
23
+ props.onCancel?.()
24
+ await unmount(mountedCreateDetailView, { outro: true });
25
+ }
26
+ },
27
+ })
28
+ }
29
+
30
+ export async function openUpdateDetailView(studioContext: StudioContext, props: UpdateDetailViewProp) {
31
+ const { lobb, ctx } = studioContext;
32
+ const params = {
33
+ fields: getCollectionParamsFields(ctx, props.collectionName, true),
34
+ filter: { id: props.recordId },
35
+ limit: 1,
36
+ };
37
+ const response = await lobb.findAll(props.collectionName, params);
38
+ const result = await response.json();
39
+ const entry = result.data[0];
40
+
41
+ const targetElement = document.querySelector('main');
42
+
43
+ if (!targetElement) {
44
+ throw new Error("main html element doesn't exist for some reason")
45
+ }
46
+
47
+ const mountedUpdateDetailView = mount(UpdateDetailView, {
48
+ target: targetElement,
49
+ context: createStudioContextMap(studioContext),
50
+ props: {
51
+ ...props,
52
+ onCancel: async () => {
53
+ props.onCancel?.()
54
+ await unmount(mountedUpdateDetailView, { outro: true });
55
+ },
56
+ values: entry,
57
+ },
58
+ })
59
+ }