@lobb-js/studio 0.29.0 → 0.30.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/README.md +1 -0
- package/dist/actions.d.ts +11 -0
- package/dist/actions.js +16 -0
- package/dist/components/Studio.svelte +39 -43
- package/dist/components/StudioRoot.svelte +19 -0
- package/dist/components/StudioRoot.svelte.d.ts +6 -0
- package/dist/components/breadCrumbs.svelte +5 -4
- package/dist/components/codeEditor.svelte +1 -1
- package/dist/components/dataTable/dataTable.svelte +35 -20
- package/dist/components/dataTable/dataTable.svelte.d.ts +2 -1
- package/dist/components/dataTable/dataTableTabs.svelte +4 -2
- package/dist/components/dataTable/dataTableTabs.svelte.d.ts +2 -0
- package/dist/components/dataTable/header.svelte +15 -11
- package/dist/components/dataTable/header.svelte.d.ts +1 -0
- package/dist/components/dataTable/listViewChildren.svelte +4 -6
- package/dist/components/dataTable/listViewChildren.svelte.d.ts +0 -1
- package/dist/components/dataTable/table.svelte +8 -10
- package/dist/components/dataTable/table.svelte.d.ts +0 -1
- package/dist/components/dataTableDrawer/dataTableDrawer.svelte +4 -1
- package/dist/components/dataTableDrawer/dataTableDrawer.svelte.d.ts +2 -0
- package/dist/components/dataTablePopup/dataTablePopup.svelte +67 -0
- package/dist/components/dataTablePopup/dataTablePopup.svelte.d.ts +15 -0
- package/dist/components/detailView/create/children.svelte +1 -1
- package/dist/components/detailView/create/createDetailView.svelte +19 -61
- package/dist/components/detailView/create/createManyView.svelte +2 -4
- package/dist/components/detailView/detailView.svelte +81 -0
- package/dist/components/detailView/detailView.svelte.d.ts +8 -0
- package/dist/components/detailView/fieldInput.svelte +10 -10
- package/dist/components/detailView/fieldInputReplacement.svelte +7 -7
- package/dist/components/detailView/passwordInput.svelte +1 -1
- package/dist/components/detailView/update/updateDetailView.svelte +32 -69
- package/dist/components/diffViewer.svelte +1 -1
- package/dist/components/extensionsComponents.svelte +3 -1
- package/dist/components/foreingKeyInput.svelte +2 -2
- package/dist/components/importButton.svelte +12 -9
- package/dist/components/landing.svelte +7 -0
- package/dist/components/landing.svelte.d.ts +6 -14
- package/dist/components/miniSidebar.svelte +86 -15
- package/dist/components/miniSidebar.svelte.d.ts +2 -17
- package/dist/components/polymorphicInput.svelte +1 -1
- package/dist/components/rangeCalendarButton.svelte +10 -10
- package/dist/components/richTextEditor.svelte +1 -1
- package/dist/components/routes/collections/collections.svelte +32 -10
- package/dist/components/routes/data_model/dataModel.svelte +6 -28
- package/dist/components/routes/data_model/dataModel.svelte.d.ts +17 -2
- package/dist/components/routes/extensions/publicExtension.svelte +19 -0
- package/dist/components/routes/extensions/publicExtension.svelte.d.ts +13 -0
- package/dist/components/routes/home.svelte +2 -2
- package/dist/components/routes/workflows/workflows.svelte +4 -4
- package/dist/components/sidebar/sidebar.svelte +1 -1
- package/dist/components/sidebar/sidebarElements.svelte +4 -4
- package/dist/components/singletone.svelte +4 -6
- package/dist/components/ui/button/button.svelte +2 -3
- package/dist/components/workflowEditor.svelte +2 -2
- package/dist/eventSystem.d.ts +1 -1
- package/dist/eventSystem.js +7 -5
- package/dist/extensions/extension.types.d.ts +39 -14
- package/dist/extensions/extensionUtils.js +6 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/store.types.d.ts +1 -1
- package/dist/studioLifecycle.svelte.d.ts +2 -0
- package/dist/studioLifecycle.svelte.js +15 -0
- package/package.json +3 -4
- package/src/app.css +3 -0
- package/src/lib/actions.ts +28 -0
- package/src/lib/components/Studio.svelte +39 -43
- package/src/lib/components/StudioRoot.svelte +19 -0
- package/src/lib/components/breadCrumbs.svelte +5 -4
- package/src/lib/components/codeEditor.svelte +1 -1
- package/src/lib/components/dataTable/dataTable.svelte +35 -20
- package/src/lib/components/dataTable/dataTableTabs.svelte +4 -2
- package/src/lib/components/dataTable/header.svelte +15 -11
- package/src/lib/components/dataTable/listViewChildren.svelte +4 -6
- package/src/lib/components/dataTable/table.svelte +8 -10
- package/src/lib/components/dataTableDrawer/dataTableDrawer.svelte +4 -1
- package/src/lib/components/dataTablePopup/dataTablePopup.svelte +67 -0
- package/src/lib/components/detailView/create/children.svelte +1 -1
- package/src/lib/components/detailView/create/createDetailView.svelte +19 -61
- package/src/lib/components/detailView/create/createManyView.svelte +2 -4
- package/src/lib/components/detailView/detailView.svelte +81 -0
- package/src/lib/components/detailView/fieldInput.svelte +10 -10
- package/src/lib/components/detailView/fieldInputReplacement.svelte +7 -7
- package/src/lib/components/detailView/passwordInput.svelte +1 -1
- package/src/lib/components/detailView/update/updateDetailView.svelte +32 -69
- package/src/lib/components/diffViewer.svelte +1 -1
- package/src/lib/components/extensionsComponents.svelte +3 -1
- package/src/lib/components/foreingKeyInput.svelte +2 -2
- package/src/lib/components/importButton.svelte +12 -9
- package/src/lib/components/landing.svelte +7 -0
- package/src/lib/components/miniSidebar.svelte +86 -15
- package/src/lib/components/polymorphicInput.svelte +1 -1
- package/src/lib/components/rangeCalendarButton.svelte +10 -10
- package/src/lib/components/richTextEditor.svelte +1 -1
- package/src/lib/components/routes/collections/collections.svelte +32 -10
- package/src/lib/components/routes/data_model/dataModel.svelte +6 -28
- package/src/lib/components/routes/extensions/publicExtension.svelte +19 -0
- package/src/lib/components/routes/home.svelte +2 -2
- package/src/lib/components/routes/workflows/workflows.svelte +4 -4
- package/src/lib/components/sidebar/sidebar.svelte +1 -1
- package/src/lib/components/sidebar/sidebarElements.svelte +4 -4
- package/src/lib/components/singletone.svelte +4 -6
- package/src/lib/components/ui/button/button.svelte +2 -3
- package/src/lib/components/workflowEditor.svelte +2 -2
- package/src/lib/eventSystem.ts +8 -7
- package/src/lib/extensions/extension.types.ts +40 -6
- package/src/lib/extensions/extensionUtils.ts +6 -3
- package/src/lib/index.ts +3 -1
- package/src/lib/store.types.ts +1 -1
- package/src/lib/studioLifecycle.svelte.ts +17 -0
- package/dist/components/routes/data_model/syncManager.svelte +0 -94
- package/dist/components/routes/data_model/syncManager.svelte.d.ts +0 -3
- package/src/lib/components/routes/data_model/syncManager.svelte +0 -94
|
@@ -22,67 +22,70 @@
|
|
|
22
22
|
import Button from "../../ui/button/button.svelte";
|
|
23
23
|
import { getStudioContext } from "../../../context";
|
|
24
24
|
import { toast } from "svelte-sonner";
|
|
25
|
-
import ExtensionsComponents from "../../extensionsComponents.svelte";
|
|
26
|
-
import { getExtensionUtils } from "../../../extensions/extensionUtils";
|
|
27
25
|
import { untrack } from "svelte";
|
|
28
26
|
|
|
29
27
|
const { lobb, ctx } = getStudioContext();
|
|
30
28
|
import { getChangedProperties } from "../../../utils";
|
|
31
|
-
import { getField, getFieldIcon } from "../../dataTable/utils";
|
|
32
29
|
import DetailViewChildren from "./detailViewChildren.svelte";
|
|
33
30
|
import type { Snippet } from "svelte";
|
|
34
31
|
import { getDefaultEntry } from "../utils";
|
|
35
32
|
import type { Changes, ChildrenChanges } from "../utils";
|
|
36
|
-
import
|
|
33
|
+
import DetailView from "../detailView.svelte";
|
|
37
34
|
import Drawer from "../../drawer.svelte";
|
|
38
35
|
|
|
39
36
|
let {
|
|
40
37
|
collectionName,
|
|
41
|
-
values = {},
|
|
38
|
+
values: passedValues = {} as Record<string, any>,
|
|
42
39
|
showRelatedRecords = true,
|
|
43
40
|
onCancel,
|
|
44
41
|
onSuccessfullSave,
|
|
45
42
|
title,
|
|
46
43
|
submitButton,
|
|
47
44
|
recordId,
|
|
48
|
-
changes = $bindable<Changes | undefined>(undefined),
|
|
45
|
+
changes: passedChanges = $bindable<Changes | undefined>(undefined),
|
|
49
46
|
}: UpdateDetailViewProp = $props();
|
|
50
47
|
|
|
51
|
-
// Internal changes — used when not in recording mode, passed down to children
|
|
52
|
-
let _changes = $state<Changes>({ data: {}, children: {} });
|
|
53
|
-
|
|
54
48
|
// Recording mode = changes was passed from a parent component
|
|
55
|
-
const isRecordingMode =
|
|
49
|
+
const isRecordingMode = passedChanges !== undefined;
|
|
50
|
+
if (!isRecordingMode) passedChanges = { data: {}, children: {} };
|
|
51
|
+
const changes = passedChanges as Changes;
|
|
56
52
|
|
|
57
53
|
const fieldNames = Object.keys(ctx.meta.collections[collectionName].fields);
|
|
58
|
-
let
|
|
59
|
-
|
|
60
|
-
);
|
|
61
|
-
const initialEntry = $state.snapshot(entry);
|
|
54
|
+
let values = $state(getDefaultEntry(ctx, fieldNames, collectionName, passedValues));
|
|
55
|
+
const initialValues = $state.snapshot(values);
|
|
62
56
|
let fieldsErrors: Record<string, any> = $state({});
|
|
63
57
|
|
|
64
|
-
|
|
58
|
+
const hasChanges = $derived(
|
|
59
|
+
Object.keys(changes.data).length > 0 ||
|
|
60
|
+
Object.values(changes.children).some(
|
|
61
|
+
(ch: ChildrenChanges) => ch.created.length || ch.updated.length || ch.deleted.length || ch.linked.length || ch.unlinked.length,
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Tracks top-level field edits into changes.data.
|
|
65
66
|
// Child ops (create/link/unlink/delete) are written directly by DataTable into changes.children.
|
|
66
67
|
$effect(() => {
|
|
67
|
-
const currentEntrySnap = $state.snapshot(
|
|
68
|
+
const currentEntrySnap = $state.snapshot(values);
|
|
68
69
|
|
|
69
70
|
untrack(() => {
|
|
70
|
-
|
|
71
|
-
target.data = getChangedProperties(initialEntry, currentEntrySnap);
|
|
72
|
-
|
|
73
|
-
if (!isRecordingMode) {
|
|
74
|
-
console.log(`[${collectionName}] changes:`, $state.snapshot(target));
|
|
75
|
-
}
|
|
71
|
+
changes.data = getChangedProperties(initialValues, currentEntrySnap);
|
|
76
72
|
});
|
|
77
73
|
});
|
|
78
74
|
|
|
75
|
+
// Separate logging effect — needs its own $effect so it tracks mutations to changes.children.
|
|
76
|
+
$effect(() => {
|
|
77
|
+
if (!isRecordingMode) {
|
|
78
|
+
console.log(`[${collectionName}] changes:`, $state.snapshot(changes));
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
79
82
|
function buildApiChildren(children: Record<string, ChildrenChanges>): Record<string, any> | undefined {
|
|
80
83
|
const result: Record<string, any> = {};
|
|
81
84
|
for (const [collection, ops] of Object.entries(children)) {
|
|
82
85
|
const hasOps = ops.created.length || ops.deleted.length || ops.linked.length || ops.unlinked.length;
|
|
83
86
|
if (!hasOps) continue;
|
|
84
87
|
result[collection] = {
|
|
85
|
-
...(ops.created.length ? { create: ops.created.map((
|
|
88
|
+
...(ops.created.length ? { create: ops.created.map((op) => op.data) } : {}),
|
|
86
89
|
...(ops.deleted.length ? { delete: ops.deleted.map((r) => r.id) } : {}),
|
|
87
90
|
...(ops.linked.length ? { link: ops.linked.map((r) => r.id) } : {}),
|
|
88
91
|
...(ops.unlinked.length ? { unlink: ops.unlinked.map((r) => r.id) } : {}),
|
|
@@ -92,7 +95,7 @@
|
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
function handleCancel() {
|
|
95
|
-
if (
|
|
98
|
+
if (isRecordingMode) {
|
|
96
99
|
changes.data = {};
|
|
97
100
|
changes.children = {};
|
|
98
101
|
}
|
|
@@ -100,8 +103,7 @@
|
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
async function handleSave() {
|
|
103
|
-
const
|
|
104
|
-
const snap = $state.snapshot(target);
|
|
106
|
+
const snap = $state.snapshot(changes);
|
|
105
107
|
const { id: _id, ...data } = snap.data;
|
|
106
108
|
const children = buildApiChildren(snap.children);
|
|
107
109
|
|
|
@@ -121,6 +123,7 @@
|
|
|
121
123
|
fieldsErrors = result.details;
|
|
122
124
|
return;
|
|
123
125
|
} else if (result.message) {
|
|
126
|
+
toast.error(result.message);
|
|
124
127
|
return;
|
|
125
128
|
}
|
|
126
129
|
}
|
|
@@ -128,7 +131,7 @@
|
|
|
128
131
|
|
|
129
132
|
// Real mode: also fire separate update requests for edited children
|
|
130
133
|
if (!isRecordingMode) {
|
|
131
|
-
for (const [collection, ops] of Object.entries(snap.children)) {
|
|
134
|
+
for (const [collection, ops] of Object.entries(snap.children) as [string, ChildrenChanges][]) {
|
|
132
135
|
for (const updated of ops.updated) {
|
|
133
136
|
await lobb.updateOne(collection, String(updated.id), updated.data);
|
|
134
137
|
}
|
|
@@ -139,14 +142,6 @@
|
|
|
139
142
|
toast.success(`The record was successfully updated`);
|
|
140
143
|
onCancel?.();
|
|
141
144
|
}
|
|
142
|
-
|
|
143
|
-
const activeChanges = $derived(changes ?? _changes);
|
|
144
|
-
const hasChanges = $derived(
|
|
145
|
-
Object.keys(activeChanges.data).length > 0 ||
|
|
146
|
-
Object.values(activeChanges.children).some(
|
|
147
|
-
(c) => c.created.length || c.updated.length || c.deleted.length || c.linked.length || c.unlinked.length,
|
|
148
|
-
),
|
|
149
|
-
);
|
|
150
145
|
</script>
|
|
151
146
|
|
|
152
147
|
<Drawer onHide={handleCancel}>
|
|
@@ -169,41 +164,9 @@
|
|
|
169
164
|
</div>
|
|
170
165
|
</div>
|
|
171
166
|
<div class="flex-1 overflow-y-auto">
|
|
172
|
-
<
|
|
173
|
-
{#each fieldNames as fieldName}
|
|
174
|
-
{#if !ctx.meta.collections[collectionName].fields[fieldName]?.ui?.hidden}
|
|
175
|
-
{@const field = getField(ctx, fieldName, collectionName)}
|
|
176
|
-
{@const FieldIcon = getFieldIcon(ctx, fieldName, collectionName)}
|
|
177
|
-
<div class="flex flex-col gap-2">
|
|
178
|
-
<div class="flex flex-1 items-end justify-between gap-2 text-xs">
|
|
179
|
-
<div class="flex gap-2">
|
|
180
|
-
<div class="h-fit">{field.label}</div>
|
|
181
|
-
<div class="flex h-fit items-center gap-1 text-[0.7rem] text-muted-foreground">
|
|
182
|
-
<FieldIcon size="12" />
|
|
183
|
-
{field.type}
|
|
184
|
-
</div>
|
|
185
|
-
</div>
|
|
186
|
-
<div>
|
|
187
|
-
<ExtensionsComponents
|
|
188
|
-
name="dvFields.topRight.{collectionName}.{fieldName}"
|
|
189
|
-
utils={getExtensionUtils(lobb, ctx)}
|
|
190
|
-
bind:value={entry[fieldName]}
|
|
191
|
-
/>
|
|
192
|
-
</div>
|
|
193
|
-
</div>
|
|
194
|
-
<FieldInput
|
|
195
|
-
{collectionName}
|
|
196
|
-
{fieldName}
|
|
197
|
-
bind:value={entry[fieldName]}
|
|
198
|
-
bind:entry
|
|
199
|
-
errorMessages={fieldsErrors[fieldName]}
|
|
200
|
-
/>
|
|
201
|
-
</div>
|
|
202
|
-
{/if}
|
|
203
|
-
{/each}
|
|
204
|
-
</div>
|
|
167
|
+
<DetailView {collectionName} bind:entry={values} {fieldsErrors} />
|
|
205
168
|
{#if showRelatedRecords}
|
|
206
|
-
<DetailViewChildren {collectionName} {
|
|
169
|
+
<DetailViewChildren {collectionName} entry={values} activeChanges={changes} />
|
|
207
170
|
{/if}
|
|
208
171
|
</div>
|
|
209
172
|
<div class="flex h-12 items-center justify-end gap-2 border-t px-4">
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
});
|
|
97
97
|
</script>
|
|
98
98
|
|
|
99
|
-
<div class={cn("w-full resize-y rounded-md border bg-muted
|
|
99
|
+
<div class={cn("w-full resize-y rounded-md border bg-muted-soft shadow-sm", className)}>
|
|
100
100
|
<div
|
|
101
101
|
bind:this={editorContainer}
|
|
102
102
|
class="editor pl-2"
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
placeholder={"NULL"}
|
|
86
86
|
type="number"
|
|
87
87
|
class="
|
|
88
|
-
bg-muted
|
|
88
|
+
bg-muted-soft text-xs
|
|
89
89
|
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
90
90
|
"
|
|
91
91
|
bind:value={
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
<div class="relative z-10">
|
|
99
99
|
<Input
|
|
100
100
|
placeholder={"PARENT ID"}
|
|
101
|
-
class="bg-muted
|
|
101
|
+
class="bg-muted-soft text-xs"
|
|
102
102
|
disabled={true}
|
|
103
103
|
/>
|
|
104
104
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { AlertCircle, Check, FileText, LoaderCircle, Upload, X } from "lucide-svelte";
|
|
2
|
+
import { AlertCircle, Check, Download, FileText, LoaderCircle, Upload, X } from "lucide-svelte";
|
|
3
3
|
import Button, { type ButtonProps } from "./ui/button/button.svelte";
|
|
4
4
|
import { toast } from "svelte-sonner";
|
|
5
5
|
import { getStudioContext } from "../context";
|
|
@@ -170,10 +170,15 @@
|
|
|
170
170
|
}
|
|
171
171
|
</script>
|
|
172
172
|
|
|
173
|
-
<Button variant={rest.variant} class={rest.class}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
<Button variant={rest.variant} class={rest.class} onclick={showDrawer}>
|
|
174
|
+
<ExtensionsComponents
|
|
175
|
+
name="collections.import.button.content"
|
|
176
|
+
utils={getExtensionUtils(lobb, ctx)}
|
|
177
|
+
{collectionName}
|
|
178
|
+
>
|
|
179
|
+
<rest.Icon />
|
|
180
|
+
Import
|
|
181
|
+
</ExtensionsComponents>
|
|
177
182
|
</Button>
|
|
178
183
|
|
|
179
184
|
<Dialog.Root
|
|
@@ -231,7 +236,7 @@
|
|
|
231
236
|
tabindex="0"
|
|
232
237
|
onkeydown={(e) => e.key === "Enter" && fileInput.click()}
|
|
233
238
|
>
|
|
234
|
-
<
|
|
239
|
+
<Download class="mb-3 h-8 w-8 text-muted-foreground" />
|
|
235
240
|
<p class="text-sm font-medium">Drop a file here or click to browse</p>
|
|
236
241
|
<p class="mt-1 text-xs text-muted-foreground">Supports .csv and .json</p>
|
|
237
242
|
</div>
|
|
@@ -244,7 +249,7 @@
|
|
|
244
249
|
/>
|
|
245
250
|
{:else}
|
|
246
251
|
<textarea
|
|
247
|
-
class="block h-56 w-full resize-none rounded-md border bg-muted
|
|
252
|
+
class="block h-56 w-full resize-none rounded-md border bg-muted-soft p-3 font-mono text-sm focus:outline-none focus:ring-1 focus:ring-ring"
|
|
248
253
|
placeholder="Paste CSV or JSON here..."
|
|
249
254
|
bind:value={pasteContent}
|
|
250
255
|
></textarea>
|
|
@@ -279,7 +284,6 @@
|
|
|
279
284
|
data={transformedRows}
|
|
280
285
|
columns={collectionColumns.filter((c) => c.id !== "id")}
|
|
281
286
|
showCheckboxes={false}
|
|
282
|
-
unifiedBgColor="bg-background"
|
|
283
287
|
headerBorderTop={true}
|
|
284
288
|
/>
|
|
285
289
|
</div>
|
|
@@ -328,7 +332,6 @@
|
|
|
328
332
|
data={failedData}
|
|
329
333
|
columns={[{ id: "__error", icon: AlertCircle }, ...collectionColumns.filter((c) => c.id !== "id")]}
|
|
330
334
|
showCheckboxes={false}
|
|
331
|
-
unifiedBgColor="bg-background"
|
|
332
335
|
headerBorderTop={true}
|
|
333
336
|
>
|
|
334
337
|
{#snippet overrideCell(value, column)}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
onMount(() => {
|
|
4
|
+
document.getElementById("app-loading")?.remove();
|
|
5
|
+
});
|
|
6
|
+
</script>
|
|
7
|
+
|
|
1
8
|
<div class="min-h-screen flex items-center justify-center bg-background text-foreground p-6">
|
|
2
9
|
<div class="max-w-md text-center space-y-4">
|
|
3
10
|
<h1 class="text-2xl font-semibold">Lobb Studio</h1>
|
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
export default Landing;
|
|
2
|
-
type Landing = SvelteComponent<{
|
|
3
|
-
[x: string]: never;
|
|
4
|
-
}, {
|
|
5
|
-
[evt: string]: CustomEvent<any>;
|
|
6
|
-
}, {}> & {
|
|
7
|
-
$$bindings?: string | undefined;
|
|
8
|
-
};
|
|
9
|
-
declare const Landing: $$__sveltets_2_IsomorphicComponent<{
|
|
10
|
-
[x: string]: never;
|
|
11
|
-
}, {
|
|
12
|
-
[evt: string]: CustomEvent<any>;
|
|
13
|
-
}, {}, {}, string>;
|
|
14
1
|
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
15
|
-
new (options: import(
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
16
3
|
$$bindings?: Bindings;
|
|
17
4
|
} & Exports;
|
|
18
5
|
(internal: unknown, props: {
|
|
@@ -24,3 +11,8 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
|
|
|
24
11
|
};
|
|
25
12
|
z_$$bindings?: Bindings;
|
|
26
13
|
}
|
|
14
|
+
declare const Landing: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type Landing = InstanceType<typeof Landing>;
|
|
18
|
+
export default Landing;
|
|
@@ -16,16 +16,19 @@
|
|
|
16
16
|
import Separator from "./ui/separator/separator.svelte";
|
|
17
17
|
import * as Tooltip from "./ui/tooltip";
|
|
18
18
|
import * as Accordion from "./ui/accordion/index.js";
|
|
19
|
+
import { onMount } from "svelte";
|
|
19
20
|
|
|
20
21
|
import { getStudioContext } from "../context";
|
|
21
22
|
import { getDashboardNavs } from "../extensions/extensionUtils";
|
|
23
|
+
import { emitEvent } from "../eventSystem";
|
|
22
24
|
|
|
23
|
-
const { ctx } = getStudioContext();
|
|
25
|
+
const { lobb, ctx } = getStudioContext();
|
|
24
26
|
import { mediaQueries } from "../utils";
|
|
25
27
|
import * as Popover from "./ui/popover";
|
|
26
|
-
import {
|
|
28
|
+
import { page } from "$app/state";
|
|
29
|
+
import { goto } from "$app/navigation";
|
|
27
30
|
|
|
28
|
-
const
|
|
31
|
+
const rawSections: any[][] = [
|
|
29
32
|
[
|
|
30
33
|
{
|
|
31
34
|
label: "Home",
|
|
@@ -39,13 +42,15 @@
|
|
|
39
42
|
},
|
|
40
43
|
{
|
|
41
44
|
label: "Data Model",
|
|
42
|
-
href: "/studio/datamodel
|
|
45
|
+
href: "/studio/datamodel",
|
|
43
46
|
icon: Layers,
|
|
47
|
+
represents: "core_data_model",
|
|
44
48
|
},
|
|
45
49
|
{
|
|
46
50
|
label: "Workflows",
|
|
47
51
|
href: "/studio/workflows",
|
|
48
52
|
icon: Workflow,
|
|
53
|
+
represents: "core_workflows",
|
|
49
54
|
},
|
|
50
55
|
],
|
|
51
56
|
[],
|
|
@@ -55,20 +60,72 @@
|
|
|
55
60
|
const navs = getDashboardNavs(ctx);
|
|
56
61
|
|
|
57
62
|
if (navs.top) {
|
|
58
|
-
|
|
63
|
+
rawSections[0] = [...rawSections[0], ...navs.top];
|
|
59
64
|
}
|
|
60
65
|
if (navs.middle) {
|
|
61
|
-
|
|
66
|
+
rawSections[1] = [...rawSections[1], ...navs.middle];
|
|
62
67
|
}
|
|
63
68
|
if (navs.bottom) {
|
|
64
|
-
|
|
69
|
+
rawSections[2] = [...rawSections[2], ...navs.bottom];
|
|
65
70
|
}
|
|
66
71
|
|
|
72
|
+
// Items without a `represents` are always visible. Items with one are
|
|
73
|
+
// gated by emitting auth.canAccess — the auth extension (or any
|
|
74
|
+
// drop-in replacement) decides based on the current user's session.
|
|
75
|
+
// Start empty so nothing flashes before the answers come back.
|
|
76
|
+
let sections: any[][] = $state([[], [], []]);
|
|
77
|
+
|
|
78
|
+
async function isItemVisible(item: any): Promise<boolean> {
|
|
79
|
+
if (!item.represents) return true;
|
|
80
|
+
const res = await emitEvent(
|
|
81
|
+
{ lobb, ctx },
|
|
82
|
+
"auth.canAccess",
|
|
83
|
+
{ collection: item.represents, action: "read" },
|
|
84
|
+
);
|
|
85
|
+
return res === true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Highlight the nav item matching the current URL. "/studio" requires an
|
|
89
|
+
// exact match (otherwise it would light up on every sub-route since it's a
|
|
90
|
+
// prefix of everything); other items use startsWith so sub-paths
|
|
91
|
+
// (e.g. /studio/collections/risks) still highlight their parent.
|
|
92
|
+
// Popover items with children are active when any of their children match.
|
|
93
|
+
const currentPath = $derived(page.url.pathname);
|
|
94
|
+
function isItemActive(item: any): boolean {
|
|
95
|
+
if (item.navs) return item.navs.some((c: any) => isItemActive(c));
|
|
96
|
+
if (!item.href) return false;
|
|
97
|
+
if (item.href === "/studio") return currentPath === "/studio";
|
|
98
|
+
return currentPath === item.href || currentPath.startsWith(item.href + "/");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// onMount is enough — Studio gets remounted on login/logout (see
|
|
102
|
+
// remountStudio in @lobb-js/studio), so by the time this component
|
|
103
|
+
// mounts, ctx.extensions.auth.user / permissions are already populated.
|
|
104
|
+
onMount(async () => {
|
|
105
|
+
const result: any[][] = [[], [], []];
|
|
106
|
+
for (let i = 0; i < rawSections.length; i++) {
|
|
107
|
+
for (const item of rawSections[i]) {
|
|
108
|
+
if (item.navs) {
|
|
109
|
+
const visibleChildren: any[] = [];
|
|
110
|
+
for (const child of item.navs) {
|
|
111
|
+
if (await isItemVisible(child)) visibleChildren.push(child);
|
|
112
|
+
}
|
|
113
|
+
if (visibleChildren.length && (await isItemVisible(item))) {
|
|
114
|
+
result[i].push({ ...item, navs: visibleChildren });
|
|
115
|
+
}
|
|
116
|
+
} else if (await isItemVisible(item)) {
|
|
117
|
+
result[i].push(item);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
sections = result;
|
|
122
|
+
});
|
|
67
123
|
</script>
|
|
68
124
|
|
|
69
125
|
{#snippet section(section: any)}
|
|
70
126
|
<div class="flex flex-col {isSmallScreen ? 'gap-0' : 'gap-2'}">
|
|
71
127
|
{#each section as item}
|
|
128
|
+
{@const active = isItemActive(item)}
|
|
72
129
|
{#if isSmallScreen}
|
|
73
130
|
{#if !item.navs}
|
|
74
131
|
<Button
|
|
@@ -76,11 +133,13 @@
|
|
|
76
133
|
if (item.onclick) {
|
|
77
134
|
item.onclick();
|
|
78
135
|
} else {
|
|
79
|
-
|
|
136
|
+
goto(item.href);
|
|
80
137
|
}
|
|
81
138
|
isCollapsed = true;
|
|
82
139
|
}}
|
|
83
|
-
class="flex items-center justify-start flex-nowrap text-
|
|
140
|
+
class="flex items-center justify-start flex-nowrap text-nowrap h-10 w-full {active
|
|
141
|
+
? 'bg-accent text-accent-foreground'
|
|
142
|
+
: 'text-muted-foreground'}"
|
|
84
143
|
variant="ghost"
|
|
85
144
|
size="icon"
|
|
86
145
|
Icon={item.icon}
|
|
@@ -92,7 +151,9 @@
|
|
|
92
151
|
<Accordion.Item class="border-b-0">
|
|
93
152
|
<Accordion.Trigger class="justify-between p-0 h-10">
|
|
94
153
|
<div
|
|
95
|
-
class="flex items-center gap-2
|
|
154
|
+
class="flex items-center gap-2 {active
|
|
155
|
+
? 'text-accent-foreground'
|
|
156
|
+
: 'text-muted-foreground'}"
|
|
96
157
|
>
|
|
97
158
|
<item.icon size="18" />
|
|
98
159
|
<div class="text-nowrap">{item.label}</div>
|
|
@@ -100,16 +161,19 @@
|
|
|
100
161
|
</Accordion.Trigger>
|
|
101
162
|
<Accordion.Content class="pl-2 border-l">
|
|
102
163
|
{#each item.navs as childItem}
|
|
164
|
+
{@const childActive = isItemActive(childItem)}
|
|
103
165
|
<Button
|
|
104
166
|
onclick={() => {
|
|
105
167
|
if (childItem.onclick) {
|
|
106
168
|
childItem.onclick();
|
|
107
169
|
} else {
|
|
108
|
-
|
|
170
|
+
goto(item.href);
|
|
109
171
|
}
|
|
110
172
|
isCollapsed = true;
|
|
111
173
|
}}
|
|
112
|
-
class="flex items-center justify-start flex-nowrap text-
|
|
174
|
+
class="flex items-center justify-start flex-nowrap text-nowrap h-8 w-full {childActive
|
|
175
|
+
? 'bg-accent text-accent-foreground'
|
|
176
|
+
: 'text-muted-foreground'}"
|
|
113
177
|
variant="ghost"
|
|
114
178
|
size="icon"
|
|
115
179
|
Icon={childItem.icon}
|
|
@@ -133,7 +197,9 @@
|
|
|
133
197
|
isCollapsed = true;
|
|
134
198
|
}}
|
|
135
199
|
href={item.href}
|
|
136
|
-
class=
|
|
200
|
+
class={active
|
|
201
|
+
? 'bg-accent text-accent-foreground'
|
|
202
|
+
: 'text-muted-foreground'}
|
|
137
203
|
variant="ghost"
|
|
138
204
|
size="icon"
|
|
139
205
|
Icon={item.icon}
|
|
@@ -142,7 +208,9 @@
|
|
|
142
208
|
<Popover.Root>
|
|
143
209
|
<Popover.Trigger>
|
|
144
210
|
<Button
|
|
145
|
-
class=
|
|
211
|
+
class={active
|
|
212
|
+
? 'bg-accent text-accent-foreground'
|
|
213
|
+
: 'text-muted-foreground'}
|
|
146
214
|
variant="ghost"
|
|
147
215
|
size="icon"
|
|
148
216
|
Icon={item.icon}
|
|
@@ -155,12 +223,15 @@
|
|
|
155
223
|
>
|
|
156
224
|
<div class="py-1">
|
|
157
225
|
{#each item.navs as childItem}
|
|
226
|
+
{@const childActive = isItemActive(childItem)}
|
|
158
227
|
<div
|
|
159
228
|
class="px-1 text-xs text-muted-foreground"
|
|
160
229
|
>
|
|
161
230
|
<Button
|
|
162
231
|
variant="ghost"
|
|
163
|
-
class="flex h-7 w-full justify-start p-2 text-xs font-normal
|
|
232
|
+
class="flex h-7 w-full justify-start p-2 text-xs font-normal {childActive
|
|
233
|
+
? 'bg-accent text-accent-foreground'
|
|
234
|
+
: 'text-muted-foreground'}"
|
|
164
235
|
Icon={childItem.icon}
|
|
165
236
|
onclick={() => {
|
|
166
237
|
if (childItem.onclick) {
|
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
export declare let collapseMiniSideBar: () => void;
|
|
2
2
|
export declare let expandMiniSideBar: () => void;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
$$bindings?: Bindings;
|
|
6
|
-
} & Exports;
|
|
7
|
-
(internal: unknown, props: {
|
|
8
|
-
$$events?: Events;
|
|
9
|
-
$$slots?: Slots;
|
|
10
|
-
}): Exports & {
|
|
11
|
-
$set?: any;
|
|
12
|
-
$on?: any;
|
|
13
|
-
};
|
|
14
|
-
z_$$bindings?: Bindings;
|
|
15
|
-
}
|
|
16
|
-
declare const MiniSidebar: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
17
|
-
[evt: string]: CustomEvent<any>;
|
|
18
|
-
}, {}, {}, string>;
|
|
19
|
-
type MiniSidebar = InstanceType<typeof MiniSidebar>;
|
|
3
|
+
declare const MiniSidebar: import("svelte").Component<Record<string, never>, {}, "">;
|
|
4
|
+
type MiniSidebar = ReturnType<typeof MiniSidebar>;
|
|
20
5
|
export default MiniSidebar;
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
}
|
|
71
71
|
</script>
|
|
72
72
|
|
|
73
|
-
<div class="flex h-9 w-full items-center gap-1.5 rounded-md border pl-1.5 pr-9 text-xs bg-muted
|
|
73
|
+
<div class="flex h-9 w-full items-center gap-1.5 rounded-md border pl-1.5 pr-9 text-xs bg-muted-soft {destructive ? 'border-destructive bg-destructive/10' : ''}">
|
|
74
74
|
<!-- Collection picker -->
|
|
75
75
|
<Popover.Root bind:open={collectionPopoverOpen}>
|
|
76
76
|
<Popover.Trigger>
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
class="flex flex-col overflow-hidden text-muted-foreground"
|
|
60
60
|
>
|
|
61
61
|
<button
|
|
62
|
-
class="text-start text-sm py-2 px-2 hover:bg-muted
|
|
62
|
+
class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
|
|
63
63
|
onclick={() => {
|
|
64
64
|
const currentDate = today(getLocalTimeZone());
|
|
65
65
|
value = {
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
Today
|
|
72
72
|
</button>
|
|
73
73
|
<button
|
|
74
|
-
class="text-start text-sm py-2 px-2 hover:bg-muted
|
|
74
|
+
class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
|
|
75
75
|
onclick={() => {
|
|
76
76
|
const currentDate = today(getLocalTimeZone());
|
|
77
77
|
value = {
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
Yesterday
|
|
84
84
|
</button>
|
|
85
85
|
<button
|
|
86
|
-
class="text-start text-sm py-2 px-2 hover:bg-muted
|
|
86
|
+
class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
|
|
87
87
|
onclick={() => {
|
|
88
88
|
const currentDate = today(getLocalTimeZone());
|
|
89
89
|
const weekStart = startOfWeek(currentDate, "en-US");
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
This week (Sun - Today)
|
|
97
97
|
</button>
|
|
98
98
|
<button
|
|
99
|
-
class="text-start text-sm py-2 px-2 hover:bg-muted
|
|
99
|
+
class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
|
|
100
100
|
onclick={() => {
|
|
101
101
|
const currentDate = today(getLocalTimeZone());
|
|
102
102
|
const thisWeekStart = startOfWeek(
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
Last week (Sun - Sat)
|
|
120
120
|
</button>
|
|
121
121
|
<button
|
|
122
|
-
class="text-start text-sm py-2 px-2 hover:bg-muted
|
|
122
|
+
class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
|
|
123
123
|
onclick={() => {
|
|
124
124
|
const currentDate = today(getLocalTimeZone());
|
|
125
125
|
value = {
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
Last 7 days
|
|
132
132
|
</button>
|
|
133
133
|
<button
|
|
134
|
-
class="text-start text-sm py-2 px-2 hover:bg-muted
|
|
134
|
+
class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
|
|
135
135
|
onclick={() => {
|
|
136
136
|
const currentDate = today(getLocalTimeZone());
|
|
137
137
|
value = {
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
Last 30 days
|
|
144
144
|
</button>
|
|
145
145
|
<button
|
|
146
|
-
class="text-start text-sm py-2 px-2 hover:bg-muted
|
|
146
|
+
class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
|
|
147
147
|
onclick={() => {
|
|
148
148
|
const currentDate = today(getLocalTimeZone());
|
|
149
149
|
value = {
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
Last 90 days
|
|
156
156
|
</button>
|
|
157
157
|
<button
|
|
158
|
-
class="text-start text-sm py-2 px-2 hover:bg-muted
|
|
158
|
+
class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
|
|
159
159
|
onclick={() => {
|
|
160
160
|
const currentDate = today(getLocalTimeZone());
|
|
161
161
|
value = {
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
Last 12 months
|
|
168
168
|
</button>
|
|
169
169
|
<button
|
|
170
|
-
class="text-start text-sm py-2 px-2 hover:bg-muted
|
|
170
|
+
class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
|
|
171
171
|
onclick={() => {
|
|
172
172
|
const currentDate = today(getLocalTimeZone());
|
|
173
173
|
const lastYearStart = currentDate
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
Last Calendar year
|
|
186
186
|
</button>
|
|
187
187
|
<button
|
|
188
|
-
class="text-start text-sm py-2 px-2 hover:bg-muted
|
|
188
|
+
class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
|
|
189
189
|
onclick={() => {
|
|
190
190
|
const currentDate = today(getLocalTimeZone());
|
|
191
191
|
const yearStart = currentDate.set({
|