@lobb-js/studio 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/package.json +2 -1
  2. package/src/App.svelte +5 -0
  3. package/src/app.css +124 -0
  4. package/src/lib/components/LlmButton.svelte +137 -0
  5. package/src/lib/components/Studio.svelte +129 -0
  6. package/src/lib/components/alertView.svelte +20 -0
  7. package/src/lib/components/breadCrumbs.svelte +60 -0
  8. package/src/lib/components/codeEditor.svelte +152 -0
  9. package/src/lib/components/combobox.svelte +92 -0
  10. package/src/lib/components/confirmationDialog/confirmationDialog.svelte +33 -0
  11. package/src/lib/components/confirmationDialog/store.svelte.ts +28 -0
  12. package/src/lib/components/createManyButton.svelte +109 -0
  13. package/src/lib/components/dataTable/childRecords.svelte +142 -0
  14. package/src/lib/components/dataTable/dataTable.svelte +225 -0
  15. package/src/lib/components/dataTable/fieldCell.svelte +77 -0
  16. package/src/lib/components/dataTable/filter.svelte +284 -0
  17. package/src/lib/components/dataTable/filterButton.svelte +39 -0
  18. package/src/lib/components/dataTable/footer.svelte +84 -0
  19. package/src/lib/components/dataTable/header.svelte +155 -0
  20. package/src/lib/components/dataTable/sort.svelte +173 -0
  21. package/src/lib/components/dataTable/sortButton.svelte +36 -0
  22. package/src/lib/components/dataTable/table.svelte +337 -0
  23. package/src/lib/components/dataTable/utils.ts +127 -0
  24. package/src/lib/components/detailView/create/children.svelte +70 -0
  25. package/src/lib/components/detailView/create/createDetailView.svelte +228 -0
  26. package/src/lib/components/detailView/create/createDetailViewButton.svelte +37 -0
  27. package/src/lib/components/detailView/create/createManyView.svelte +252 -0
  28. package/src/lib/components/detailView/create/subRecords.svelte +50 -0
  29. package/src/lib/components/detailView/detailViewForm.svelte +104 -0
  30. package/src/lib/components/detailView/fieldCustomInput.svelte +26 -0
  31. package/src/lib/components/detailView/fieldInput.svelte +258 -0
  32. package/src/lib/components/detailView/fieldInputReplacement.svelte +199 -0
  33. package/src/lib/components/detailView/store.svelte.ts +59 -0
  34. package/src/lib/components/detailView/update/children.svelte +96 -0
  35. package/src/lib/components/detailView/update/updateDetailView.svelte +176 -0
  36. package/src/lib/components/detailView/update/updateDetailViewButton.svelte +56 -0
  37. package/src/lib/components/detailView/utils.ts +176 -0
  38. package/src/lib/components/diffViewer.svelte +105 -0
  39. package/src/lib/components/drawer.svelte +28 -0
  40. package/src/lib/components/extensionsComponents.svelte +33 -0
  41. package/src/lib/components/foreingKeyInput.svelte +80 -0
  42. package/src/lib/components/header.svelte +45 -0
  43. package/src/lib/components/loadingTypesForMonacoEditor.ts +36 -0
  44. package/src/lib/components/miniSidebar.svelte +226 -0
  45. package/src/lib/components/rangeCalendarButton.svelte +257 -0
  46. package/src/lib/components/richTextEditor.svelte +284 -0
  47. package/src/lib/components/routes/collections/collection.svelte +57 -0
  48. package/src/lib/components/routes/collections/collections.svelte +45 -0
  49. package/src/lib/components/routes/data_model/dataModel.svelte +40 -0
  50. package/src/lib/components/routes/data_model/flow.css +22 -0
  51. package/src/lib/components/routes/data_model/flow.svelte +84 -0
  52. package/src/lib/components/routes/data_model/syncManager.svelte +94 -0
  53. package/src/lib/components/routes/data_model/utils.ts +35 -0
  54. package/src/lib/components/routes/extensions/extension.svelte +19 -0
  55. package/src/lib/components/routes/home.svelte +40 -0
  56. package/src/lib/components/routes/workflows/workflows.svelte +136 -0
  57. package/src/lib/components/selectRecord.svelte +130 -0
  58. package/src/lib/components/setServerPage.svelte +50 -0
  59. package/src/lib/components/sidebar/index.ts +4 -0
  60. package/src/lib/components/sidebar/sidebar.svelte +149 -0
  61. package/src/lib/components/sidebar/sidebarElements.svelte +144 -0
  62. package/src/lib/components/sidebar/sidebarTrigger.svelte +33 -0
  63. package/src/lib/components/singletone.svelte +71 -0
  64. package/src/lib/components/ui/accordion/accordion-content.svelte +22 -0
  65. package/src/lib/components/ui/accordion/accordion-item.svelte +12 -0
  66. package/src/lib/components/ui/accordion/accordion-trigger.svelte +31 -0
  67. package/src/lib/components/ui/accordion/index.ts +17 -0
  68. package/src/lib/components/ui/alert/alert-description.svelte +16 -0
  69. package/src/lib/components/ui/alert/alert-title.svelte +24 -0
  70. package/src/lib/components/ui/alert/alert.svelte +39 -0
  71. package/src/lib/components/ui/alert/index.ts +14 -0
  72. package/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte +13 -0
  73. package/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte +17 -0
  74. package/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte +26 -0
  75. package/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte +16 -0
  76. package/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte +20 -0
  77. package/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte +20 -0
  78. package/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte +19 -0
  79. package/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte +18 -0
  80. package/src/lib/components/ui/alert-dialog/index.ts +40 -0
  81. package/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +23 -0
  82. package/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte +16 -0
  83. package/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte +31 -0
  84. package/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte +23 -0
  85. package/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte +23 -0
  86. package/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte +27 -0
  87. package/src/lib/components/ui/breadcrumb/breadcrumb.svelte +15 -0
  88. package/src/lib/components/ui/breadcrumb/index.ts +25 -0
  89. package/src/lib/components/ui/button/button.svelte +110 -0
  90. package/src/lib/components/ui/button/index.ts +17 -0
  91. package/src/lib/components/ui/checkbox/checkbox.svelte +35 -0
  92. package/src/lib/components/ui/checkbox/index.ts +6 -0
  93. package/src/lib/components/ui/command/command-dialog.svelte +35 -0
  94. package/src/lib/components/ui/command/command-empty.svelte +12 -0
  95. package/src/lib/components/ui/command/command-group.svelte +31 -0
  96. package/src/lib/components/ui/command/command-input.svelte +25 -0
  97. package/src/lib/components/ui/command/command-item.svelte +19 -0
  98. package/src/lib/components/ui/command/command-link-item.svelte +19 -0
  99. package/src/lib/components/ui/command/command-list.svelte +16 -0
  100. package/src/lib/components/ui/command/command-separator.svelte +12 -0
  101. package/src/lib/components/ui/command/command-shortcut.svelte +20 -0
  102. package/src/lib/components/ui/command/command.svelte +21 -0
  103. package/src/lib/components/ui/command/index.ts +40 -0
  104. package/src/lib/components/ui/dialog/dialog-content.svelte +38 -0
  105. package/src/lib/components/ui/dialog/dialog-description.svelte +16 -0
  106. package/src/lib/components/ui/dialog/dialog-footer.svelte +20 -0
  107. package/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
  108. package/src/lib/components/ui/dialog/dialog-overlay.svelte +19 -0
  109. package/src/lib/components/ui/dialog/dialog-title.svelte +16 -0
  110. package/src/lib/components/ui/dialog/index.ts +37 -0
  111. package/src/lib/components/ui/input/index.ts +7 -0
  112. package/src/lib/components/ui/input/input.svelte +46 -0
  113. package/src/lib/components/ui/label/index.ts +7 -0
  114. package/src/lib/components/ui/label/label.svelte +19 -0
  115. package/src/lib/components/ui/popover/index.ts +17 -0
  116. package/src/lib/components/ui/popover/popover-content.svelte +28 -0
  117. package/src/lib/components/ui/range-calendar/index.ts +30 -0
  118. package/src/lib/components/ui/range-calendar/range-calendar-cell.svelte +19 -0
  119. package/src/lib/components/ui/range-calendar/range-calendar-day.svelte +35 -0
  120. package/src/lib/components/ui/range-calendar/range-calendar-grid-body.svelte +12 -0
  121. package/src/lib/components/ui/range-calendar/range-calendar-grid-head.svelte +12 -0
  122. package/src/lib/components/ui/range-calendar/range-calendar-grid-row.svelte +12 -0
  123. package/src/lib/components/ui/range-calendar/range-calendar-grid.svelte +16 -0
  124. package/src/lib/components/ui/range-calendar/range-calendar-head-cell.svelte +16 -0
  125. package/src/lib/components/ui/range-calendar/range-calendar-header.svelte +16 -0
  126. package/src/lib/components/ui/range-calendar/range-calendar-heading.svelte +16 -0
  127. package/src/lib/components/ui/range-calendar/range-calendar-months.svelte +20 -0
  128. package/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte +27 -0
  129. package/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte +27 -0
  130. package/src/lib/components/ui/range-calendar/range-calendar.svelte +57 -0
  131. package/src/lib/components/ui/select/index.ts +34 -0
  132. package/src/lib/components/ui/select/select-content.svelte +38 -0
  133. package/src/lib/components/ui/select/select-group-heading.svelte +16 -0
  134. package/src/lib/components/ui/select/select-item.svelte +37 -0
  135. package/src/lib/components/ui/select/select-scroll-down-button.svelte +19 -0
  136. package/src/lib/components/ui/select/select-scroll-up-button.svelte +19 -0
  137. package/src/lib/components/ui/select/select-separator.svelte +13 -0
  138. package/src/lib/components/ui/select/select-trigger.svelte +24 -0
  139. package/src/lib/components/ui/separator/index.ts +7 -0
  140. package/src/lib/components/ui/separator/separator.svelte +22 -0
  141. package/src/lib/components/ui/skeleton/index.ts +7 -0
  142. package/src/lib/components/ui/skeleton/skeleton.svelte +22 -0
  143. package/src/lib/components/ui/sonner/index.ts +1 -0
  144. package/src/lib/components/ui/sonner/sonner.svelte +20 -0
  145. package/src/lib/components/ui/switch/index.ts +7 -0
  146. package/src/lib/components/ui/switch/switch.svelte +27 -0
  147. package/src/lib/components/ui/textarea/index.ts +7 -0
  148. package/src/lib/components/ui/textarea/textarea.svelte +22 -0
  149. package/src/lib/components/ui/tooltip/index.ts +18 -0
  150. package/src/lib/components/ui/tooltip/tooltip-content.svelte +21 -0
  151. package/src/lib/components/workflowEditor.svelte +188 -0
  152. package/src/lib/context.ts +22 -0
  153. package/src/lib/eventSystem.ts +40 -0
  154. package/src/lib/extensions/extension.types.ts +92 -0
  155. package/src/lib/extensions/extensionUtils.ts +156 -0
  156. package/src/lib/index.ts +24 -0
  157. package/src/lib/store.svelte.ts +13 -0
  158. package/src/lib/store.types.ts +28 -0
  159. package/src/lib/utils.ts +68 -0
  160. package/src/main.ts +18 -0
  161. package/src/stories/detailView/detailViewForm.stories.svelte +79 -0
  162. package/src/vite-env.d.ts +2 -0
  163. package/vite-plugins/index.js +2 -0
  164. package/vite-plugins/lobb-proxy.js +36 -0
@@ -0,0 +1,77 @@
1
+ <script lang="ts">
2
+ import { getFieldRelation } from "../../utils";
3
+ import { ExternalLink } from "lucide-svelte";
4
+ import UpdateDetailViewButton from "../detailView/update/updateDetailViewButton.svelte";
5
+ import { getField } from "./utils";
6
+ import _ from "lodash";
7
+ import { getStudioContext } from "../../context";
8
+
9
+ const { ctx } = getStudioContext();
10
+
11
+ interface Props {
12
+ collectionName: string;
13
+ fieldName: string;
14
+ value: any;
15
+ entry: Record<string, any>;
16
+ tableParams?: any;
17
+ }
18
+
19
+ let {
20
+ collectionName,
21
+ fieldName,
22
+ value,
23
+ entry,
24
+ tableParams = $bindable(),
25
+ }: Props = $props();
26
+
27
+ const field = getField(ctx, fieldName, collectionName);
28
+ const relation = getFieldRelation(ctx, collectionName, fieldName);
29
+ const isRefrenceField = Boolean(
30
+ getFieldRelation(ctx, collectionName, fieldName),
31
+ );
32
+ </script>
33
+
34
+ {#if value === ""}
35
+ <div class="text-muted-foreground">EMPTY STRING</div>
36
+ {:else if value === null || value === undefined}
37
+ <div class="text-muted-foreground">NULL</div>
38
+ {:else if isRefrenceField}
39
+ {#if value.id !== 0}
40
+ {@const primaryField = Object.values(value)[1]}
41
+ {#if value.id}
42
+ <div class="flex items-center gap-2">
43
+ <div>{value.id}</div>
44
+ {#if primaryField}
45
+ <div class="border bg-muted px-3 py-1 rounded-full">
46
+ {primaryField}
47
+ </div>
48
+ {/if}
49
+ <UpdateDetailViewButton
50
+ collectionName={relation.to.collection}
51
+ recordId={value.id}
52
+ variant="ghost"
53
+ class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
54
+ Icon={ExternalLink}
55
+ onSuccessfullSave={async () => {
56
+ tableParams = { ...tableParams };
57
+ }}
58
+ ></UpdateDetailViewButton>
59
+ </div>
60
+ {:else}
61
+ <div class="text-muted-foreground">NULL</div>
62
+ {/if}
63
+ {:else}
64
+ <div class="text-muted-foreground">PARENT ID</div>
65
+ {/if}
66
+ {:else if field.type === "datetime"}
67
+ {@const date = new Date(value).toLocaleDateString()}
68
+ {@const time = new Date(value).toLocaleTimeString()}
69
+ <div>{date}, {time}</div>
70
+ {:else if field.type === "date"}
71
+ {@const date = new Date(value).toLocaleDateString()}
72
+ <div>{date}</div>
73
+ {:else if field.type === "time"}
74
+ <div>{value}</div>
75
+ {:else}
76
+ {value}
77
+ {/if}
@@ -0,0 +1,284 @@
1
+ <script lang="ts">
2
+ import * as Popover from "../../components/ui/popover/index.js";
3
+ import Filter from "./filter.svelte";
4
+ import {
5
+ Plus,
6
+ Boxes,
7
+ CircleOff,
8
+ ListFilter,
9
+ Diamond,
10
+ Trash,
11
+ } from "lucide-svelte";
12
+ import { buttonVariants } from "../ui/button";
13
+ import _ from "lodash";
14
+ import { getStudioContext } from "../../context";
15
+ import Button from "../ui/button/button.svelte";
16
+
17
+ const { ctx } = getStudioContext();
18
+
19
+ interface Props {
20
+ filter: any;
21
+ collectionName: string;
22
+ isFirst?: boolean;
23
+ deleteFilter?: () => void;
24
+ }
25
+
26
+ let {
27
+ filter = $bindable({}),
28
+ collectionName,
29
+ isFirst = false,
30
+ deleteFilter,
31
+ }: Props = $props();
32
+
33
+ let firstPopover = $state(false);
34
+ let secondPopover = $state(false);
35
+
36
+ $effect.pre(() => {
37
+ // convert direct values to { $eq: (value) }
38
+ for (let index = 0; index < Object.keys(filter).length; index++) {
39
+ const key = Object.keys(filter)[index];
40
+ const value = filter[key];
41
+ if (key !== "$and" && key !== "$or") {
42
+ if (!_.isPlainObject(value)) {
43
+ filter[key] = {
44
+ $eq: value,
45
+ };
46
+ }
47
+ }
48
+ }
49
+ });
50
+
51
+ function groupAddingHandler(filter: any, key: string) {
52
+ if (key === "$and" || key === "$or") {
53
+ filter[key] = [];
54
+ } else {
55
+ filter[key] = {};
56
+ }
57
+ }
58
+
59
+ function getGroupOptions(filter: any) {
60
+ const collectionFieldNames = Object.keys(
61
+ ctx.meta.collections[collectionName].fields,
62
+ );
63
+ const options = ["$and", "$or", ...collectionFieldNames];
64
+ const existingPropertiesNames = Object.keys(filter);
65
+ const filteredOptions = _.difference(options, existingPropertiesNames);
66
+ return filteredOptions;
67
+ }
68
+
69
+ function getOperatorOptions(filter: any) {
70
+ const operators = ctx.meta.filter.operators;
71
+ const existingPropertiesNames = Object.keys(filter);
72
+ const filteredOptions = _.difference(
73
+ operators,
74
+ existingPropertiesNames,
75
+ );
76
+ return filteredOptions;
77
+ }
78
+
79
+ function operatorAddingHandler(filter: any, key: string) {
80
+ filter[key] = "";
81
+ }
82
+ </script>
83
+
84
+ {#snippet filterAddButton(filter: any)}
85
+ <Popover.Root bind:open={firstPopover}>
86
+ <Popover.Trigger
87
+ class={buttonVariants({
88
+ variant: "ghost",
89
+ class: "h-7 px-2 text-xs font-normal text-muted-foreground",
90
+ })}
91
+ >
92
+ <Plus />
93
+ </Popover.Trigger>
94
+ <Popover.Content class="flex w-48 flex-col p-2 max-h-60 overflow-auto">
95
+ {#each getGroupOptions(filter) as fieldName}
96
+ <button
97
+ onclick={() => {
98
+ groupAddingHandler(filter, fieldName);
99
+ firstPopover = false;
100
+ }}
101
+ class="flex cursor-pointer items-center gap-2 rounded-md p-2 px-2 text-xs text-muted-foreground hover:bg-muted hover:text-foreground"
102
+ >
103
+ <div>{fieldName}</div>
104
+ </button>
105
+ {/each}
106
+ </Popover.Content>
107
+ </Popover.Root>
108
+ {/snippet}
109
+
110
+ <div class="flex flex-col rounded-md {isFirst ? '' : 'border'}">
111
+ <div class="flex justify-between items-center gap-2 border-b p-2 h-10">
112
+ <div class="flex text-xs font-semibold text-muted-foreground gap-2">
113
+ <ListFilter size="17.5" />
114
+ <div>Filter</div>
115
+ </div>
116
+ <div>
117
+ {@render filterAddButton(filter)}
118
+ {#if deleteFilter}
119
+ <Button
120
+ class="text-muted-foreground px-2 h-7"
121
+ variant="ghost"
122
+ size="icon"
123
+ Icon={Trash}
124
+ onclick={deleteFilter}
125
+ ></Button>
126
+ {/if}
127
+ </div>
128
+ </div>
129
+ <div
130
+ class="flex flex-col gap-2 p-2 {isFirst
131
+ ? 'max-h-100 overflow-auto'
132
+ : ''}"
133
+ >
134
+ {#if Object.entries(filter).length}
135
+ {#each Object.entries(filter) as [key, value]}
136
+ {@const collectionFields =
137
+ ctx.meta.collections[collectionName].fields}
138
+ {#if key === "$and" || key === "$or"}
139
+ <div class="flex flex-col rounded-md border">
140
+ <div
141
+ class="flex justify-between items-center gap-2 text-xs font-semibold text-muted-foreground p-2 border-b h-10"
142
+ >
143
+ <div class="flex gap-2">
144
+ <Boxes size="17.5" />
145
+ {key === "$and" ? "AND" : "OR"}
146
+ </div>
147
+ <div>
148
+ <Button
149
+ class="text-muted-foreground px-2 h-7"
150
+ variant="ghost"
151
+ size="icon"
152
+ Icon={Plus}
153
+ onclick={() =>
154
+ (filter[key] = [...filter[key], {}])}
155
+ ></Button>
156
+ <Button
157
+ class="text-muted-foreground px-2 h-7"
158
+ variant="ghost"
159
+ size="icon"
160
+ Icon={Trash}
161
+ onclick={() => {
162
+ delete filter[key];
163
+ }}
164
+ ></Button>
165
+ </div>
166
+ </div>
167
+ {#if Object.keys(filter[key]).length}
168
+ <div class="flex flex-col gap-2 p-2">
169
+ {#each filter[key] as _, index}
170
+ <Filter
171
+ bind:filter={filter[key][index]}
172
+ {collectionName}
173
+ deleteFilter={() => {
174
+ filter[key].splice(index, 1);
175
+ }}
176
+ />
177
+ {/each}
178
+ </div>
179
+ {:else}
180
+ <div
181
+ class="flex justify-center gap-2 text-xs text-muted-foreground text-center p-4"
182
+ >
183
+ <CircleOff size="17.5" />
184
+ No rules defined
185
+ </div>
186
+ {/if}
187
+ </div>
188
+ {:else}
189
+ <div class="flex flex-col gap-2 rounded-md border">
190
+ <div
191
+ class="flex gap-2 justify-between items-center text-xs font-semibold text-muted-foreground p-2 border-b h-10"
192
+ >
193
+ <div class="flex gap-2">
194
+ <Diamond size="17.5" />
195
+ {key}
196
+ </div>
197
+ <div>
198
+ <Popover.Root bind:open={secondPopover}>
199
+ <Popover.Trigger
200
+ class={buttonVariants({
201
+ variant: "ghost",
202
+ class: "h-7 px-2 text-xs font-normal",
203
+ })}
204
+ >
205
+ <Plus />
206
+ </Popover.Trigger>
207
+ <Popover.Content
208
+ class="flex w-48 flex-col p-2 max-h-60 overflow-auto"
209
+ >
210
+ {#each getOperatorOptions(filter[key]) as fieldName}
211
+ <button
212
+ onclick={() => {
213
+ operatorAddingHandler(
214
+ filter[key],
215
+ fieldName,
216
+ );
217
+ secondPopover = false;
218
+ }}
219
+ class="flex cursor-pointer items-center gap-2 rounded-md p-2 px-2 text-xs text-muted-foreground hover:bg-muted hover:text-foreground"
220
+ >
221
+ <div>{fieldName}</div>
222
+ </button>
223
+ {/each}
224
+ </Popover.Content>
225
+ </Popover.Root>
226
+ <Button
227
+ class="text-muted-foreground px-2 h-7"
228
+ variant="ghost"
229
+ size="icon"
230
+ Icon={Trash}
231
+ onclick={() => {
232
+ delete filter[key];
233
+ }}
234
+ ></Button>
235
+ </div>
236
+ </div>
237
+ {#if Object.entries(value as any).length}
238
+ <div
239
+ class="gap-2 p-2"
240
+ style="display: grid; grid-template-columns: auto 1fr auto;"
241
+ >
242
+ {#each Object.entries(value as any) as [ruleKey, ruleValue]}
243
+ <div
244
+ class="rounded-md bg-muted border text-xs text-muted-foreground py-1 px-2"
245
+ >
246
+ {ruleKey}
247
+ </div>
248
+ <input
249
+ class="w-full rounded-md bg-muted border text-xs text-muted-foreground py-1 px-2"
250
+ type="text"
251
+ bind:value={filter[key][ruleKey]}
252
+ />
253
+ <Button
254
+ class="text-muted-foreground px-2 h-7"
255
+ variant="ghost"
256
+ size="icon"
257
+ Icon={Trash}
258
+ onclick={() => {
259
+ delete filter[key][ruleKey];
260
+ }}
261
+ ></Button>
262
+ {/each}
263
+ </div>
264
+ {:else}
265
+ <div
266
+ class="flex justify-center gap-2 text-xs text-muted-foreground text-center rounded-md p-2"
267
+ >
268
+ <CircleOff size="17.5" />
269
+ No rules defined
270
+ </div>
271
+ {/if}
272
+ </div>
273
+ {/if}
274
+ {/each}
275
+ {:else}
276
+ <div
277
+ class="flex justify-center gap-2 text-xs text-muted-foreground text-center rounded-md p-2"
278
+ >
279
+ <CircleOff size="17.5" />
280
+ No rules defined
281
+ </div>
282
+ {/if}
283
+ </div>
284
+ </div>
@@ -0,0 +1,39 @@
1
+ <script lang="ts">
2
+ import * as Popover from "../../components/ui/popover/index.js";
3
+ import { ListFilter } from "lucide-svelte";
4
+ import { buttonVariants } from "../ui/button";
5
+ import Filter from "./filter.svelte";
6
+
7
+ interface Props {
8
+ filter: any;
9
+ collectionName: string;
10
+ showText?: boolean;
11
+ }
12
+
13
+ let {
14
+ filter = $bindable({}),
15
+ showText = true,
16
+ collectionName,
17
+ }: Props = $props();
18
+ </script>
19
+
20
+ <Popover.Root>
21
+ <Popover.Trigger
22
+ class={buttonVariants({
23
+ variant: "ghost",
24
+ class: "h-7 px-3 text-xs font-normal",
25
+ })}
26
+ >
27
+ <ListFilter />
28
+ {#if showText}
29
+ {#if Object.keys(filter).length}
30
+ Filtered by {Object.keys(filter).length} rules
31
+ {:else}
32
+ Filter
33
+ {/if}
34
+ {/if}
35
+ </Popover.Trigger>
36
+ <Popover.Content class="w-screen max-w-100 p-0">
37
+ <Filter bind:filter {collectionName} isFirst={true} />
38
+ </Popover.Content>
39
+ </Popover.Root>
@@ -0,0 +1,84 @@
1
+ <script lang="ts">
2
+ import { ArrowLeft, ArrowRight } from "lucide-svelte";
3
+ import Button from "../ui/button/button.svelte";
4
+ import Input from "../ui/input/input.svelte";
5
+ import * as Select from "../ui/select";
6
+
7
+ interface Props {
8
+ limit: string;
9
+ totalCount: number;
10
+ currentPage: number;
11
+ }
12
+
13
+ let {
14
+ limit = $bindable("100"),
15
+ currentPage = $bindable(1),
16
+ totalCount,
17
+ }: Props = $props();
18
+
19
+ let pageCount: number = $derived(Math.ceil(totalCount / Number(limit)));
20
+ let footerWidth: number = $state(0);
21
+ let footerIsSmall: boolean = $derived(footerWidth < 560);
22
+
23
+ function gotoPage(pageNumber: number) {
24
+ currentPage = pageNumber;
25
+ }
26
+ </script>
27
+
28
+ <div
29
+ bind:clientWidth={footerWidth}
30
+ class="flex justify-between box-content border-t bg-background px-2 h-10"
31
+ >
32
+ <div class="flex items-center gap-2">
33
+ <Button
34
+ disabled={Number(currentPage) <= 1}
35
+ onclick={() => gotoPage(currentPage - 1)}
36
+ variant="outline"
37
+ class="h-7 w-7 p-0"
38
+ Icon={ArrowLeft}
39
+ ></Button>
40
+ {#if !footerIsSmall}
41
+ <div class="text-xs text-muted-foreground">Page</div>
42
+ {/if}
43
+ <Input bind:value={currentPage} class="h-7 w-14 bg-muted" />
44
+ <div class="text-xs text-muted-foreground">
45
+ {footerIsSmall ? "/" : "of"}
46
+ {pageCount}
47
+ </div>
48
+ <Button
49
+ disabled={Number(currentPage) >= pageCount}
50
+ onclick={() => gotoPage(currentPage + 1)}
51
+ variant="outline"
52
+ class="h-7 w-7 p-0"
53
+ Icon={ArrowRight}
54
+ ></Button>
55
+ </div>
56
+ <div class="flex h-10 items-center gap-2">
57
+ <Select.Root type="single" name="limit" bind:value={limit}>
58
+ <Select.Trigger class="h-7 {footerIsSmall ? 'w-20' : 'w-28'}"
59
+ >{limit}
60
+ {footerIsSmall ? "" : "rows"}</Select.Trigger
61
+ >
62
+ <Select.Content>
63
+ <Select.Group>
64
+ <Select.Item
65
+ value="100"
66
+ label="100 {footerIsSmall ? '' : 'rows'}"
67
+ />
68
+ <Select.Item
69
+ value="500"
70
+ label="500 {footerIsSmall ? '' : 'rows'}"
71
+ />
72
+ <Select.Item
73
+ value="1000"
74
+ label="1000 {footerIsSmall ? '' : 'rows'}"
75
+ />
76
+ </Select.Group>
77
+ </Select.Content>
78
+ </Select.Root>
79
+ <div class="text-xs text-muted-foreground">
80
+ {totalCount}
81
+ {footerIsSmall ? "" : "records"}
82
+ </div>
83
+ </div>
84
+ </div>
@@ -0,0 +1,155 @@
1
+ <script lang="ts">
2
+ import { getStudioContext } from "../../context";
3
+
4
+ const { lobb, ctx } = getStudioContext();
5
+ import { ListRestart, Plus, SquareStack, Trash } from "lucide-svelte";
6
+ import LlmButton from "../LlmButton.svelte";
7
+ import FilterButton from "./filterButton.svelte";
8
+ import SortButton from "./sortButton.svelte";
9
+ import Button from "../ui/button/button.svelte";
10
+ import CreateManyButton from "../createManyButton.svelte";
11
+ import { showDialog } from "../confirmationDialog/store.svelte";
12
+ import CreateDetailViewButton from "../detailView/create/createDetailViewButton.svelte";
13
+ import type { Snippet } from "svelte";
14
+
15
+ interface Props {
16
+ collectionName: string;
17
+ params: any;
18
+ selectedRecords: string[];
19
+ left?: Snippet<[]>;
20
+ }
21
+
22
+ let {
23
+ collectionName,
24
+ params = $bindable(),
25
+ selectedRecords = $bindable(),
26
+ left
27
+ }: Props = $props();
28
+
29
+ let headerWidth: number = $state(0);
30
+ let headerIsSmall: boolean = $derived(headerWidth < 560);
31
+
32
+ async function handleDeleteButton() {
33
+ const result = await showDialog(
34
+ "Are you sure?",
35
+ "This will delete all the records you selected.",
36
+ );
37
+ if (result) {
38
+ const res = await lobb.deleteMany(collectionName, {
39
+ id: {
40
+ $in: selectedRecords,
41
+ },
42
+ });
43
+ if (res.status >= 400) {
44
+ const json = await res.json();
45
+ if (json.message === 'Record has children and cannot be deleted') {
46
+ const result = await showDialog(
47
+ "Delete record and all its children?",
48
+ "This record has related child entries. Deleting it will also remove all its children permanently. This action cannot be undone."
49
+ );
50
+ if (result) {
51
+ await lobb.deleteMany(
52
+ collectionName,
53
+ {
54
+ id: {
55
+ $in: selectedRecords,
56
+ },
57
+ },
58
+ true,
59
+ );
60
+ resetTable();
61
+ }
62
+ }
63
+ return;
64
+ }
65
+ resetTable();
66
+ }
67
+ }
68
+
69
+ function resetTable() {
70
+ params = { ...params };
71
+ selectedRecords = [];
72
+ }
73
+ </script>
74
+
75
+ <div
76
+ class="flex justify-between items-center gap-2 p-2 border-b bg-background h-10"
77
+ bind:clientWidth={headerWidth}
78
+ >
79
+ <div class="flex items-center gap-1">
80
+ {@render left?.()}
81
+ {#if selectedRecords.length}
82
+ <Button
83
+ Icon={Trash}
84
+ onclick={handleDeleteButton}
85
+ variant="outline"
86
+ class="h-7 px-3 text-xs font-normal"
87
+ >
88
+ Delete {selectedRecords.length}
89
+ {selectedRecords.length > 1 ? "records" : "record"}
90
+ </Button>
91
+ {:else}
92
+ <FilterButton
93
+ bind:filter={params.filter}
94
+ showText={!headerIsSmall}
95
+ {collectionName}
96
+ />
97
+ <SortButton
98
+ {collectionName}
99
+ bind:sort={params.sort}
100
+ showText={!headerIsSmall}
101
+ />
102
+ <LlmButton
103
+ variant="outline"
104
+ title="Filter table with AI"
105
+ description="Tell the AI how do you want to filter the table"
106
+ class="h-7 px-3 text-xs font-normal"
107
+ format={{
108
+ type: "json_object",
109
+ }}
110
+ messages={[
111
+ {
112
+ role: "system",
113
+ content: [
114
+ "You will receive natural language queries from a user describing how they want to filter a table list view.",
115
+ "Your task is to generate a filter object based on the user's prompt.",
116
+ `This is the schema of the filter json: ${JSON.stringify(ctx.meta.filter.filter_schema)}`,
117
+ `This is the schema of the current collection: ${JSON.stringify(ctx.meta.collections[collectionName])}`,
118
+ ].join(" "),
119
+ },
120
+ ]}
121
+ onApiResponseComplete={async (res) => {
122
+ params.filter = JSON.parse(res);
123
+ }}
124
+ >
125
+ {headerIsSmall ? "" : "Filter with AI"}
126
+ </LlmButton>
127
+ {/if}
128
+ </div>
129
+ <div>
130
+ <Button
131
+ variant="ghost"
132
+ class="h-7 px-2 font-normal text-muted-foreground"
133
+ Icon={ListRestart}
134
+ onclick={() => (params = { ...params })}
135
+ >
136
+ {headerIsSmall ? "" : "Refresh"}
137
+ </Button>
138
+ <CreateManyButton
139
+ {collectionName}
140
+ variant="outline"
141
+ class="h-7 px-2 text-xs font-normal"
142
+ Icon={SquareStack}
143
+ onSuccessfullSave={() => (params = { ...params })}
144
+ ></CreateManyButton>
145
+ <CreateDetailViewButton
146
+ {collectionName}
147
+ variant="default"
148
+ class="h-7 px-3 text-xs font-normal"
149
+ Icon={Plus}
150
+ onSuccessfullSave={() => (params = { ...params })}
151
+ >
152
+ {headerIsSmall ? "" : "Create"}
153
+ </CreateDetailViewButton>
154
+ </div>
155
+ </div>