@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,40 @@
1
+ <script>
2
+ import Button from "../../components/ui/button/button.svelte";
3
+ import { getStudioContext } from "../../context";
4
+ import { location } from "@wjfe/n-savant";
5
+
6
+ const { ctx } = getStudioContext();
7
+ import { ArrowRight } from "lucide-svelte";
8
+ </script>
9
+
10
+ <div class="flex flex-col">
11
+ <div
12
+ class="flex flex-1 w-full flex-col items-center justify-center gap-4 text-muted-foreground"
13
+ >
14
+ <div class="flex flex-col items-center justify-center p-4">
15
+ <div class="text-3xl">Welcome to Lobb!</div>
16
+ <div class="text-xs text-center">
17
+ Your journey starts here. Explore and make the most of your
18
+ experience.
19
+ </div>
20
+ </div>
21
+ <div class="flex flex-col items-center justify-center">
22
+ <Button
23
+ Icon={ArrowRight}
24
+ variant="outline"
25
+ class="h-7 px-3 text-xs font-normal"
26
+ onclick={() => location.navigate("/studio/collections")}
27
+ >
28
+ Go to collections
29
+ </Button>
30
+ </div>
31
+ </div>
32
+ <div class="flex justify-end p-2 text-xs text-muted-foreground/50">
33
+ <div class="flex flex-col text-end">
34
+ {#if ctx.studioVersion}
35
+ <div>studio: v{ctx.studioVersion}</div>
36
+ {/if}
37
+ <div>core: v{ctx.meta.version}</div>
38
+ </div>
39
+ </div>
40
+ </div>
@@ -0,0 +1,136 @@
1
+ <script lang="ts">
2
+ import type { SideBarData } from "../../../components/sidebar/sidebarElements.svelte";
3
+ import WorkflowEditor, {
4
+ type WorkflowEntry,
5
+ } from "../../../components/workflowEditor.svelte";
6
+ import { getStudioContext } from "../../../context";
7
+
8
+ const { lobb, ctx } = getStudioContext();
9
+ import Sidebar from "../../../components/sidebar/sidebar.svelte";
10
+ import Button from "../../../components/ui/button/button.svelte";
11
+ import { location } from "@wjfe/n-savant";
12
+ import { CircleSlash2, Plus, Trash2 } from "lucide-svelte";
13
+ import { onMount } from "svelte";
14
+ import { showDialog } from "../../../components/confirmationDialog/store.svelte";
15
+ import SidebarTrigger from "../../../components/sidebar/sidebarTrigger.svelte";
16
+
17
+ let { workflowName } = $props();
18
+
19
+ let sidebarData: SideBarData | null = $state(null);
20
+ let workflowEntry: WorkflowEntry | null = $state(null);
21
+
22
+ onMount(async () => {
23
+ getSidebarData();
24
+ });
25
+
26
+ $effect(() => {
27
+ fetchWorkflowData(workflowName);
28
+ });
29
+
30
+ async function getSidebarData() {
31
+ const response = await lobb.findAll("core_workflows", {});
32
+ const result = await response.json();
33
+ const workflows: any[] = result.data;
34
+ sidebarData = workflows.map((workflow) => {
35
+ return {
36
+ name: workflow.name,
37
+ path: workflow.directory,
38
+ onclick: () => {
39
+ location.navigate(`/studio/workflows/${workflow.name}`);
40
+ },
41
+ meta: {
42
+ id: workflow.id,
43
+ },
44
+ };
45
+ });
46
+ }
47
+
48
+ async function fetchWorkflowData(workflowName: string) {
49
+ if (workflowName && workflowName !== "new") {
50
+ const response = await lobb.findAll("core_workflows", {
51
+ filter: {
52
+ name: workflowName,
53
+ },
54
+ });
55
+ const result = await response.json();
56
+ const workflow = result.data[0];
57
+ workflowEntry = workflow;
58
+ } else {
59
+ const workflowHandlerDefaultValue =
60
+ ctx.meta.collections.core_workflows.fields.handler
61
+ .pre_processors.default;
62
+ workflowEntry = {
63
+ name: "",
64
+ event_name: "",
65
+ handler: workflowHandlerDefaultValue,
66
+ directory: "",
67
+ };
68
+ }
69
+ }
70
+
71
+ async function handleWorkflowDelete(
72
+ workflowName: string,
73
+ workflowId: string,
74
+ ) {
75
+ const result = await showDialog(
76
+ "Are you sure?",
77
+ "This will delete the Workflow you selected.",
78
+ );
79
+ if (result) {
80
+ await lobb.deleteOne("core_workflows", workflowId);
81
+ getSidebarData();
82
+ if (workflowEntry && workflowName === workflowEntry.name) {
83
+ location.navigate("/studio/workflows");
84
+ }
85
+ }
86
+ }
87
+ </script>
88
+
89
+ <Sidebar title="Workflows" data={sidebarData}>
90
+ {#snippet belowSearch()}
91
+ <div class="pb-4 px-2">
92
+ <Button
93
+ class="h-7 px-3 text-xs font-normal w-full"
94
+ variant="outline"
95
+ onclick={() => location.navigate("/studio/workflows/new")}
96
+ Icon={Plus}
97
+ >
98
+ Create a Workflow
99
+ </Button>
100
+ </div>
101
+ {/snippet}
102
+ {#snippet elementRightSide(element)}
103
+ <Button
104
+ class="h-6 w-6 text-muted-foreground hover:bg-transparent"
105
+ variant="ghost"
106
+ size="icon"
107
+ onclick={() => handleWorkflowDelete(element.name, element.meta?.id)}
108
+ Icon={Trash2}
109
+ ></Button>
110
+ {/snippet}
111
+ <div class="relative h-full w-full">
112
+ {#if workflowName === undefined}
113
+ <div
114
+ class="flex h-full w-full flex-col items-center justify-center gap-4 text-muted-foreground"
115
+ >
116
+ <CircleSlash2 class="opacity-50" size="50" />
117
+ <div class="flex flex-col items-center justify-center">
118
+ <div>No workflow selected</div>
119
+ <div class="text-xs">
120
+ Select a workflow to edit it or create new ones
121
+ </div>
122
+ </div>
123
+ </div>
124
+ {:else if workflowEntry}
125
+ {#key workflowEntry}
126
+ <WorkflowEditor
127
+ bind:workflow={workflowEntry}
128
+ refreshSidebar={getSidebarData}
129
+ />
130
+ {/key}
131
+ {/if}
132
+ <div class="absolute top-0 left-0 p-2.5">
133
+ <SidebarTrigger />
134
+ </div>
135
+ </div>
136
+ </Sidebar>
@@ -0,0 +1,130 @@
1
+ <script lang="ts">
2
+ import { ArrowLeft, Link } from "lucide-svelte";
3
+ import Button, { type ButtonProps } from "./ui/button/button.svelte";
4
+ import DataTable from "./dataTable/dataTable.svelte";
5
+ import { getCollectionPrimaryField } from "./dataTable/utils";
6
+ import { emitEvent } from "../eventSystem";
7
+ import { getStudioContext } from "../context";
8
+ import Drawer from "./drawer.svelte";
9
+
10
+ const { ctx, lobb } = getStudioContext();
11
+
12
+ interface LocalProps extends ButtonProps {
13
+ collectionName: string;
14
+ parentCollectionName?: string;
15
+ fieldName?: string;
16
+ value?: any;
17
+ onSelect?: (entry: any) => void;
18
+ filter?: Record<string, any>;
19
+ additionalFilter?: Record<string, any>;
20
+ text?: string;
21
+ entry?: Record<string, any>;
22
+ }
23
+
24
+ let {
25
+ parentCollectionName,
26
+ collectionName,
27
+ fieldName,
28
+ value = $bindable(),
29
+ onSelect,
30
+ filter = {},
31
+ additionalFilter = {},
32
+ text,
33
+ entry,
34
+ ...restProps
35
+ }: LocalProps = $props();
36
+
37
+ let openDrawer = $state(false);
38
+
39
+ async function handleButtonClick() {
40
+ try {
41
+ const eventResult = await emitEvent(
42
+ { lobb, ctx },
43
+ "studio.collections.preForeignKeySelect",
44
+ {
45
+ parentCollectionName,
46
+ collectionName,
47
+ fieldName,
48
+ entry,
49
+ },
50
+ );
51
+ if (eventResult.filter) {
52
+ additionalFilter = eventResult.filter;
53
+ }
54
+
55
+ openDrawer = true;
56
+ } catch (error) {
57
+ console.error(error);
58
+ }
59
+ }
60
+
61
+ async function onSelectHandler(entry: any) {
62
+ const primaryFieldName = getCollectionPrimaryField(ctx, collectionName);
63
+ const localValue: any = {
64
+ id: entry.id,
65
+ };
66
+ if (primaryFieldName) {
67
+ const primaryFieldValue = entry[primaryFieldName];
68
+ localValue[primaryFieldName] = primaryFieldValue;
69
+ }
70
+ value = localValue;
71
+
72
+ // calling the onSelect callback function
73
+ if (onSelect) {
74
+ onSelect(entry);
75
+ }
76
+
77
+ // closing the drawer
78
+ openDrawer = false;
79
+ }
80
+ </script>
81
+
82
+ <!-- THE SELECT BUTTON -->
83
+ <Button onclick={handleButtonClick} {...restProps}>
84
+ {#if restProps.children}
85
+ {@render restProps.children()}
86
+ {:else}
87
+ <Link size="13" />
88
+ {#if text}
89
+ {text}
90
+ {:else}
91
+ Select record
92
+ {/if}
93
+ {/if}
94
+ </Button>
95
+
96
+ <!-- THE SELECT DRAWER -->
97
+ {#if openDrawer}
98
+ <Drawer onHide={async () => { openDrawer = false }}>
99
+ <div class="flex h-12 items-center gap-4 border-b px-4">
100
+ <Button
101
+ variant="outline"
102
+ onclick={() => (openDrawer = false)}
103
+ class=" h-8 w-8 rounded-full text-xs font-normal"
104
+ Icon={ArrowLeft}
105
+ ></Button>
106
+ <div class="flex items-center gap-2">
107
+ <div class="text-sm">Select record from</div>
108
+ <span
109
+ class="rounded-md border bg-muted px-2 py-0.5 text-sm"
110
+ >
111
+ {collectionName}
112
+ </span>
113
+ </div>
114
+ </div>
115
+ <div class="flex-1 overflow-y-auto bg-muted">
116
+ <DataTable
117
+ {collectionName}
118
+ tableProps={{
119
+ showCheckboxes: false,
120
+ select: {
121
+ onSelect: onSelectHandler,
122
+ },
123
+ }}
124
+ filter={{
125
+ $and: [filter, additionalFilter],
126
+ }}
127
+ />
128
+ </div>
129
+ </Drawer>
130
+ {/if}
@@ -0,0 +1,50 @@
1
+ <script lang="ts">
2
+ import { toast } from "svelte-sonner";
3
+ import Button from "./ui/button/button.svelte";
4
+ import { getStudioContext } from "../context";
5
+ import { Input } from "../components/ui/input";
6
+
7
+ const { ctx } = getStudioContext();
8
+
9
+ let formData = {
10
+ server: "",
11
+ };
12
+
13
+ async function handleClick(e: Event) {
14
+ if (!formData.server) {
15
+ toast.error("Please fill the server url");
16
+ return;
17
+ }
18
+ try {
19
+ await fetch(formData.server);
20
+ } catch (error) {
21
+ toast.error(`Couldn't connect to the (${formData.server}) server`);
22
+ return;
23
+ }
24
+ localStorage.setItem("lobb_url", formData.server);
25
+ ctx.lobbUrl = formData.server;
26
+ }
27
+ </script>
28
+
29
+ <div
30
+ class="fixed left-0 top-0 flex h-screen w-screen items-center justify-center bg-muted"
31
+ >
32
+ <div class="flex w-full max-w-100 flex-col gap-6 p-6">
33
+ <div>
34
+ <div class="text-4xl">Welcome back</div>
35
+ <div>Sign in to your account</div>
36
+ </div>
37
+ <div class="flex flex-col gap-6 rounded-md border bg-white p-6">
38
+ <div class="flex flex-col gap-2">
39
+ <div>
40
+ <div class="mb-1 text-sm font-medium">Server</div>
41
+ <Input
42
+ bind:value={formData.server}
43
+ placeholder="http://example.lobb.com"
44
+ />
45
+ </div>
46
+ </div>
47
+ <Button onclick={handleClick}>Save & Connect</Button>
48
+ </div>
49
+ </div>
50
+ </div>
@@ -0,0 +1,4 @@
1
+ import Sidebar from "./sidebar.svelte";
2
+ import SidebarTrigger from "./sidebarTrigger.svelte";
3
+
4
+ export { Sidebar, SidebarTrigger }
@@ -0,0 +1,149 @@
1
+ <script lang="ts" module>
2
+ export interface SidebarProperties {
3
+ collapsed: boolean;
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts">
8
+ import { setContext, type Snippet } from "svelte";
9
+
10
+ import { mediaQueries } from "../../utils";
11
+ import Skeleton from "../ui/skeleton/skeleton.svelte";
12
+ import { Search } from "lucide-svelte";
13
+ import SidebarElements, {
14
+ type SideBarData,
15
+ type SideBarElement,
16
+ type SidebarElementsProps,
17
+ } from "./sidebarElements.svelte";
18
+
19
+ interface Props {
20
+ title: string;
21
+ data: SideBarData | null;
22
+ sidebarElementsProps?: Partial<SidebarElementsProps>;
23
+ showSearch?: boolean;
24
+ children: Snippet<[]>;
25
+ belowSearch?: Snippet;
26
+ elementRightSide?: Snippet<[SideBarElement]>;
27
+ }
28
+
29
+ let {
30
+ title,
31
+ data,
32
+ sidebarElementsProps,
33
+ showSearch = true,
34
+ children,
35
+ belowSearch,
36
+ elementRightSide,
37
+ }: Props = $props();
38
+
39
+ let visibleData = $derived(data ? [...data] : null);
40
+ const sidebarProperties: SidebarProperties = $state({
41
+ collapsed: false,
42
+ });
43
+ setContext("sidebarProperties", sidebarProperties);
44
+
45
+ $effect(() => {
46
+ if (mediaQueries.lg.current) {
47
+ sidebarProperties.collapsed = false;
48
+ } else {
49
+ sidebarProperties.collapsed = true;
50
+ }
51
+ });
52
+
53
+ let searchTerm = $state("");
54
+
55
+ const sidebarWidth = 275;
56
+ let sidebarContainerHeight = $state(0);
57
+ let sidebarContainerWidth = $state(0);
58
+ let sidebarHeaderHeight = $state(0);
59
+ let sidebarBodyHeight = $derived(
60
+ sidebarContainerHeight - sidebarHeaderHeight,
61
+ );
62
+
63
+ function handleSearch() {
64
+ if (data) {
65
+ visibleData = filterSidebarData(data, searchTerm.toLowerCase());
66
+ }
67
+ }
68
+
69
+ function filterSidebarData(items: SideBarData, term: string): SideBarData {
70
+ return items.filter((item) => item.name.includes(term));
71
+ }
72
+ </script>
73
+
74
+ <div
75
+ class="flex h-full transition-[grid-template-columns]"
76
+ style="
77
+ display: grid;
78
+ grid-template-columns: {sidebarProperties.collapsed ? 0 : sidebarWidth}px 1fr;
79
+ "
80
+ bind:clientHeight={sidebarContainerHeight}
81
+ bind:clientWidth={sidebarContainerWidth}
82
+ >
83
+ <div
84
+ class="
85
+ bg-background border-r overflow-hidden
86
+ "
87
+ style="
88
+ {sidebarProperties.collapsed ? 'border-right-width: 0px; padding: 0px;' : ''}
89
+ height: {sidebarContainerHeight}px;
90
+ "
91
+ >
92
+ {#if visibleData !== null}
93
+ <div bind:clientHeight={sidebarHeaderHeight} class="p-2 pb-0!">
94
+ <div
95
+ class="relative flex justify-between items-center p-2 font-medium"
96
+ >
97
+ {title}
98
+ </div>
99
+ {#if showSearch}
100
+ <div class="p-2">
101
+ <div
102
+ class="flex items-center px-4 py-1 text-muted-foreground bg-muted/30 border rounded-md"
103
+ >
104
+ <input
105
+ type="text"
106
+ class="w-full bg-transparent text-sm focus:outline-none"
107
+ oninput={handleSearch}
108
+ bind:value={searchTerm}
109
+ placeholder="Search"
110
+ />
111
+ <Search size="20" />
112
+ </div>
113
+ </div>
114
+ {/if}
115
+ </div>
116
+ <div
117
+ class="
118
+ text-primary p-2 overflow-y-auto overflow-x-clip
119
+ "
120
+ style="max-height: {sidebarBodyHeight}px;"
121
+ >
122
+ {@render belowSearch?.()}
123
+ {#key visibleData}
124
+ <SidebarElements
125
+ bind:data={visibleData}
126
+ {elementRightSide}
127
+ {...sidebarElementsProps}
128
+ />
129
+ {/key}
130
+ </div>
131
+ {:else}
132
+ <div class="flex flex-col gap-2 p-2">
133
+ <Skeleton class="h-6 w-full" />
134
+ <Skeleton class="h-6 w-full" />
135
+ <Skeleton class="h-6 w-full" />
136
+ <Skeleton class="h-6 w-full" />
137
+ <Skeleton class="h-6 w-full" />
138
+ </div>
139
+ {/if}
140
+ </div>
141
+ <div
142
+ class="overflow-auto h-full"
143
+ style="
144
+ max-height: {sidebarContainerHeight}px;
145
+ "
146
+ >
147
+ {@render children()}
148
+ </div>
149
+ </div>
@@ -0,0 +1,144 @@
1
+ <script lang="ts" module>
2
+ export interface SideBarElement {
3
+ name: string;
4
+ onclick?: () => Promise<void> | void;
5
+ href?: string;
6
+ icon?: any;
7
+ path?: string;
8
+ meta?: Record<string, any>;
9
+ }
10
+
11
+ export type SideBarData = Array<SideBarElement>;
12
+
13
+ export interface SidebarElementsProps {
14
+ data: SideBarData;
15
+ path?: string[];
16
+ elementRightSide?: Snippet<[SideBarElement]>;
17
+ }
18
+ </script>
19
+
20
+ <script lang="ts">
21
+ import { Ban } from "lucide-svelte";
22
+ import type { Snippet } from "svelte";
23
+ import SidebarElements from "./sidebarElements.svelte";
24
+ import Button from "../ui/button/button.svelte";
25
+ import { location } from "@wjfe/n-savant";
26
+
27
+ let {
28
+ data = $bindable(),
29
+ path = [],
30
+ elementRightSide,
31
+ }: SidebarElementsProps = $props();
32
+
33
+ const elementsToShow = data.reduce<Array<string | SideBarElement>>(
34
+ (acc, item, index) => {
35
+ const firstPath = item.path?.split("/")[0];
36
+ if (firstPath && item.path && !acc.includes(firstPath)) {
37
+ acc.push(firstPath);
38
+ } else if (!item.path) {
39
+ acc.push(item);
40
+ }
41
+ return acc;
42
+ },
43
+ [],
44
+ );
45
+
46
+ let expandedHeights: number[] = $state(Array(data?.length).fill(0));
47
+
48
+ async function handleElementClick(element: SideBarElement) {
49
+ if (element.onclick) {
50
+ await element.onclick();
51
+ }
52
+ }
53
+
54
+ function getDirElements(dirName: string) {
55
+ let elements = data.filter((item) => item.path?.startsWith(dirName));
56
+ elements = elements.map((item) => {
57
+ return {
58
+ ...item,
59
+ path: item.path?.split("/").slice(1).join("/"),
60
+ };
61
+ });
62
+ return elements;
63
+ }
64
+ </script>
65
+
66
+ <div class="flex flex-col">
67
+ {#if elementsToShow.length}
68
+ {#each elementsToShow as element, index}
69
+ {#if typeof element === "string"}
70
+ {@const directoryName = element.split("/")[0]}
71
+ <button
72
+ class="
73
+ flex items-center justify-between p-2 gap-2 text-muted-foreground
74
+ rounded-md cursor-default
75
+ "
76
+ >
77
+ <div class="flex items-center gap-2">
78
+ <div class="text-xs">
79
+ {directoryName}
80
+ </div>
81
+ </div>
82
+ </button>
83
+ {#if getDirElements(directoryName)}
84
+ <div
85
+ class="overflow-hidden"
86
+ style="
87
+ height: {true ? expandedHeights[index] : 0}px;
88
+ "
89
+ >
90
+ <div
91
+ bind:clientHeight={expandedHeights[index]}
92
+ class="border-l ml-4 pl-2"
93
+ >
94
+ <SidebarElements
95
+ data={getDirElements(element)}
96
+ path={[...path, element]}
97
+ {elementRightSide}
98
+ />
99
+ </div>
100
+ </div>
101
+ {/if}
102
+ {:else}
103
+ {@const elementPath = [...path, element]}
104
+ {@const isselected = location.url.pathname === element.href}
105
+ <Button
106
+ onclick={() => handleElementClick(element)}
107
+ href={element.href}
108
+ variant="ghost"
109
+ class="
110
+ flex items-center justify-between p-2 gap-2 hover:bg-muted/30 text-muted-foreground
111
+ rounded-md {isselected ? 'bg-muted' : ''}
112
+ "
113
+ title={element.name}
114
+ >
115
+ <div class="flex items-center gap-2 truncate">
116
+ {#if element.icon}
117
+ <element.icon size="17.5" />
118
+ {/if}
119
+ <div
120
+ class="
121
+ text-xs
122
+ {isselected ? 'text-primary font-medium' : ''}
123
+ "
124
+ >
125
+ {element.name}
126
+ </div>
127
+ </div>
128
+ <div class="flex gap-2 items-center">
129
+ {#if elementRightSide}
130
+ {@render elementRightSide(element)}
131
+ {/if}
132
+ </div>
133
+ </Button>
134
+ {/if}
135
+ {/each}
136
+ {:else}
137
+ <div
138
+ class="flex justify-center items-center gap-2 text-muted-foreground"
139
+ >
140
+ <Ban size="17.5" />
141
+ <div class="text-xs text-center">No result</div>
142
+ </div>
143
+ {/if}
144
+ </div>
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import type { SidebarProperties } from "./sidebar.svelte";
3
+ import { PanelLeftClose, PanelLeftOpen } from "lucide-svelte";
4
+ import { getContext } from "svelte";
5
+ import type { HTMLButtonAttributes } from "svelte/elements";
6
+
7
+ interface Props {
8
+ class?: HTMLButtonAttributes["class"];
9
+ }
10
+
11
+ let { class: className }: Props = $props();
12
+
13
+ const sidebarProperties: SidebarProperties =
14
+ getContext("sidebarProperties");
15
+ </script>
16
+
17
+ {#if sidebarProperties.collapsed}
18
+ <PanelLeftOpen
19
+ class="text-muted-foreground hover:bg-transparent cursor-pointer hover:text-foreground {className}"
20
+ size="18"
21
+ onclick={() => {
22
+ sidebarProperties.collapsed = !sidebarProperties.collapsed;
23
+ }}
24
+ />
25
+ {:else}
26
+ <PanelLeftClose
27
+ class="text-muted-foreground hover:bg-transparent cursor-pointer hover:text-foreground {className}"
28
+ size="18"
29
+ onclick={() => {
30
+ sidebarProperties.collapsed = !sidebarProperties.collapsed;
31
+ }}
32
+ />
33
+ {/if}