@lobb-js/studio 0.1.31

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