@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,92 @@
1
+ <script lang="ts">
2
+ import CheckIcon from "@lucide/svelte/icons/check";
3
+ import ChevronsUpDownIcon from "@lucide/svelte/icons/chevrons-up-down";
4
+ import { tick } from "svelte";
5
+ import * as Command from "$lib/components/ui/command/index.js";
6
+ import * as Popover from "$lib/components/ui/popover/index.js";
7
+ import { Button } from "$lib/components/ui/button/index.js";
8
+ import { cn } from "$lib/utils.js";
9
+ import type { HTMLButtonAttributes } from "svelte/elements";
10
+
11
+ interface Option {
12
+ value: string;
13
+ label: string;
14
+ }
15
+
16
+ interface Props {
17
+ placeholder: string;
18
+ searchPlaceholder: string;
19
+ options: Option[];
20
+ value: string;
21
+ buttonClass?: HTMLButtonAttributes["class"];
22
+ }
23
+
24
+ let {
25
+ placeholder,
26
+ searchPlaceholder,
27
+ options,
28
+ value = $bindable(),
29
+ buttonClass,
30
+ }: Props = $props();
31
+
32
+ let open = $state(false);
33
+ let triggerRef = $state<HTMLButtonElement>(null!);
34
+
35
+ const selectedValue = $derived(
36
+ options.find((option) => option.value === value)?.label,
37
+ );
38
+
39
+ // We want to refocus the trigger button when the user selects
40
+ // an item from the list so users can continue navigating the
41
+ // rest of the form with the keyboard.
42
+ function closeAndFocusTrigger() {
43
+ open = false;
44
+ tick().then(() => {
45
+ triggerRef.focus();
46
+ });
47
+ }
48
+ </script>
49
+
50
+ <Popover.Root bind:open>
51
+ <Popover.Trigger bind:ref={triggerRef}>
52
+ {#snippet child({ props })}
53
+ <Button
54
+ variant="outline"
55
+ class="w-full justify-between {buttonClass}"
56
+ {...props}
57
+ role="combobox"
58
+ aria-expanded={open}
59
+ >
60
+ {selectedValue || placeholder}
61
+ <ChevronsUpDownIcon class="opacity-50" />
62
+ </Button>
63
+ {/snippet}
64
+ </Popover.Trigger>
65
+ <Popover.Content class="w-[320px] p-0" align="start">
66
+ <Command.Root>
67
+ <Command.Input placeholder={searchPlaceholder} />
68
+ <Command.List>
69
+ <Command.Empty>No result found.</Command.Empty>
70
+ <Command.Group value="frameworks">
71
+ {#each options as option (option.value)}
72
+ <Command.Item
73
+ value={option.value}
74
+ onSelect={() => {
75
+ value = option.value;
76
+ closeAndFocusTrigger();
77
+ }}
78
+ >
79
+ <CheckIcon
80
+ class={cn(
81
+ value !== option.value &&
82
+ "text-transparent",
83
+ )}
84
+ />
85
+ {option.label}
86
+ </Command.Item>
87
+ {/each}
88
+ </Command.Group>
89
+ </Command.List>
90
+ </Command.Root>
91
+ </Popover.Content>
92
+ </Popover.Root>
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import * as AlertDialog from "$lib/components/ui/alert-dialog/index";
3
+ import { Check, X } from "lucide-svelte";
4
+ import Button from "../ui/button/button.svelte";
5
+
6
+ interface DialogProps {
7
+ title: string;
8
+ description: string;
9
+ onDecision: (result: boolean) => void;
10
+ }
11
+
12
+ const { title, description, onDecision }: DialogProps = $props();
13
+
14
+ </script>
15
+
16
+ <AlertDialog.Root open={true}>
17
+ <AlertDialog.Content>
18
+ <AlertDialog.Header>
19
+ <AlertDialog.Title>{title}</AlertDialog.Title>
20
+ <AlertDialog.Description>
21
+ {description}
22
+ </AlertDialog.Description>
23
+ </AlertDialog.Header>
24
+ <AlertDialog.Footer>
25
+ <Button variant="outline" Icon={X} onclick={() => onDecision(false)}>
26
+ Cancel
27
+ </Button>
28
+ <Button Icon={Check} onclick={() => onDecision(true)}>
29
+ Continue
30
+ </Button>
31
+ </AlertDialog.Footer>
32
+ </AlertDialog.Content>
33
+ </AlertDialog.Root>
@@ -0,0 +1,28 @@
1
+ import { mount, unmount } from "svelte";
2
+ import ConfirmationDialog from "./confirmationDialog.svelte";
3
+
4
+ export function showDialog(title: string, description: string): Promise<boolean> {
5
+ return new Promise((resolve) => {
6
+ const targetElement = document.querySelector('main');
7
+
8
+ if (!targetElement) {
9
+ throw new Error("main html element doesn't exist for some reason");
10
+ }
11
+
12
+ const mountedElement = mount(ConfirmationDialog, {
13
+ target: targetElement,
14
+ props: {
15
+ title,
16
+ description,
17
+ onDecision: async (result: boolean) => {
18
+ resolve(result);
19
+ await unmount(mountedElement, {
20
+ outro: true
21
+ });
22
+ }
23
+ },
24
+ });
25
+
26
+ console.log("the dialog is mounted man")
27
+ });
28
+ }
@@ -0,0 +1,107 @@
1
+ <script lang="ts">
2
+ import { fade, fly } from "svelte/transition";
3
+ import { ArrowLeft, Plus, X } from "lucide-svelte";
4
+ import Button, { type ButtonProps } from "./ui/button/button.svelte";
5
+ import { toast } from "svelte-sonner";
6
+ import MonacoEditor from "./monacoEditor.svelte";
7
+ import { lobb } from "$lib";
8
+ import { calculateDrawerWidth } from "$lib/utils";
9
+ import Drawer from "./drawer.svelte";
10
+
11
+ interface LocalProps extends ButtonProps {
12
+ collectionName: string;
13
+ onSuccessfullSave?: () => Promise<void>;
14
+ }
15
+
16
+ let { collectionName, onSuccessfullSave, ...rest }: LocalProps = $props();
17
+
18
+ let openDrawer = $state(false);
19
+ let createManyPayload = $state("");
20
+
21
+ async function handleCreateMany() {
22
+ const res = await lobb.createMany(
23
+ collectionName,
24
+ JSON.parse(createManyPayload),
25
+ );
26
+ if (res.status >= 400) {
27
+ return;
28
+ }
29
+ if (onSuccessfullSave) {
30
+ await onSuccessfullSave();
31
+ }
32
+ hideDrawer();
33
+ }
34
+
35
+ function showDrawer() {
36
+ if (!collectionName) {
37
+ toast.error("No collection is selected");
38
+ return;
39
+ }
40
+ openDrawer = true;
41
+ }
42
+
43
+ function hideDrawer() {
44
+ openDrawer = false;
45
+ createManyPayload = "";
46
+ }
47
+ </script>
48
+
49
+ <!-- THE SELECT BUTTON -->
50
+ <Button
51
+ variant={rest.variant}
52
+ class={rest.class}
53
+ Icon={rest.Icon}
54
+ onclick={showDrawer}
55
+ >
56
+ {#if rest.children}
57
+ {@render rest.children()}
58
+ {/if}
59
+ </Button>
60
+
61
+ <!-- THE SELECT DRAWER -->
62
+ {#if openDrawer}
63
+ <Drawer onHide={async () => hideDrawer()}>
64
+ <div class="flex h-12 items-center gap-4 border-b px-4">
65
+ <Button
66
+ variant="outline"
67
+ onclick={() => (openDrawer = false)}
68
+ class=" h-8 w-8 rounded-full text-xs font-normal"
69
+ Icon={ArrowLeft}
70
+ ></Button>
71
+ <div class="flex items-center gap-2">
72
+ <div class="text-sm">Create Many On</div>
73
+ <span class="rounded-md border bg-muted px-2 py-0.5 text-sm">
74
+ {collectionName}
75
+ </span>
76
+ </div>
77
+ </div>
78
+ <div class="flex-1 overflow-y-auto">
79
+ <MonacoEditor
80
+ name={collectionName}
81
+ type="json"
82
+ bind:value={createManyPayload}
83
+ class="h-full border-0"
84
+ />
85
+ </div>
86
+ <div class="flex h-12 items-center justify-end gap-2 border-t px-4">
87
+ <div class="flex gap-3">
88
+ <Button
89
+ variant="outline"
90
+ onclick={() => hideDrawer()}
91
+ class="h-7 px-3 text-xs font-normal"
92
+ Icon={X}
93
+ >
94
+ Cancel
95
+ </Button>
96
+ <Button
97
+ variant="default"
98
+ class="h-7 px-3 text-xs font-normal"
99
+ Icon={Plus}
100
+ onclick={handleCreateMany}
101
+ >
102
+ Create Many
103
+ </Button>
104
+ </div>
105
+ </div>
106
+ </Drawer>
107
+ {/if}
@@ -0,0 +1,140 @@
1
+ <script lang="ts">
2
+ import { ctx } from "$lib/store.svelte";
3
+ import { ChevronRight, Plus, Smartphone, Table } from "lucide-svelte";
4
+ import DataTable from "./dataTable.svelte";
5
+ import CreateDetailViewButton from "../detailView/create/createDetailViewButton.svelte";
6
+ import ExtensionsComponents from "../extensionsComponents.svelte";
7
+ import { getExtensionUtils } from "../../../extensions/extensionUtils";
8
+
9
+ interface Props {
10
+ collectionName: string;
11
+ recordId: string;
12
+ width: number;
13
+ unifiedBgColor?: "bg-soft" | "bg-background";
14
+ }
15
+
16
+ let { collectionName, recordId, width, unifiedBgColor }: Props = $props();
17
+
18
+ const relations = ctx.meta.relations.filter(
19
+ (relation) => relation.to.collection === collectionName,
20
+ );
21
+ let expandedRows: boolean[] = $state(
22
+ new Array(relations.length).fill(false),
23
+ );
24
+ let refreshDataTable = $state(true);
25
+ let tableHeaderWidth = $state(0);
26
+ </script>
27
+
28
+ <div class="flex" style="width: {width}px;">
29
+ <div
30
+ class="
31
+ flex justify-center border-r
32
+ {unifiedBgColor ? unifiedBgColor : 'bg-background'}
33
+ "
34
+ style="width: 40px"
35
+ ></div>
36
+ <div class="flex-1 flex flex-col">
37
+ {#each relations as relation, index}
38
+ {@const lastRow = relations.length - 1 === index}
39
+ {@const fromCollection = relation.from.collection}
40
+ {@const fromField = relation.from.field}
41
+ <div
42
+ class="
43
+ overflow-hidden
44
+ {unifiedBgColor ? unifiedBgColor : 'bg-background'}
45
+ "
46
+ >
47
+ <div
48
+ bind:clientWidth={tableHeaderWidth}
49
+ class="
50
+ flex justify-between items-center gap-2 text-sm h-10
51
+ {expandedRows[index] || !lastRow ? 'border-b' : ''}
52
+ "
53
+ >
54
+ <button
55
+ class="flex gap-2 px-2 flex-1 h-full items-center"
56
+ onclick={() => {
57
+ expandedRows[index] = !expandedRows[index];
58
+ }}
59
+ >
60
+ <ChevronRight
61
+ size="17.5"
62
+ class="text-muted-foreground transition-transform"
63
+ style={expandedRows[index]
64
+ ? "transform: rotate(90deg);"
65
+ : "transform: rotate(0deg);"}
66
+ />
67
+ <Table size="17.5" class="text-muted-foreground" />
68
+ <div class="text-muted-foreground">
69
+ {relation.from.collection}
70
+ </div>
71
+ </button>
72
+ <div class="flex items-center px-2">
73
+ <CreateDetailViewButton
74
+ collectionName={relation.from.collection}
75
+ variant="ghost"
76
+ class="h-7 px-3 text-xs font-normal"
77
+ Icon={Plus}
78
+ values={{
79
+ [fromField]: {
80
+ id: recordId,
81
+ },
82
+ }}
83
+ onSuccessfullSave={async () => {
84
+ refreshDataTable = !refreshDataTable;
85
+ }}
86
+ >
87
+ Create
88
+ </CreateDetailViewButton>
89
+ </div>
90
+ </div>
91
+ {#if expandedRows[index]}
92
+ <div
93
+ class="
94
+ flex max-h-96 overflow-auto
95
+ {lastRow ? '' : 'border-b'}
96
+ "
97
+ >
98
+ <div
99
+ class="border-r {unifiedBgColor
100
+ ? unifiedBgColor
101
+ : ''}"
102
+ style="width: 100vw; max-width: 40px"
103
+ ></div>
104
+ <div
105
+ class="flex-1"
106
+ style="width: {tableHeaderWidth - 40}px;"
107
+ >
108
+ {#key refreshDataTable}
109
+ <ExtensionsComponents
110
+ name="listView.entry.children.{fromCollection}"
111
+ collectionName={fromCollection}
112
+ filter={{
113
+ [fromField]: recordId,
114
+ }}
115
+ utils={getExtensionUtils()}
116
+ >
117
+ <DataTable
118
+ collectionName={fromCollection}
119
+ filter={{
120
+ [fromField]: recordId,
121
+ }}
122
+ showHeader={false}
123
+ showFooter={false}
124
+ showDelete={true}
125
+ {unifiedBgColor}
126
+ tableProps={{
127
+ showLastRowBorder: false,
128
+ showLastColumnBorder: false,
129
+ showCheckboxes: false,
130
+ }}
131
+ />
132
+ </ExtensionsComponents>
133
+ {/key}
134
+ </div>
135
+ </div>
136
+ {/if}
137
+ </div>
138
+ {/each}
139
+ </div>
140
+ </div>
@@ -0,0 +1,223 @@
1
+ <script lang="ts">
2
+ import _ from "lodash";
3
+ import { lobb } from "$lib";
4
+ import Footer from "./footer.svelte";
5
+ import Header from "./header.svelte";
6
+ import Table, { type TableProps } from "./table.svelte";
7
+ import { getCollectionColumns, getCollectionParamsFields } from "./utils";
8
+ import { Pencil, Trash } from "lucide-svelte";
9
+ import * as icons from "lucide-svelte";
10
+ import ChildRecords from "./childRecords.svelte";
11
+ import FieldCell from "./fieldCell.svelte";
12
+ import Skeleton from "../ui/skeleton/skeleton.svelte";
13
+ import { ctx } from "$lib/store.svelte";
14
+ import Button from "../ui/button/button.svelte";
15
+ import { showDialog } from "../confirmationDialog/store.svelte";
16
+ import UpdateDetailViewButton from "../detailView/update/updateDetailViewButton.svelte";
17
+ import { emitEvent } from "$lib/eventSystem";
18
+ import type { Snippet } from "svelte";
19
+
20
+ interface Props {
21
+ collectionName: string;
22
+ filter?: any;
23
+ showHeader?: boolean;
24
+ showFooter?: boolean;
25
+ unifiedBgColor?: "bg-soft" | "bg-background";
26
+ showDelete?: boolean;
27
+ tableProps?: Partial<TableProps>;
28
+ headerLeft?: Snippet<[]>;
29
+ }
30
+
31
+ let {
32
+ collectionName,
33
+ filter,
34
+ showHeader = true,
35
+ showFooter = true,
36
+ unifiedBgColor,
37
+ showDelete = false,
38
+ tableProps,
39
+ headerLeft,
40
+ }: Props = $props();
41
+
42
+ const fields = getCollectionParamsFields(collectionName);
43
+ let params = $state({
44
+ fields: fields,
45
+ filter: filter ?? {},
46
+ sort: {},
47
+ limit: "100",
48
+ page: 1,
49
+ });
50
+
51
+ let selectedRecords = $state([]);
52
+ let totalCount = $state(0);
53
+ let data: TableProps["data"] = $state([]);
54
+ let loading = $state(true);
55
+ const columns: TableProps["columns"] = $state(
56
+ getCollectionColumns(collectionName),
57
+ );
58
+ let dataTableContainerWidth: number = $state(0);
59
+ let dataTableWidth: number = $state(0);
60
+ const doesCollectionHasChildren = Boolean(
61
+ ctx.meta.relations.find(
62
+ (relation) => relation.to.collection === collectionName,
63
+ ),
64
+ );
65
+
66
+ // requests the data from the server when the params is changed
67
+ $effect(() => {
68
+ loadData(params);
69
+ });
70
+
71
+ async function loadData(params: any) {
72
+ // parsing sort before sending the request
73
+ const paramsCopy = $state.snapshot(params);
74
+ const sort: TableProps["sort"] = paramsCopy.sort;
75
+ const sortStrings: string[] = [];
76
+ if (sort) {
77
+ for (const [key, value] of Object.entries(sort)) {
78
+ sortStrings.push(`${value === "asc" ? "" : "-"}${key}`);
79
+ }
80
+ }
81
+ paramsCopy.sort = sortStrings.join(",");
82
+
83
+ // sending the request
84
+ const response = await lobb.findAll(collectionName, paramsCopy);
85
+ const res = await response.json();
86
+
87
+ data = res.data;
88
+ totalCount = res.meta.totalCount;
89
+
90
+ loading = false;
91
+ }
92
+
93
+ async function handleDelete(entryId: string) {
94
+ const result = await showDialog(
95
+ "Are you sure?",
96
+ "This will delete the record you selected.",
97
+ );
98
+ if (result) {
99
+ await lobb.deleteOne(collectionName, entryId);
100
+ params = { ...params };
101
+ }
102
+ }
103
+
104
+ async function getWorkflowTools(
105
+ entry: Record<string, any>,
106
+ ): Promise<any[]> {
107
+ // TODO: instead of firing the events like this. get them all the fire them one by one to get their results
108
+ const eventResult = await emitEvent(
109
+ "studio.collections.listView.tools",
110
+ {
111
+ collectionName,
112
+ entry,
113
+ },
114
+ );
115
+
116
+ if (eventResult) {
117
+ return eventResult.tools;
118
+ }
119
+
120
+ return [];
121
+ }
122
+ </script>
123
+
124
+ <div
125
+ bind:clientWidth={dataTableContainerWidth}
126
+ class="
127
+ flex flex-col overflow-auto h-full w-full
128
+ {unifiedBgColor ? unifiedBgColor : ''}
129
+ "
130
+ >
131
+ {#if showHeader}
132
+ <Header bind:params {collectionName} bind:selectedRecords>
133
+ {#snippet left()}
134
+ {@render headerLeft?.()}
135
+ {/snippet}
136
+ </Header>
137
+ {/if}
138
+ <div class="relative flex-1 overflow-auto w-full">
139
+ {#if loading}
140
+ <div class="flex flex-col gap-2 p-2 w-full">
141
+ <Skeleton class="h-8 w-full" />
142
+ <Skeleton class="h-8 w-[80%]" />
143
+ <Skeleton class="h-8 w-[60%]" />
144
+ </div>
145
+ {:else}
146
+ <Table
147
+ {data}
148
+ {columns}
149
+ showCollapsible={doesCollectionHasChildren}
150
+ selectByColumn="id"
151
+ showLastRowBorder={true}
152
+ showLastColumnBorder={true}
153
+ bind:sort={params.sort}
154
+ bind:selectedRecords
155
+ {unifiedBgColor}
156
+ bind:tableWidth={dataTableWidth}
157
+ {...tableProps}
158
+ >
159
+ {#snippet tools(entry)}
160
+ <UpdateDetailViewButton
161
+ {collectionName}
162
+ recordId={entry.id}
163
+ variant="ghost"
164
+ class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
165
+ Icon={Pencil}
166
+ onSuccessfullSave={async () => {
167
+ params = { ...params };
168
+ }}
169
+ ></UpdateDetailViewButton>
170
+ {#if showDelete}
171
+ <Button
172
+ class="h-6 w-6 text-muted-foreground hover:bg-transparent"
173
+ variant="ghost"
174
+ size="icon"
175
+ onclick={() => handleDelete(entry.id)}
176
+ Icon={Trash}
177
+ ></Button>
178
+ {/if}
179
+ {#await getWorkflowTools($state.snapshot(entry))}
180
+ <div></div>
181
+ {:then workflowTools}
182
+ {#each workflowTools as workflowTool}
183
+ <Button
184
+ variant="ghost"
185
+ class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
186
+ Icon={icons[
187
+ workflowTool.icon as keyof typeof icons
188
+ ]}
189
+ onclick={workflowTool.onclick}
190
+ ></Button>
191
+ {/each}
192
+ {/await}
193
+ {/snippet}
194
+ {#snippet overrideCell(value, column, entry)}
195
+ <FieldCell
196
+ {collectionName}
197
+ fieldName={column.id}
198
+ {value}
199
+ {entry}
200
+ tableParams={params}
201
+ />
202
+ {/snippet}
203
+ {#snippet collapsible(entry)}
204
+ <ChildRecords
205
+ {collectionName}
206
+ recordId={entry.id}
207
+ width={dataTableWidth > dataTableContainerWidth
208
+ ? dataTableContainerWidth
209
+ : dataTableWidth}
210
+ unifiedBgColor={unifiedBgColor ?? "bg-background"}
211
+ />
212
+ {/snippet}
213
+ </Table>
214
+ {/if}
215
+ </div>
216
+ {#if showFooter}
217
+ <Footer
218
+ bind:currentPage={params.page}
219
+ bind:limit={params.limit}
220
+ {totalCount}
221
+ />
222
+ {/if}
223
+ </div>