@lobb-js/studio 0.7.1 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/package.json +3 -2
  2. package/src/App.svelte +5 -0
  3. package/src/app.css +124 -0
  4. package/src/lib/components/LlmButton.svelte +137 -0
  5. package/src/lib/components/Studio.svelte +129 -0
  6. package/src/lib/components/alertView.svelte +20 -0
  7. package/src/lib/components/breadCrumbs.svelte +60 -0
  8. package/src/lib/components/codeEditor.svelte +152 -0
  9. package/src/lib/components/combobox.svelte +92 -0
  10. package/src/lib/components/confirmationDialog/confirmationDialog.svelte +33 -0
  11. package/src/lib/components/confirmationDialog/store.svelte.ts +28 -0
  12. package/src/lib/components/createManyButton.svelte +109 -0
  13. package/src/lib/components/dataTable/childRecords.svelte +142 -0
  14. package/src/lib/components/dataTable/dataTable.svelte +225 -0
  15. package/src/lib/components/dataTable/fieldCell.svelte +77 -0
  16. package/src/lib/components/dataTable/filter.svelte +284 -0
  17. package/src/lib/components/dataTable/filterButton.svelte +39 -0
  18. package/src/lib/components/dataTable/footer.svelte +84 -0
  19. package/src/lib/components/dataTable/header.svelte +155 -0
  20. package/src/lib/components/dataTable/sort.svelte +173 -0
  21. package/src/lib/components/dataTable/sortButton.svelte +36 -0
  22. package/src/lib/components/dataTable/table.svelte +337 -0
  23. package/src/lib/components/dataTable/utils.ts +127 -0
  24. package/src/lib/components/detailView/create/children.svelte +70 -0
  25. package/src/lib/components/detailView/create/createDetailView.svelte +228 -0
  26. package/src/lib/components/detailView/create/createDetailViewButton.svelte +37 -0
  27. package/src/lib/components/detailView/create/createManyView.svelte +252 -0
  28. package/src/lib/components/detailView/create/subRecords.svelte +50 -0
  29. package/src/lib/components/detailView/detailViewForm.svelte +104 -0
  30. package/src/lib/components/detailView/fieldCustomInput.svelte +26 -0
  31. package/src/lib/components/detailView/fieldInput.svelte +258 -0
  32. package/src/lib/components/detailView/fieldInputReplacement.svelte +199 -0
  33. package/src/lib/components/detailView/store.svelte.ts +59 -0
  34. package/src/lib/components/detailView/update/children.svelte +96 -0
  35. package/src/lib/components/detailView/update/updateDetailView.svelte +176 -0
  36. package/src/lib/components/detailView/update/updateDetailViewButton.svelte +56 -0
  37. package/src/lib/components/detailView/utils.ts +176 -0
  38. package/src/lib/components/diffViewer.svelte +105 -0
  39. package/src/lib/components/drawer.svelte +28 -0
  40. package/src/lib/components/extensionsComponents.svelte +33 -0
  41. package/src/lib/components/foreingKeyInput.svelte +80 -0
  42. package/src/lib/components/header.svelte +45 -0
  43. package/src/lib/components/loadingTypesForMonacoEditor.ts +36 -0
  44. package/src/lib/components/miniSidebar.svelte +226 -0
  45. package/src/lib/components/rangeCalendarButton.svelte +257 -0
  46. package/src/lib/components/richTextEditor.svelte +284 -0
  47. package/src/lib/components/routes/collections/collection.svelte +57 -0
  48. package/src/lib/components/routes/collections/collections.svelte +45 -0
  49. package/src/lib/components/routes/data_model/dataModel.svelte +40 -0
  50. package/src/lib/components/routes/data_model/flow.css +22 -0
  51. package/src/lib/components/routes/data_model/flow.svelte +84 -0
  52. package/src/lib/components/routes/data_model/syncManager.svelte +94 -0
  53. package/src/lib/components/routes/data_model/utils.ts +35 -0
  54. package/src/lib/components/routes/extensions/extension.svelte +19 -0
  55. package/src/lib/components/routes/home.svelte +40 -0
  56. package/src/lib/components/routes/workflows/workflows.svelte +136 -0
  57. package/src/lib/components/selectRecord.svelte +130 -0
  58. package/src/lib/components/setServerPage.svelte +50 -0
  59. package/src/lib/components/sidebar/index.ts +4 -0
  60. package/src/lib/components/sidebar/sidebar.svelte +149 -0
  61. package/src/lib/components/sidebar/sidebarElements.svelte +144 -0
  62. package/src/lib/components/sidebar/sidebarTrigger.svelte +33 -0
  63. package/src/lib/components/singletone.svelte +71 -0
  64. package/src/lib/components/ui/accordion/accordion-content.svelte +22 -0
  65. package/src/lib/components/ui/accordion/accordion-item.svelte +12 -0
  66. package/src/lib/components/ui/accordion/accordion-trigger.svelte +31 -0
  67. package/src/lib/components/ui/accordion/index.ts +17 -0
  68. package/src/lib/components/ui/alert/alert-description.svelte +16 -0
  69. package/src/lib/components/ui/alert/alert-title.svelte +24 -0
  70. package/src/lib/components/ui/alert/alert.svelte +39 -0
  71. package/src/lib/components/ui/alert/index.ts +14 -0
  72. package/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte +13 -0
  73. package/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte +17 -0
  74. package/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte +26 -0
  75. package/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte +16 -0
  76. package/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte +20 -0
  77. package/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte +20 -0
  78. package/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte +19 -0
  79. package/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte +18 -0
  80. package/src/lib/components/ui/alert-dialog/index.ts +40 -0
  81. package/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +23 -0
  82. package/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte +16 -0
  83. package/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte +31 -0
  84. package/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte +23 -0
  85. package/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte +23 -0
  86. package/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte +27 -0
  87. package/src/lib/components/ui/breadcrumb/breadcrumb.svelte +15 -0
  88. package/src/lib/components/ui/breadcrumb/index.ts +25 -0
  89. package/src/lib/components/ui/button/button.svelte +110 -0
  90. package/src/lib/components/ui/button/index.ts +17 -0
  91. package/src/lib/components/ui/checkbox/checkbox.svelte +35 -0
  92. package/src/lib/components/ui/checkbox/index.ts +6 -0
  93. package/src/lib/components/ui/command/command-dialog.svelte +35 -0
  94. package/src/lib/components/ui/command/command-empty.svelte +12 -0
  95. package/src/lib/components/ui/command/command-group.svelte +31 -0
  96. package/src/lib/components/ui/command/command-input.svelte +25 -0
  97. package/src/lib/components/ui/command/command-item.svelte +19 -0
  98. package/src/lib/components/ui/command/command-link-item.svelte +19 -0
  99. package/src/lib/components/ui/command/command-list.svelte +16 -0
  100. package/src/lib/components/ui/command/command-separator.svelte +12 -0
  101. package/src/lib/components/ui/command/command-shortcut.svelte +20 -0
  102. package/src/lib/components/ui/command/command.svelte +21 -0
  103. package/src/lib/components/ui/command/index.ts +40 -0
  104. package/src/lib/components/ui/dialog/dialog-content.svelte +38 -0
  105. package/src/lib/components/ui/dialog/dialog-description.svelte +16 -0
  106. package/src/lib/components/ui/dialog/dialog-footer.svelte +20 -0
  107. package/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
  108. package/src/lib/components/ui/dialog/dialog-overlay.svelte +19 -0
  109. package/src/lib/components/ui/dialog/dialog-title.svelte +16 -0
  110. package/src/lib/components/ui/dialog/index.ts +37 -0
  111. package/src/lib/components/ui/input/index.ts +7 -0
  112. package/src/lib/components/ui/input/input.svelte +46 -0
  113. package/src/lib/components/ui/label/index.ts +7 -0
  114. package/src/lib/components/ui/label/label.svelte +19 -0
  115. package/src/lib/components/ui/popover/index.ts +17 -0
  116. package/src/lib/components/ui/popover/popover-content.svelte +28 -0
  117. package/src/lib/components/ui/range-calendar/index.ts +30 -0
  118. package/src/lib/components/ui/range-calendar/range-calendar-cell.svelte +19 -0
  119. package/src/lib/components/ui/range-calendar/range-calendar-day.svelte +35 -0
  120. package/src/lib/components/ui/range-calendar/range-calendar-grid-body.svelte +12 -0
  121. package/src/lib/components/ui/range-calendar/range-calendar-grid-head.svelte +12 -0
  122. package/src/lib/components/ui/range-calendar/range-calendar-grid-row.svelte +12 -0
  123. package/src/lib/components/ui/range-calendar/range-calendar-grid.svelte +16 -0
  124. package/src/lib/components/ui/range-calendar/range-calendar-head-cell.svelte +16 -0
  125. package/src/lib/components/ui/range-calendar/range-calendar-header.svelte +16 -0
  126. package/src/lib/components/ui/range-calendar/range-calendar-heading.svelte +16 -0
  127. package/src/lib/components/ui/range-calendar/range-calendar-months.svelte +20 -0
  128. package/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte +27 -0
  129. package/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte +27 -0
  130. package/src/lib/components/ui/range-calendar/range-calendar.svelte +57 -0
  131. package/src/lib/components/ui/select/index.ts +34 -0
  132. package/src/lib/components/ui/select/select-content.svelte +38 -0
  133. package/src/lib/components/ui/select/select-group-heading.svelte +16 -0
  134. package/src/lib/components/ui/select/select-item.svelte +37 -0
  135. package/src/lib/components/ui/select/select-scroll-down-button.svelte +19 -0
  136. package/src/lib/components/ui/select/select-scroll-up-button.svelte +19 -0
  137. package/src/lib/components/ui/select/select-separator.svelte +13 -0
  138. package/src/lib/components/ui/select/select-trigger.svelte +24 -0
  139. package/src/lib/components/ui/separator/index.ts +7 -0
  140. package/src/lib/components/ui/separator/separator.svelte +22 -0
  141. package/src/lib/components/ui/skeleton/index.ts +7 -0
  142. package/src/lib/components/ui/skeleton/skeleton.svelte +22 -0
  143. package/src/lib/components/ui/sonner/index.ts +1 -0
  144. package/src/lib/components/ui/sonner/sonner.svelte +20 -0
  145. package/src/lib/components/ui/switch/index.ts +7 -0
  146. package/src/lib/components/ui/switch/switch.svelte +27 -0
  147. package/src/lib/components/ui/textarea/index.ts +7 -0
  148. package/src/lib/components/ui/textarea/textarea.svelte +22 -0
  149. package/src/lib/components/ui/tooltip/index.ts +18 -0
  150. package/src/lib/components/ui/tooltip/tooltip-content.svelte +21 -0
  151. package/src/lib/components/workflowEditor.svelte +188 -0
  152. package/src/lib/context.ts +22 -0
  153. package/src/lib/eventSystem.ts +40 -0
  154. package/src/lib/extensions/extension.types.ts +92 -0
  155. package/src/lib/extensions/extensionUtils.ts +156 -0
  156. package/src/lib/index.ts +24 -0
  157. package/src/lib/store.svelte.ts +13 -0
  158. package/src/lib/store.types.ts +28 -0
  159. package/src/lib/utils.ts +68 -0
  160. package/src/main.ts +18 -0
  161. package/src/stories/detailView/detailViewForm.stories.svelte +79 -0
  162. package/src/vite-env.d.ts +2 -0
@@ -0,0 +1,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 "../components/ui/command/index.js";
6
+ import * as Popover from "../components/ui/popover/index.js";
7
+ import { Button } from "../components/ui/button/index.js";
8
+ import { cn } from "../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 "../../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,109 @@
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 CodeEditor from "./codeEditor.svelte";
7
+ import { getStudioContext } from "../context";
8
+ import { calculateDrawerWidth } from "../utils";
9
+
10
+ const { lobb } = getStudioContext();
11
+ import Drawer from "./drawer.svelte";
12
+
13
+ interface LocalProps extends ButtonProps {
14
+ collectionName: string;
15
+ onSuccessfullSave?: () => Promise<void>;
16
+ }
17
+
18
+ let { collectionName, onSuccessfullSave, ...rest }: LocalProps = $props();
19
+
20
+ let openDrawer = $state(false);
21
+ let createManyPayload = $state("");
22
+
23
+ async function handleCreateMany() {
24
+ const res = await lobb.createMany(
25
+ collectionName,
26
+ JSON.parse(createManyPayload),
27
+ );
28
+ if (res.status >= 400) {
29
+ return;
30
+ }
31
+ if (onSuccessfullSave) {
32
+ await onSuccessfullSave();
33
+ }
34
+ hideDrawer();
35
+ }
36
+
37
+ function showDrawer() {
38
+ if (!collectionName) {
39
+ toast.error("No collection is selected");
40
+ return;
41
+ }
42
+ openDrawer = true;
43
+ }
44
+
45
+ function hideDrawer() {
46
+ openDrawer = false;
47
+ createManyPayload = "";
48
+ }
49
+ </script>
50
+
51
+ <!-- THE SELECT BUTTON -->
52
+ <Button
53
+ variant={rest.variant}
54
+ class={rest.class}
55
+ Icon={rest.Icon}
56
+ onclick={showDrawer}
57
+ >
58
+ {#if rest.children}
59
+ {@render rest.children()}
60
+ {/if}
61
+ </Button>
62
+
63
+ <!-- THE SELECT DRAWER -->
64
+ {#if openDrawer}
65
+ <Drawer onHide={async () => hideDrawer()}>
66
+ <div class="flex h-12 items-center gap-4 border-b px-4">
67
+ <Button
68
+ variant="outline"
69
+ onclick={() => (openDrawer = false)}
70
+ class=" h-8 w-8 rounded-full text-xs font-normal"
71
+ Icon={ArrowLeft}
72
+ ></Button>
73
+ <div class="flex items-center gap-2">
74
+ <div class="text-sm">Create Many On</div>
75
+ <span class="rounded-md border bg-muted px-2 py-0.5 text-sm">
76
+ {collectionName}
77
+ </span>
78
+ </div>
79
+ </div>
80
+ <div class="flex-1 overflow-y-auto">
81
+ <CodeEditor
82
+ name={collectionName}
83
+ type="json"
84
+ bind:value={createManyPayload}
85
+ class="h-full border-0"
86
+ />
87
+ </div>
88
+ <div class="flex h-12 items-center justify-end gap-2 border-t px-4">
89
+ <div class="flex gap-3">
90
+ <Button
91
+ variant="outline"
92
+ onclick={() => hideDrawer()}
93
+ class="h-7 px-3 text-xs font-normal"
94
+ Icon={X}
95
+ >
96
+ Cancel
97
+ </Button>
98
+ <Button
99
+ variant="default"
100
+ class="h-7 px-3 text-xs font-normal"
101
+ Icon={Plus}
102
+ onclick={handleCreateMany}
103
+ >
104
+ Create Many
105
+ </Button>
106
+ </div>
107
+ </div>
108
+ </Drawer>
109
+ {/if}
@@ -0,0 +1,142 @@
1
+ <script lang="ts">
2
+ import { getStudioContext } from "../../context";
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
+ const { ctx, lobb } = getStudioContext();
10
+
11
+ interface Props {
12
+ collectionName: string;
13
+ recordId: string;
14
+ width: number;
15
+ unifiedBgColor?: "bg-muted/30" | "bg-background";
16
+ }
17
+
18
+ let { collectionName, recordId, width, unifiedBgColor }: Props = $props();
19
+
20
+ const relations = ctx.meta.relations.filter(
21
+ (relation) => relation.to.collection === collectionName,
22
+ );
23
+ let expandedRows: boolean[] = $state(
24
+ new Array(relations.length).fill(false),
25
+ );
26
+ let refreshDataTable = $state(true);
27
+ let tableHeaderWidth = $state(0);
28
+ </script>
29
+
30
+ <div class="flex" style="width: {width}px;">
31
+ <div
32
+ class="
33
+ flex justify-center border-r
34
+ {unifiedBgColor ? unifiedBgColor : 'bg-background'}
35
+ "
36
+ style="width: 40px"
37
+ ></div>
38
+ <div class="flex-1 flex flex-col">
39
+ {#each relations as relation, index}
40
+ {@const lastRow = relations.length - 1 === index}
41
+ {@const fromCollection = relation.from.collection}
42
+ {@const fromField = relation.from.field}
43
+ <div
44
+ class="
45
+ overflow-hidden
46
+ {unifiedBgColor ? unifiedBgColor : 'bg-background'}
47
+ "
48
+ >
49
+ <div
50
+ bind:clientWidth={tableHeaderWidth}
51
+ class="
52
+ flex justify-between items-center gap-2 text-sm h-10
53
+ {expandedRows[index] || !lastRow ? 'border-b' : ''}
54
+ "
55
+ >
56
+ <button
57
+ class="flex gap-2 px-2 flex-1 h-full items-center"
58
+ onclick={() => {
59
+ expandedRows[index] = !expandedRows[index];
60
+ }}
61
+ >
62
+ <ChevronRight
63
+ size="17.5"
64
+ class="text-muted-foreground transition-transform"
65
+ style={expandedRows[index]
66
+ ? "transform: rotate(90deg);"
67
+ : "transform: rotate(0deg);"}
68
+ />
69
+ <Table size="17.5" class="text-muted-foreground" />
70
+ <div class="text-muted-foreground">
71
+ {relation.from.collection}
72
+ </div>
73
+ </button>
74
+ <div class="flex items-center px-2">
75
+ <CreateDetailViewButton
76
+ collectionName={relation.from.collection}
77
+ variant="ghost"
78
+ class="h-7 px-3 text-xs font-normal"
79
+ Icon={Plus}
80
+ values={{
81
+ [fromField]: {
82
+ id: recordId,
83
+ },
84
+ }}
85
+ onSuccessfullSave={async () => {
86
+ refreshDataTable = !refreshDataTable;
87
+ }}
88
+ >
89
+ Create
90
+ </CreateDetailViewButton>
91
+ </div>
92
+ </div>
93
+ {#if expandedRows[index]}
94
+ <div
95
+ class="
96
+ flex max-h-96 overflow-auto
97
+ {lastRow ? '' : 'border-b'}
98
+ "
99
+ >
100
+ <div
101
+ class="border-r {unifiedBgColor
102
+ ? unifiedBgColor
103
+ : ''}"
104
+ style="width: 100vw; max-width: 40px"
105
+ ></div>
106
+ <div
107
+ class="flex-1"
108
+ style="width: {tableHeaderWidth - 40}px;"
109
+ >
110
+ {#key refreshDataTable}
111
+ <ExtensionsComponents
112
+ name="listView.entry.children.{fromCollection}"
113
+ collectionName={fromCollection}
114
+ filter={{
115
+ [fromField]: recordId,
116
+ }}
117
+ utils={getExtensionUtils(lobb, ctx)}
118
+ >
119
+ <DataTable
120
+ collectionName={fromCollection}
121
+ filter={{
122
+ [fromField]: recordId,
123
+ }}
124
+ showHeader={false}
125
+ showFooter={false}
126
+ showDelete={true}
127
+ {unifiedBgColor}
128
+ tableProps={{
129
+ showLastRowBorder: false,
130
+ showLastColumnBorder: false,
131
+ showCheckboxes: false,
132
+ }}
133
+ />
134
+ </ExtensionsComponents>
135
+ {/key}
136
+ </div>
137
+ </div>
138
+ {/if}
139
+ </div>
140
+ {/each}
141
+ </div>
142
+ </div>
@@ -0,0 +1,225 @@
1
+ <script lang="ts">
2
+ import _ from "lodash";
3
+ import { getStudioContext } from "../../context";
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 Button from "../ui/button/button.svelte";
14
+ import { showDialog } from "../confirmationDialog/store.svelte";
15
+ import UpdateDetailViewButton from "../detailView/update/updateDetailViewButton.svelte";
16
+ import { emitEvent } from "../../eventSystem";
17
+ import type { Snippet } from "svelte";
18
+
19
+ const { lobb, ctx } = getStudioContext();
20
+
21
+ interface Props {
22
+ collectionName: string;
23
+ filter?: any;
24
+ showHeader?: boolean;
25
+ showFooter?: boolean;
26
+ unifiedBgColor?: "bg-muted/30" | "bg-background";
27
+ showDelete?: boolean;
28
+ tableProps?: Partial<TableProps>;
29
+ headerLeft?: Snippet<[]>;
30
+ }
31
+
32
+ let {
33
+ collectionName,
34
+ filter,
35
+ showHeader = true,
36
+ showFooter = true,
37
+ unifiedBgColor,
38
+ showDelete = false,
39
+ tableProps,
40
+ headerLeft,
41
+ }: Props = $props();
42
+
43
+ const fields = getCollectionParamsFields(ctx, collectionName);
44
+ let params = $state({
45
+ fields: fields,
46
+ filter: filter ?? {},
47
+ sort: {},
48
+ limit: "100",
49
+ page: 1,
50
+ });
51
+
52
+ let selectedRecords = $state([]);
53
+ let totalCount = $state(0);
54
+ let data: TableProps["data"] = $state([]);
55
+ let loading = $state(true);
56
+ const columns: TableProps["columns"] = $state(
57
+ getCollectionColumns(ctx, collectionName),
58
+ );
59
+ let dataTableContainerWidth: number = $state(0);
60
+ let dataTableWidth: number = $state(0);
61
+ const doesCollectionHasChildren = Boolean(
62
+ ctx.meta.relations.find(
63
+ (relation) => relation.to.collection === collectionName,
64
+ ),
65
+ );
66
+
67
+ // requests the data from the server when the params is changed
68
+ $effect(() => {
69
+ loadData(params);
70
+ });
71
+
72
+ async function loadData(params: any) {
73
+ // parsing sort before sending the request
74
+ const paramsCopy = $state.snapshot(params);
75
+ const sort: TableProps["sort"] = paramsCopy.sort;
76
+ const sortStrings: string[] = [];
77
+ if (sort) {
78
+ for (const [key, value] of Object.entries(sort)) {
79
+ sortStrings.push(`${value === "asc" ? "" : "-"}${key}`);
80
+ }
81
+ }
82
+ paramsCopy.sort = sortStrings.join(",");
83
+
84
+ // sending the request
85
+ const response = await lobb.findAll(collectionName, paramsCopy);
86
+ const res = await response.json();
87
+
88
+ data = res.data;
89
+ totalCount = res.meta.totalCount;
90
+
91
+ loading = false;
92
+ }
93
+
94
+ async function handleDelete(entryId: string) {
95
+ const result = await showDialog(
96
+ "Are you sure?",
97
+ "This will delete the record you selected.",
98
+ );
99
+ if (result) {
100
+ await lobb.deleteOne(collectionName, entryId);
101
+ params = { ...params };
102
+ }
103
+ }
104
+
105
+ async function getWorkflowTools(
106
+ entry: Record<string, any>,
107
+ ): Promise<any[]> {
108
+ // TODO: instead of firing the events like this. get them all the fire them one by one to get their results
109
+ const eventResult = await emitEvent(
110
+ { lobb, ctx },
111
+ "studio.collections.listView.tools",
112
+ {
113
+ collectionName,
114
+ entry,
115
+ },
116
+ );
117
+
118
+ if (eventResult) {
119
+ return eventResult.tools;
120
+ }
121
+
122
+ return [];
123
+ }
124
+ </script>
125
+
126
+ <div
127
+ bind:clientWidth={dataTableContainerWidth}
128
+ class="
129
+ flex flex-col overflow-auto h-full w-full
130
+ {unifiedBgColor ? unifiedBgColor : ''}
131
+ "
132
+ >
133
+ {#if showHeader}
134
+ <Header bind:params {collectionName} bind:selectedRecords>
135
+ {#snippet left()}
136
+ {@render headerLeft?.()}
137
+ {/snippet}
138
+ </Header>
139
+ {/if}
140
+ <div class="relative flex-1 overflow-auto w-full">
141
+ {#if loading}
142
+ <div class="flex flex-col gap-2 p-2 w-full">
143
+ <Skeleton class="h-8 w-full" />
144
+ <Skeleton class="h-8 w-[80%]" />
145
+ <Skeleton class="h-8 w-[60%]" />
146
+ </div>
147
+ {:else}
148
+ <Table
149
+ {data}
150
+ {columns}
151
+ showCollapsible={doesCollectionHasChildren}
152
+ selectByColumn="id"
153
+ showLastRowBorder={true}
154
+ showLastColumnBorder={true}
155
+ bind:sort={params.sort}
156
+ bind:selectedRecords
157
+ {unifiedBgColor}
158
+ bind:tableWidth={dataTableWidth}
159
+ {...tableProps}
160
+ >
161
+ {#snippet tools(entry)}
162
+ <UpdateDetailViewButton
163
+ {collectionName}
164
+ recordId={entry.id}
165
+ variant="ghost"
166
+ class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
167
+ Icon={Pencil}
168
+ onSuccessfullSave={async () => {
169
+ params = { ...params };
170
+ }}
171
+ ></UpdateDetailViewButton>
172
+ {#if showDelete}
173
+ <Button
174
+ class="h-6 w-6 text-muted-foreground hover:bg-transparent"
175
+ variant="ghost"
176
+ size="icon"
177
+ onclick={() => handleDelete(entry.id)}
178
+ Icon={Trash}
179
+ ></Button>
180
+ {/if}
181
+ {#await getWorkflowTools($state.snapshot(entry))}
182
+ <div></div>
183
+ {:then workflowTools}
184
+ {#each workflowTools as workflowTool}
185
+ <Button
186
+ variant="ghost"
187
+ class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
188
+ Icon={icons[
189
+ workflowTool.icon as keyof typeof icons
190
+ ]}
191
+ onclick={workflowTool.onclick}
192
+ ></Button>
193
+ {/each}
194
+ {/await}
195
+ {/snippet}
196
+ {#snippet overrideCell(value, column, entry)}
197
+ <FieldCell
198
+ {collectionName}
199
+ fieldName={column.id}
200
+ {value}
201
+ {entry}
202
+ tableParams={params}
203
+ />
204
+ {/snippet}
205
+ {#snippet collapsible(entry)}
206
+ <ChildRecords
207
+ {collectionName}
208
+ recordId={entry.id}
209
+ width={dataTableWidth > dataTableContainerWidth
210
+ ? dataTableContainerWidth
211
+ : dataTableWidth}
212
+ unifiedBgColor={unifiedBgColor ?? "bg-background"}
213
+ />
214
+ {/snippet}
215
+ </Table>
216
+ {/if}
217
+ </div>
218
+ {#if showFooter}
219
+ <Footer
220
+ bind:currentPage={params.page}
221
+ bind:limit={params.limit}
222
+ {totalCount}
223
+ />
224
+ {/if}
225
+ </div>