@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,171 @@
1
+ <script lang="ts">
2
+ import _ from "lodash";
3
+
4
+ import * as Popover from "$lib/components/ui/popover/index.js";
5
+ import { ArrowDown, ArrowUp, GripVertical, Plus, X } from "lucide-svelte";
6
+ import Button, { buttonVariants } from "../ui/button/button.svelte";
7
+ import Label from "../ui/label/label.svelte";
8
+ import Switch from "../ui/switch/switch.svelte";
9
+ import { ctx } from "$lib/store.svelte";
10
+ import { getFieldIcon } from "./utils";
11
+
12
+ interface Props {
13
+ collectionName: string;
14
+ sort: Record<string, "asc" | "desc">;
15
+ showText: boolean;
16
+ }
17
+
18
+ let { collectionName, sort = $bindable({}), showText }: Props = $props();
19
+
20
+ let popoverOpen = $state(false);
21
+
22
+ function getFieldNames() {
23
+ const options = Object.keys(
24
+ ctx.meta.collections[collectionName].fields,
25
+ );
26
+ const existingPropertiesNames = Object.keys(sort);
27
+ const filteredOptions = _.difference(options, existingPropertiesNames);
28
+ return filteredOptions;
29
+ }
30
+
31
+ function moveProperty<T extends Record<string, any>>(
32
+ obj: T,
33
+ propertyToMove: keyof T,
34
+ direction: "up" | "down",
35
+ steps: number = 1,
36
+ ): T {
37
+ const keys = Object.keys(obj) as Array<keyof T>;
38
+ const currentIndex = keys.indexOf(propertyToMove);
39
+
40
+ if (currentIndex === -1) {
41
+ console.warn(
42
+ `Property '${String(propertyToMove)}' not found in the object.`,
43
+ );
44
+ return { ...obj };
45
+ }
46
+
47
+ const [removedKey] = keys.splice(currentIndex, 1) as [keyof T];
48
+
49
+ let newIndex: number;
50
+ if (direction === "up") {
51
+ newIndex = Math.max(0, currentIndex - steps);
52
+ } else if (direction === "down") {
53
+ newIndex = Math.min(keys.length, currentIndex + steps);
54
+ } else {
55
+ console.warn(
56
+ `Invalid direction specified: '${direction}'. Use 'up' or 'down'.`,
57
+ );
58
+ return { ...obj };
59
+ }
60
+
61
+ keys.splice(newIndex, 0, removedKey);
62
+
63
+ const newObject: T = {} as T;
64
+ keys.forEach((key) => {
65
+ newObject[key] = obj[key];
66
+ });
67
+
68
+ return newObject;
69
+ }
70
+ </script>
71
+
72
+ <div class="flex flex-col gap-2 p-2 text-muted-foreground">
73
+ {#if Object.keys(sort).length}
74
+ <div class="flex flex-col gap-2 text-muted-foreground">
75
+ {#each Object.entries(sort) as [fieldName, order]}
76
+ <div class="flex items-center justify-between text-xs">
77
+ <dir class="m-0 flex items-center gap-3 p-0">
78
+ <div class="flex gap-1">
79
+ <ArrowUp
80
+ onclick={() =>
81
+ (sort = moveProperty(
82
+ sort,
83
+ fieldName,
84
+ "up",
85
+ ))}
86
+ class="cursor-pointer hover:text-foreground"
87
+ size="15"
88
+ />
89
+ <ArrowDown
90
+ onclick={() =>
91
+ (sort = moveProperty(
92
+ sort,
93
+ fieldName,
94
+ "down",
95
+ ))}
96
+ class="cursor-pointer hover:text-foreground"
97
+ size="15"
98
+ />
99
+ </div>
100
+ <div class="text-foreground font-medium">
101
+ {fieldName}
102
+ </div>
103
+ </dir>
104
+ <dir class="m-0 flex items-center gap-2 p-0">
105
+ <div class="flex items-center space-x-2">
106
+ <Label class="text-xs">Ascending</Label>
107
+ <!-- <Switch checked={true} /> -->
108
+ <Switch
109
+ bind:checked={
110
+ () => sort[fieldName] === "asc",
111
+ (v) =>
112
+ (sort[fieldName] = v ? "asc" : "desc")
113
+ }
114
+ />
115
+ </div>
116
+ <Button
117
+ onclick={() => delete sort[fieldName]}
118
+ class="h-6 w-6 text-muted-foreground hover:bg-transparent"
119
+ variant="ghost"
120
+ size="icon"
121
+ Icon={X}
122
+ ></Button>
123
+ </dir>
124
+ </div>
125
+ {/each}
126
+ </div>
127
+ {:else}
128
+ <div class="flex flex-col gap-0.5 p-2">
129
+ <div class="text-xs text-foreground">
130
+ No sorts applied to this view
131
+ </div>
132
+ <div class="text-xs text-muted-foreground">
133
+ Add a column below to sort the view
134
+ </div>
135
+ </div>
136
+ {/if}
137
+ </div>
138
+ <div class="flex justify-between border-t p-2">
139
+ {#if getFieldNames().length}
140
+ <Popover.Root bind:open={popoverOpen}>
141
+ <Popover.Trigger
142
+ class={buttonVariants({
143
+ variant: "ghost",
144
+ class: "h-7 px-3 text-xs font-normal",
145
+ })}
146
+ >
147
+ <Plus />
148
+ Add a sort rule
149
+ </Popover.Trigger>
150
+ <Popover.Content class="flex w-48 flex-col p-2">
151
+ {#each getFieldNames() as fieldName}
152
+ {@const FieldIcon = getFieldIcon(fieldName, collectionName)}
153
+ <button
154
+ onclick={() => {
155
+ sort[fieldName] = "asc";
156
+ popoverOpen = false;
157
+ }}
158
+ class="flex cursor-pointer items-center gap-2 rounded-md p-2 px-3 text-xs text-muted-foreground hover:bg-muted hover:text-foreground"
159
+ >
160
+ <FieldIcon size="15" />
161
+ <div>{fieldName}</div>
162
+ </button>
163
+ {/each}
164
+ </Popover.Content>
165
+ </Popover.Root>
166
+ {:else}
167
+ <div class="flex items-center text-xs text-muted-foreground">
168
+ All columns have been added
169
+ </div>
170
+ {/if}
171
+ </div>
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import * as Popover from "$lib/components/ui/popover/index.js";
3
+ import { ArrowDownWideNarrow } from "lucide-svelte";
4
+ import { buttonVariants } from "$lib/components/ui/button";
5
+ import Sort from "./sort.svelte";
6
+
7
+ interface Props {
8
+ collectionName: string;
9
+ sort: Record<string, "asc" | "desc">;
10
+ showText: boolean;
11
+ }
12
+
13
+ let { collectionName, sort = $bindable({}), showText }: Props = $props();
14
+ </script>
15
+
16
+ <Popover.Root>
17
+ <Popover.Trigger
18
+ class={buttonVariants({
19
+ variant: "ghost",
20
+ class: "h-7 px-3 text-xs font-normal",
21
+ })}
22
+ >
23
+ <ArrowDownWideNarrow />
24
+ {#if showText}
25
+ {@const sortRules = Object.keys(sort).length}
26
+ {#if sortRules}
27
+ Sorted by {sortRules} rules
28
+ {:else}
29
+ Sort
30
+ {/if}
31
+ {/if}
32
+ </Popover.Trigger>
33
+ <Popover.Content class="w-screen max-w-[20rem] p-0">
34
+ <Sort {collectionName} {showText} bind:sort />
35
+ </Popover.Content>
36
+ </Popover.Root>
@@ -0,0 +1,337 @@
1
+ <script lang="ts" module>
2
+ interface Column {
3
+ id: string;
4
+ icon?: any;
5
+ subtext?: any;
6
+ }
7
+
8
+ type Entry = Record<string, any>;
9
+
10
+ interface Select {
11
+ onSelect: (entry: any) => void;
12
+ }
13
+
14
+ export interface TableProps {
15
+ data: Entry[];
16
+ columns?: Column[];
17
+ showCollapsible?: boolean;
18
+
19
+ // sorting
20
+ sort?: Record<string, "asc" | "desc">;
21
+ localSorting?: boolean;
22
+
23
+ // checkboxes
24
+ selectedRecords?: Array<any>;
25
+ selectByColumn?: string | null;
26
+ showCheckboxes?: boolean;
27
+
28
+ // styles
29
+ showLastRowBorder?: boolean;
30
+ showLastColumnBorder?: boolean;
31
+
32
+ // snippets
33
+ overrideCell?: Snippet<[any, Column, Entry]>;
34
+ tools?: Snippet<[Entry, number]>;
35
+ collapsible?: Snippet<[Entry, number]>;
36
+
37
+ // other
38
+ parentWidth?: number;
39
+ unifiedBgColor?: "bg-soft" | "bg-background";
40
+ select?: Select;
41
+ tableWidth?: number;
42
+ }
43
+ </script>
44
+
45
+ <script lang="ts">
46
+ import {
47
+ ArrowDownNarrowWide,
48
+ ArrowUpWideNarrow,
49
+ ChevronRight,
50
+ CircleOff,
51
+ } from "lucide-svelte";
52
+ import Checkbox from "../ui/checkbox/checkbox.svelte";
53
+ import { orderBy } from "lodash";
54
+ import type { Snippet } from "svelte";
55
+ import Button from "../ui/button/button.svelte";
56
+
57
+ let {
58
+ data,
59
+ columns = Object.keys(data[0]).map((key) => {
60
+ return {
61
+ id: key,
62
+ };
63
+ }),
64
+ showCollapsible = false,
65
+ sort = $bindable({}),
66
+ localSorting = false,
67
+ selectedRecords = $bindable(),
68
+ showCheckboxes = true,
69
+ selectByColumn,
70
+ showLastRowBorder,
71
+ showLastColumnBorder,
72
+ parentWidth,
73
+ overrideCell,
74
+ tools,
75
+ collapsible,
76
+ unifiedBgColor,
77
+ select,
78
+ tableWidth = $bindable(),
79
+ }: TableProps = $props();
80
+
81
+ let expandedRows: boolean[] = $state(new Array(data.length).fill(false));
82
+
83
+ // calculate columns count
84
+ const toolsExists = selectedRecords || tools ? 1 : 0;
85
+ const columnsLength = columns.length + toolsExists;
86
+
87
+ // set table width
88
+ let columnsWidths: number[] = $state([]);
89
+ $effect(() => {
90
+ tableWidth =
91
+ columnsWidths.reduce((sum, width) => sum + width, 0) +
92
+ (columnsLength - 1);
93
+ });
94
+
95
+ function sortHandler(columnId: string) {
96
+ if (sort[columnId] === "asc") {
97
+ sort[columnId] = "desc";
98
+ } else if (sort[columnId] === "desc") {
99
+ delete sort[columnId];
100
+ } else {
101
+ sort[columnId] = "asc";
102
+ }
103
+ sort = { ...sort };
104
+
105
+ // update the data based on the new sorts
106
+ if (localSorting) {
107
+ if (Object.keys(sort).length) {
108
+ data = orderBy(data, Object.keys(sort), Object.values(sort));
109
+ } else {
110
+ const columnNames = columns.map((column) => column.id);
111
+ const defaultSorts = columns.map((column) => "asc") as Array<
112
+ "asc" | "desc"
113
+ >;
114
+ data = orderBy(data, columnNames, defaultSorts);
115
+ }
116
+ }
117
+ }
118
+
119
+ function toggleCheckAllHandler(value: boolean) {
120
+ if (value) {
121
+ if (selectByColumn) {
122
+ selectedRecords = data.map(
123
+ (entry, index) => entry[selectByColumn],
124
+ );
125
+ } else {
126
+ selectedRecords = data.map((entry, index) => index);
127
+ }
128
+ } else {
129
+ selectedRecords = [];
130
+ }
131
+ }
132
+
133
+ function checkBoxHandler(event: MouseEvent, entry: any, index: number) {
134
+ event.preventDefault();
135
+
136
+ const value =
137
+ selectByColumn && entry.hasOwnProperty(selectByColumn)
138
+ ? entry[selectByColumn]
139
+ : index;
140
+
141
+ if (selectedRecords) {
142
+ const foundIndex = selectedRecords.findIndex((v) => v === value);
143
+
144
+ if (foundIndex !== -1) {
145
+ selectedRecords.splice(foundIndex, 1);
146
+ } else {
147
+ selectedRecords.push(value);
148
+ }
149
+ selectedRecords = [...selectedRecords];
150
+ }
151
+ }
152
+
153
+ function isCheckedHandler(entry: any, index: number): boolean {
154
+ const value =
155
+ selectByColumn && entry.hasOwnProperty(selectByColumn)
156
+ ? entry[selectByColumn]
157
+ : index;
158
+
159
+ if (selectedRecords) {
160
+ return selectedRecords.includes(value);
161
+ }
162
+
163
+ return false;
164
+ }
165
+ </script>
166
+
167
+ <div
168
+ style="
169
+ display: grid;
170
+ grid-template-columns: minmax(auto, 7.5rem) repeat({columnsLength -
171
+ 1}, minmax(auto, 15rem));
172
+ grid-template-rows: 2.5rem;
173
+ "
174
+ >
175
+ {#if selectedRecords || tools}
176
+ <div
177
+ bind:clientWidth={columnsWidths[0]}
178
+ class="
179
+ sticky left-0 top-0 z-20
180
+ flex items-center p-2.5 text-xs h-10
181
+ border-r border-b gap-2
182
+ {unifiedBgColor ? unifiedBgColor : 'bg-soft'}
183
+ "
184
+ >
185
+ <!-- collapsable toggle -->
186
+ {#if showCollapsible}
187
+ <div class="w-[20px]"></div>
188
+ {/if}
189
+ {#if selectedRecords && showCheckboxes}
190
+ <Checkbox
191
+ class="border-muted-foreground hover:border-foreground"
192
+ onCheckedChange={toggleCheckAllHandler}
193
+ checked={Boolean(selectedRecords.length)}
194
+ />
195
+ {/if}
196
+ </div>
197
+ {/if}
198
+ {#each columns as column, index}
199
+ {@const lastColumn = columns.length - 1 === index}
200
+ {@const ColumnIcon = column.icon}
201
+ <button
202
+ bind:clientWidth={columnsWidths[index + toolsExists]}
203
+ onclick={() => sortHandler(column.id)}
204
+ class="
205
+ sticky top-0 z-10
206
+ flex items-center p-2.5 text-xs h-10
207
+ {unifiedBgColor ? unifiedBgColor : 'bg-soft'}
208
+ {lastColumn && !showLastColumnBorder ? '' : 'border-r'}
209
+ border-b gap-2
210
+ "
211
+ >
212
+ {#if sort[column.id] === "asc"}
213
+ <ArrowDownNarrowWide
214
+ size="12.5"
215
+ class="text-muted-foreground"
216
+ />
217
+ {:else if sort[column.id] === "desc"}
218
+ <ArrowUpWideNarrow size="12.5" class="text-muted-foreground" />
219
+ {:else}
220
+ <ColumnIcon size="12.5" class="text-muted-foreground" />
221
+ {/if}
222
+ <div class="font-bold">{column.id}</div>
223
+ <div class="text-muted-foreground text-[0.7rem]">
224
+ {column.subtext}
225
+ </div>
226
+ </button>
227
+ {/each}
228
+ {#if Object.keys(data).length}
229
+ {#each data as entry, index}
230
+ {@const isDisabled = Boolean(entry.__disabled)}
231
+ {@const lastRow = data.length - 1 === index}
232
+ {#if selectedRecords || tools}
233
+ <div
234
+ class="
235
+ sticky left-0
236
+ flex items-center p-2.5 text-xs h-10
237
+ {unifiedBgColor ? unifiedBgColor : 'bg-background'}
238
+ border-r gap-2
239
+ "
240
+ >
241
+ <!-- collapsable toggle -->
242
+ {#if showCollapsible}
243
+ <Button
244
+ variant="ghost"
245
+ class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent transition-transform"
246
+ style={expandedRows[index]
247
+ ? "transform: rotate(90deg);"
248
+ : "transform: rotate(0deg);"}
249
+ Icon={ChevronRight}
250
+ onclick={() => {
251
+ expandedRows[index] = !expandedRows[index];
252
+ expandedRows = [...expandedRows];
253
+ }}
254
+ disabled={isDisabled}
255
+ ></Button>
256
+ {/if}
257
+ {#if selectedRecords && showCheckboxes}
258
+ <Checkbox
259
+ class="border-muted-foreground
260
+ hover:border-foreground"
261
+ onclick={(event) =>
262
+ checkBoxHandler(event, entry, index)}
263
+ checked={isCheckedHandler(entry, index)}
264
+ disabled={isDisabled}
265
+ />
266
+ {/if}
267
+ {#if tools && !isDisabled}
268
+ {@render tools(entry, index)}
269
+ {/if}
270
+ </div>
271
+ {/if}
272
+ {#each columns as column, index}
273
+ {@const lastColumn = columns.length - 1 === index}
274
+ {@const fieldValue = entry[column.id]}
275
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
276
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
277
+ <div
278
+ onclick={() => {
279
+ select?.onSelect(entry);
280
+ }}
281
+ class="
282
+ flex items-center p-2.5 text-xs h-10 text-nowrap overflow-clip
283
+ {select ? 'cursor-pointer' : ''}
284
+ {unifiedBgColor ? unifiedBgColor : 'bg-background'}
285
+ {lastColumn && !showLastColumnBorder ? '' : 'border-r'}
286
+ "
287
+ >
288
+ {#if overrideCell}
289
+ {@render overrideCell(fieldValue, column, entry)}
290
+ {:else}
291
+ {fieldValue}
292
+ {/if}
293
+ </div>
294
+ {/each}
295
+ <!-- nested data -->
296
+ <div
297
+ style="grid-column: span {columnsLength};"
298
+ class="
299
+ {!showLastColumnBorder ? '' : 'border-r'}
300
+ {lastRow && !showLastRowBorder ? '' : 'border-b'}
301
+ "
302
+ >
303
+ <div
304
+ style="
305
+ {parentWidth ? `width: ${parentWidth}px` : ''};
306
+ max-width: 100vw;
307
+ {expandedRows[index] ? '' : 'height: 0px;'}
308
+ "
309
+ class="
310
+ sticky left-0 top-0 overflow-auto bg-soft
311
+ {unifiedBgColor ? unifiedBgColor : ''}
312
+ {expandedRows[index] ? 'border-t' : ''}
313
+ "
314
+ >
315
+ {#if collapsible && expandedRows[index]}
316
+ {@render collapsible(entry, index)}
317
+ {/if}
318
+ </div>
319
+ </div>
320
+ {/each}
321
+ {/if}
322
+ </div>
323
+ {#if Object.keys(data).length === 0}
324
+ <div
325
+ class="
326
+ sticky left-0
327
+ flex justify-center items-center gap-2 text-xs
328
+ text-muted-foreground text-center rounded-md p-2 h-14
329
+ "
330
+ style="
331
+ {parentWidth ? `width: ${parentWidth}px` : ''};
332
+ "
333
+ >
334
+ <CircleOff size="17.5" />
335
+ No result
336
+ </div>
337
+ {/if}
@@ -0,0 +1,127 @@
1
+ import type { TableProps } from "./table.svelte";
2
+
3
+ import { ctx } from "$lib/store.svelte";
4
+ import {
5
+ Binary,
6
+ Braces,
7
+ Brackets,
8
+ Calendar,
9
+ CalendarClock,
10
+ Clock,
11
+ Hash,
12
+ Key,
13
+ Text,
14
+ Type,
15
+ } from "lucide-svelte/icons";
16
+
17
+ export function getCollectionColumns(collectionName: string): TableProps['columns'] {
18
+ const collectionFields = getFields(collectionName);
19
+ const headers: TableProps['columns'] = [];
20
+ for (const fieldName in collectionFields) {
21
+ const field = collectionFields[fieldName];
22
+ headers.push({
23
+ id: field.key,
24
+ subtext: field.type,
25
+ icon: getFieldIcon(fieldName, collectionName),
26
+ });
27
+ }
28
+ return headers;
29
+ }
30
+
31
+ export function getFieldIcon(fieldName: string, collectionName: string) {
32
+ const field = getField(fieldName, collectionName);
33
+ if (fieldName === "id") {
34
+ return Key;
35
+ } else if (field.type === "string") {
36
+ return Type;
37
+ } else if (field.type === "text") {
38
+ return Text;
39
+ } else if (field.type === "object") {
40
+ return Braces;
41
+ } else if (field.type === "array") {
42
+ return Brackets;
43
+ } else if (field.type === "bool") {
44
+ return Binary;
45
+ } else if (field.type === "integer") {
46
+ return Hash;
47
+ } else if (field.type === "long") {
48
+ return Hash;
49
+ } else if (field.type === "float") {
50
+ return Hash;
51
+ } else if (field.type === "decimal") {
52
+ return Hash;
53
+ } else if (field.type === "date") {
54
+ return Calendar;
55
+ } else if (field.type === "datetime") {
56
+ return CalendarClock;
57
+ } else if (field.type === "time") {
58
+ return Clock;
59
+ }
60
+
61
+ throw new Error(`(${field.type}) doesnt have an icon`);
62
+ }
63
+
64
+ export function getFields(collectionName: string) {
65
+ return ctx.meta.collections[collectionName].fields;
66
+ }
67
+
68
+ export function getField(fieldName: string, collectionName: string) {
69
+ return getFields(collectionName)[fieldName];
70
+ }
71
+
72
+ export function getCollectionPrimaryField(collectionName: string): string | undefined {
73
+ const collectionFields =
74
+ ctx.meta.collections[collectionName].fields;
75
+
76
+ const primaryFieldObject: any = Object.values(collectionFields).find(
77
+ (field) => (field as any).type === "string",
78
+ );
79
+
80
+ if (
81
+ primaryFieldObject &&
82
+ typeof primaryFieldObject === "object" &&
83
+ "key" in primaryFieldObject
84
+ ) {
85
+ const fieldName = primaryFieldObject.key;
86
+ return fieldName;
87
+ }
88
+ }
89
+
90
+ export function getCollectionParamsFields(collectionName: string, allFields = false) {
91
+ const relations = ctx.meta.relations;
92
+ const foreignFields = relations
93
+ .filter((relation) => {
94
+ return relation.from.collection === collectionName
95
+ })
96
+ .map((relation) => {
97
+ return {
98
+ field: relation.from.field,
99
+ collection: relation.to.collection,
100
+ };
101
+ });
102
+
103
+ const columns = [];
104
+ for (let index = 0; index < foreignFields.length; index++) {
105
+ const foreignField = foreignFields[index];
106
+ if (!foreignField.collection) {
107
+ continue;
108
+ }
109
+ columns.push(`${foreignField.field}.id`);
110
+
111
+ if (!allFields) {
112
+ const primaryField = getCollectionPrimaryField(foreignField.collection)
113
+ if (primaryField) {
114
+ columns.push(`${foreignField.field}.${primaryField}`);
115
+ }
116
+ } else {
117
+ const fieldNames = Object.keys(ctx.meta.collections[foreignField.collection].fields);
118
+ for (let index = 0; index < fieldNames.length; index++) {
119
+ const fieldName = fieldNames[index];
120
+ columns.push(`${foreignField.field}.${fieldName}`);
121
+ }
122
+ }
123
+ }
124
+
125
+ const foreignColumns = columns.join(",");
126
+ return `*${foreignColumns ? `,${foreignColumns}` : ""}`;
127
+ }