@lobb-js/studio 0.7.2 → 0.8.0
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.
- package/package.json +2 -1
- package/src/App.svelte +5 -0
- package/src/app.css +124 -0
- package/src/lib/components/LlmButton.svelte +137 -0
- package/src/lib/components/Studio.svelte +129 -0
- package/src/lib/components/alertView.svelte +20 -0
- package/src/lib/components/breadCrumbs.svelte +60 -0
- package/src/lib/components/codeEditor.svelte +152 -0
- package/src/lib/components/combobox.svelte +92 -0
- package/src/lib/components/confirmationDialog/confirmationDialog.svelte +33 -0
- package/src/lib/components/confirmationDialog/store.svelte.ts +28 -0
- package/src/lib/components/createManyButton.svelte +109 -0
- package/src/lib/components/dataTable/childRecords.svelte +142 -0
- package/src/lib/components/dataTable/dataTable.svelte +225 -0
- package/src/lib/components/dataTable/fieldCell.svelte +77 -0
- package/src/lib/components/dataTable/filter.svelte +284 -0
- package/src/lib/components/dataTable/filterButton.svelte +39 -0
- package/src/lib/components/dataTable/footer.svelte +84 -0
- package/src/lib/components/dataTable/header.svelte +155 -0
- package/src/lib/components/dataTable/sort.svelte +173 -0
- package/src/lib/components/dataTable/sortButton.svelte +36 -0
- package/src/lib/components/dataTable/table.svelte +337 -0
- package/src/lib/components/dataTable/utils.ts +127 -0
- package/src/lib/components/detailView/create/children.svelte +70 -0
- package/src/lib/components/detailView/create/createDetailView.svelte +228 -0
- package/src/lib/components/detailView/create/createDetailViewButton.svelte +37 -0
- package/src/lib/components/detailView/create/createManyView.svelte +252 -0
- package/src/lib/components/detailView/create/subRecords.svelte +50 -0
- package/src/lib/components/detailView/detailViewForm.svelte +104 -0
- package/src/lib/components/detailView/fieldCustomInput.svelte +26 -0
- package/src/lib/components/detailView/fieldInput.svelte +258 -0
- package/src/lib/components/detailView/fieldInputReplacement.svelte +199 -0
- package/src/lib/components/detailView/store.svelte.ts +59 -0
- package/src/lib/components/detailView/update/children.svelte +96 -0
- package/src/lib/components/detailView/update/updateDetailView.svelte +176 -0
- package/src/lib/components/detailView/update/updateDetailViewButton.svelte +56 -0
- package/src/lib/components/detailView/utils.ts +176 -0
- package/src/lib/components/diffViewer.svelte +105 -0
- package/src/lib/components/drawer.svelte +28 -0
- package/src/lib/components/extensionsComponents.svelte +33 -0
- package/src/lib/components/foreingKeyInput.svelte +80 -0
- package/src/lib/components/header.svelte +45 -0
- package/src/lib/components/loadingTypesForMonacoEditor.ts +36 -0
- package/src/lib/components/miniSidebar.svelte +226 -0
- package/src/lib/components/rangeCalendarButton.svelte +257 -0
- package/src/lib/components/richTextEditor.svelte +284 -0
- package/src/lib/components/routes/collections/collection.svelte +57 -0
- package/src/lib/components/routes/collections/collections.svelte +45 -0
- package/src/lib/components/routes/data_model/dataModel.svelte +40 -0
- package/src/lib/components/routes/data_model/flow.css +22 -0
- package/src/lib/components/routes/data_model/flow.svelte +84 -0
- package/src/lib/components/routes/data_model/syncManager.svelte +94 -0
- package/src/lib/components/routes/data_model/utils.ts +35 -0
- package/src/lib/components/routes/extensions/extension.svelte +19 -0
- package/src/lib/components/routes/home.svelte +40 -0
- package/src/lib/components/routes/workflows/workflows.svelte +136 -0
- package/src/lib/components/selectRecord.svelte +130 -0
- package/src/lib/components/setServerPage.svelte +50 -0
- package/src/lib/components/sidebar/index.ts +4 -0
- package/src/lib/components/sidebar/sidebar.svelte +149 -0
- package/src/lib/components/sidebar/sidebarElements.svelte +144 -0
- package/src/lib/components/sidebar/sidebarTrigger.svelte +33 -0
- package/src/lib/components/singletone.svelte +71 -0
- package/src/lib/components/ui/accordion/accordion-content.svelte +22 -0
- package/src/lib/components/ui/accordion/accordion-item.svelte +12 -0
- package/src/lib/components/ui/accordion/accordion-trigger.svelte +31 -0
- package/src/lib/components/ui/accordion/index.ts +17 -0
- package/src/lib/components/ui/alert/alert-description.svelte +16 -0
- package/src/lib/components/ui/alert/alert-title.svelte +24 -0
- package/src/lib/components/ui/alert/alert.svelte +39 -0
- package/src/lib/components/ui/alert/index.ts +14 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte +13 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte +17 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte +26 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte +16 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte +20 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte +20 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte +19 -0
- package/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte +18 -0
- package/src/lib/components/ui/alert-dialog/index.ts +40 -0
- package/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +23 -0
- package/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte +16 -0
- package/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte +31 -0
- package/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte +23 -0
- package/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte +23 -0
- package/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte +27 -0
- package/src/lib/components/ui/breadcrumb/breadcrumb.svelte +15 -0
- package/src/lib/components/ui/breadcrumb/index.ts +25 -0
- package/src/lib/components/ui/button/button.svelte +110 -0
- package/src/lib/components/ui/button/index.ts +17 -0
- package/src/lib/components/ui/checkbox/checkbox.svelte +35 -0
- package/src/lib/components/ui/checkbox/index.ts +6 -0
- package/src/lib/components/ui/command/command-dialog.svelte +35 -0
- package/src/lib/components/ui/command/command-empty.svelte +12 -0
- package/src/lib/components/ui/command/command-group.svelte +31 -0
- package/src/lib/components/ui/command/command-input.svelte +25 -0
- package/src/lib/components/ui/command/command-item.svelte +19 -0
- package/src/lib/components/ui/command/command-link-item.svelte +19 -0
- package/src/lib/components/ui/command/command-list.svelte +16 -0
- package/src/lib/components/ui/command/command-separator.svelte +12 -0
- package/src/lib/components/ui/command/command-shortcut.svelte +20 -0
- package/src/lib/components/ui/command/command.svelte +21 -0
- package/src/lib/components/ui/command/index.ts +40 -0
- package/src/lib/components/ui/dialog/dialog-content.svelte +38 -0
- package/src/lib/components/ui/dialog/dialog-description.svelte +16 -0
- package/src/lib/components/ui/dialog/dialog-footer.svelte +20 -0
- package/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
- package/src/lib/components/ui/dialog/dialog-overlay.svelte +19 -0
- package/src/lib/components/ui/dialog/dialog-title.svelte +16 -0
- package/src/lib/components/ui/dialog/index.ts +37 -0
- package/src/lib/components/ui/input/index.ts +7 -0
- package/src/lib/components/ui/input/input.svelte +46 -0
- package/src/lib/components/ui/label/index.ts +7 -0
- package/src/lib/components/ui/label/label.svelte +19 -0
- package/src/lib/components/ui/popover/index.ts +17 -0
- package/src/lib/components/ui/popover/popover-content.svelte +28 -0
- package/src/lib/components/ui/range-calendar/index.ts +30 -0
- package/src/lib/components/ui/range-calendar/range-calendar-cell.svelte +19 -0
- package/src/lib/components/ui/range-calendar/range-calendar-day.svelte +35 -0
- package/src/lib/components/ui/range-calendar/range-calendar-grid-body.svelte +12 -0
- package/src/lib/components/ui/range-calendar/range-calendar-grid-head.svelte +12 -0
- package/src/lib/components/ui/range-calendar/range-calendar-grid-row.svelte +12 -0
- package/src/lib/components/ui/range-calendar/range-calendar-grid.svelte +16 -0
- package/src/lib/components/ui/range-calendar/range-calendar-head-cell.svelte +16 -0
- package/src/lib/components/ui/range-calendar/range-calendar-header.svelte +16 -0
- package/src/lib/components/ui/range-calendar/range-calendar-heading.svelte +16 -0
- package/src/lib/components/ui/range-calendar/range-calendar-months.svelte +20 -0
- package/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte +27 -0
- package/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte +27 -0
- package/src/lib/components/ui/range-calendar/range-calendar.svelte +57 -0
- package/src/lib/components/ui/select/index.ts +34 -0
- package/src/lib/components/ui/select/select-content.svelte +38 -0
- package/src/lib/components/ui/select/select-group-heading.svelte +16 -0
- package/src/lib/components/ui/select/select-item.svelte +37 -0
- package/src/lib/components/ui/select/select-scroll-down-button.svelte +19 -0
- package/src/lib/components/ui/select/select-scroll-up-button.svelte +19 -0
- package/src/lib/components/ui/select/select-separator.svelte +13 -0
- package/src/lib/components/ui/select/select-trigger.svelte +24 -0
- package/src/lib/components/ui/separator/index.ts +7 -0
- package/src/lib/components/ui/separator/separator.svelte +22 -0
- package/src/lib/components/ui/skeleton/index.ts +7 -0
- package/src/lib/components/ui/skeleton/skeleton.svelte +22 -0
- package/src/lib/components/ui/sonner/index.ts +1 -0
- package/src/lib/components/ui/sonner/sonner.svelte +20 -0
- package/src/lib/components/ui/switch/index.ts +7 -0
- package/src/lib/components/ui/switch/switch.svelte +27 -0
- package/src/lib/components/ui/textarea/index.ts +7 -0
- package/src/lib/components/ui/textarea/textarea.svelte +22 -0
- package/src/lib/components/ui/tooltip/index.ts +18 -0
- package/src/lib/components/ui/tooltip/tooltip-content.svelte +21 -0
- package/src/lib/components/workflowEditor.svelte +188 -0
- package/src/lib/context.ts +22 -0
- package/src/lib/eventSystem.ts +40 -0
- package/src/lib/extensions/extension.types.ts +92 -0
- package/src/lib/extensions/extensionUtils.ts +156 -0
- package/src/lib/index.ts +24 -0
- package/src/lib/store.svelte.ts +13 -0
- package/src/lib/store.types.ts +28 -0
- package/src/lib/utils.ts +68 -0
- package/src/main.ts +18 -0
- package/src/stories/detailView/detailViewForm.stories.svelte +79 -0
- package/src/vite-env.d.ts +2 -0
- package/vite-plugins/index.js +2 -0
- package/vite-plugins/lobb-proxy.js +36 -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,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}
|