@lobb-js/studio 0.33.0 → 0.35.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/dist/applyUITheme.d.ts +2 -0
- package/dist/{applyStudioTheme.js → applyUITheme.js} +4 -4
- package/dist/components/LlmButton.svelte +4 -2
- package/dist/components/LlmButton.svelte.d.ts +1 -0
- package/dist/components/Studio.svelte +15 -7
- package/dist/components/codeEditor.svelte +1 -1
- package/dist/components/createManyButton.svelte +2 -2
- package/dist/components/dataTable/dataTable.svelte +1 -1
- package/dist/components/dataTable/dataTableTabs.svelte +1 -1
- package/dist/components/dataTable/filter.svelte +3 -2
- package/dist/components/dataTable/filterButton.svelte +1 -1
- package/dist/components/dataTable/footer.svelte +1 -1
- package/dist/components/dataTable/header.svelte +14 -21
- package/dist/components/dataTable/listViewChildren.svelte +1 -1
- package/dist/components/dataTable/sort.svelte +1 -1
- package/dist/components/dataTable/sortButton.svelte +1 -1
- package/dist/components/dataTable/table.svelte +8 -8
- package/dist/components/dataTable/utils.js +2 -1
- package/dist/components/dataTablePopup/dataTablePopup.svelte +1 -1
- package/dist/components/detailView/create/children.svelte +1 -1
- package/dist/components/detailView/create/createDetailView.svelte +2 -2
- package/dist/components/detailView/create/createDetailViewButton.svelte +2 -0
- package/dist/components/detailView/create/createDetailViewButton.svelte.d.ts +1 -0
- package/dist/components/detailView/create/createDetailViewChildren.svelte +4 -4
- package/dist/components/detailView/create/createManyView.svelte +4 -4
- package/dist/components/detailView/detailView.svelte +2 -1
- 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 +2 -2
- package/dist/components/detailView/update/updateDetailViewButton.svelte +2 -0
- package/dist/components/detailView/update/updateDetailViewButton.svelte.d.ts +1 -0
- package/dist/components/detailView/update/updateDetailViewChildren.svelte +4 -4
- package/dist/components/diffViewer.svelte +1 -1
- package/dist/components/drawer.svelte +2 -2
- package/dist/components/foreingKeyInput.svelte +2 -2
- package/dist/components/horizontalNav.svelte +85 -0
- package/dist/components/horizontalNav.svelte.d.ts +3 -0
- package/dist/components/importButton.svelte +7 -7
- package/dist/components/mainNav.svelte +15 -0
- package/dist/components/mainNav.svelte.d.ts +6 -0
- package/dist/components/mainNavShared.d.ts +10 -0
- package/dist/components/mainNavShared.js +62 -0
- package/dist/components/polymorphicInput.svelte +1 -1
- package/dist/components/rangeCalendarButton.svelte +11 -12
- package/dist/components/richTextEditor.svelte +1 -1
- package/dist/components/routes/extensions/publicExtension.svelte +1 -1
- package/dist/components/routes/home.svelte +1 -1
- package/dist/components/routes/workflows/workflows.svelte +1 -1
- package/dist/components/setServerPage.svelte +1 -1
- package/dist/components/sidebar/sidebar.svelte +3 -3
- package/dist/components/sidebar/sidebarElements.svelte +4 -4
- package/dist/components/singletone.svelte +2 -2
- package/dist/components/ui/skeleton/skeleton.svelte +1 -1
- package/dist/components/ui/tooltip/tooltip-content.svelte +1 -1
- package/dist/components/verticalNav.svelte +174 -0
- package/dist/components/verticalNav.svelte.d.ts +3 -0
- package/dist/components/workflowEditor.svelte +2 -2
- package/dist/store.types.d.ts +4 -3
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +12 -0
- package/package.json +2 -2
- package/src/app.css +50 -76
- package/src/lib/{applyStudioTheme.ts → applyUITheme.ts} +5 -5
- package/src/lib/components/LlmButton.svelte +4 -2
- package/src/lib/components/Studio.svelte +15 -7
- package/src/lib/components/codeEditor.svelte +1 -1
- package/src/lib/components/createManyButton.svelte +2 -2
- package/src/lib/components/dataTable/dataTable.svelte +1 -1
- package/src/lib/components/dataTable/dataTableTabs.svelte +1 -1
- package/src/lib/components/dataTable/filter.svelte +3 -2
- package/src/lib/components/dataTable/filterButton.svelte +1 -1
- package/src/lib/components/dataTable/footer.svelte +1 -1
- package/src/lib/components/dataTable/header.svelte +14 -21
- package/src/lib/components/dataTable/listViewChildren.svelte +1 -1
- package/src/lib/components/dataTable/sort.svelte +1 -1
- package/src/lib/components/dataTable/sortButton.svelte +1 -1
- package/src/lib/components/dataTable/table.svelte +8 -8
- package/src/lib/components/dataTable/utils.ts +2 -1
- package/src/lib/components/dataTablePopup/dataTablePopup.svelte +1 -1
- package/src/lib/components/detailView/create/children.svelte +1 -1
- package/src/lib/components/detailView/create/createDetailView.svelte +2 -2
- package/src/lib/components/detailView/create/createDetailViewButton.svelte +2 -0
- package/src/lib/components/detailView/create/createDetailViewChildren.svelte +4 -4
- package/src/lib/components/detailView/create/createManyView.svelte +4 -4
- package/src/lib/components/detailView/detailView.svelte +2 -1
- 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 +2 -2
- package/src/lib/components/detailView/update/updateDetailViewButton.svelte +2 -0
- package/src/lib/components/detailView/update/updateDetailViewChildren.svelte +4 -4
- package/src/lib/components/diffViewer.svelte +1 -1
- package/src/lib/components/drawer.svelte +2 -2
- package/src/lib/components/foreingKeyInput.svelte +2 -2
- package/src/lib/components/horizontalNav.svelte +85 -0
- package/src/lib/components/importButton.svelte +7 -7
- package/src/lib/components/mainNav.svelte +15 -0
- package/src/lib/components/mainNavShared.ts +67 -0
- package/src/lib/components/polymorphicInput.svelte +1 -1
- package/src/lib/components/rangeCalendarButton.svelte +11 -12
- package/src/lib/components/richTextEditor.svelte +1 -1
- package/src/lib/components/routes/extensions/publicExtension.svelte +1 -1
- package/src/lib/components/routes/home.svelte +1 -1
- package/src/lib/components/routes/workflows/workflows.svelte +1 -1
- package/src/lib/components/setServerPage.svelte +1 -1
- package/src/lib/components/sidebar/sidebar.svelte +3 -3
- package/src/lib/components/sidebar/sidebarElements.svelte +4 -4
- package/src/lib/components/singletone.svelte +2 -2
- package/src/lib/components/ui/skeleton/skeleton.svelte +1 -1
- package/src/lib/components/ui/tooltip/tooltip-content.svelte +1 -1
- package/src/lib/components/verticalNav.svelte +174 -0
- package/src/lib/components/workflowEditor.svelte +2 -2
- package/src/lib/store.types.ts +2 -2
- package/src/lib/utils.ts +12 -0
- package/dist/applyStudioTheme.d.ts +0 -2
- package/dist/components/miniSidebar.svelte +0 -300
- package/dist/components/miniSidebar.svelte.d.ts +0 -5
- package/src/lib/components/miniSidebar.svelte +0 -300
package/src/lib/store.types.ts
CHANGED
|
@@ -27,14 +27,14 @@ interface Collection {
|
|
|
27
27
|
}
|
|
28
28
|
type Collections = Record<string, Collection>;
|
|
29
29
|
|
|
30
|
-
export interface
|
|
30
|
+
export interface UITheme {
|
|
31
31
|
light?: Record<string, string>;
|
|
32
32
|
dark?: Record<string, string>;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
interface Meta {
|
|
36
36
|
version: string;
|
|
37
|
-
|
|
37
|
+
ui?: { theme?: UITheme; horizontalNav?: boolean };
|
|
38
38
|
relations: Array<any>;
|
|
39
39
|
collections: Collections;
|
|
40
40
|
extensions: Record<string, any>;
|
package/src/lib/utils.ts
CHANGED
|
@@ -24,6 +24,18 @@ export const mediaQueries = {
|
|
|
24
24
|
'2xl': new MediaQuery('min-width: 1536px'),
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// User-facing label for a field type. "string"/"text" are jargon to
|
|
28
|
+
// non-technical users, so we surface "Text"/"Long text" instead. Types
|
|
29
|
+
// not in the map fall through to the raw identifier — extend as needed.
|
|
30
|
+
const FIELD_TYPE_LABELS: Record<string, string> = {
|
|
31
|
+
string: "Text",
|
|
32
|
+
text: "Long text",
|
|
33
|
+
};
|
|
34
|
+
export function getFieldTypeLabel(type: string | undefined | null): string {
|
|
35
|
+
if (!type) return "";
|
|
36
|
+
return FIELD_TYPE_LABELS[type] ?? type;
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
|
|
28
40
|
export function calculateDrawerWidth() {
|
|
29
41
|
const backgroundDrawerButtons = document.querySelectorAll(".backgroundDrawerButton");
|
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
let isSmallScreen = $derived(!mediaQueries.sm.current);
|
|
3
|
-
let isCollapsed = $derived(isSmallScreen);
|
|
4
|
-
|
|
5
|
-
export let collapseMiniSideBar = () => {
|
|
6
|
-
isCollapsed = true;
|
|
7
|
-
};
|
|
8
|
-
export let expandMiniSideBar = () => {
|
|
9
|
-
isCollapsed = false;
|
|
10
|
-
};
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
|
-
<script lang="ts">
|
|
14
|
-
import { House, Layers, Library, Workflow, X } from "lucide-svelte";
|
|
15
|
-
import Button from "./ui/button/button.svelte";
|
|
16
|
-
import Separator from "./ui/separator/separator.svelte";
|
|
17
|
-
import * as Tooltip from "./ui/tooltip";
|
|
18
|
-
import * as Accordion from "./ui/accordion/index.js";
|
|
19
|
-
import { onMount } from "svelte";
|
|
20
|
-
|
|
21
|
-
import { getStudioContext } from "../context";
|
|
22
|
-
import { getDashboardNavs } from "../extensions/extensionUtils";
|
|
23
|
-
import { emitEvent } from "../eventSystem";
|
|
24
|
-
|
|
25
|
-
const { lobb, ctx } = getStudioContext();
|
|
26
|
-
import { mediaQueries } from "../utils";
|
|
27
|
-
import * as Popover from "./ui/popover";
|
|
28
|
-
import { page } from "$app/state";
|
|
29
|
-
import { goto } from "$app/navigation";
|
|
30
|
-
|
|
31
|
-
const rawSections: any[][] = [
|
|
32
|
-
[
|
|
33
|
-
{
|
|
34
|
-
label: "Home",
|
|
35
|
-
href: "/studio",
|
|
36
|
-
icon: House,
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
label: "Collections",
|
|
40
|
-
href: "/studio/collections",
|
|
41
|
-
icon: Library,
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
label: "Data Model",
|
|
45
|
-
href: "/studio/datamodel",
|
|
46
|
-
icon: Layers,
|
|
47
|
-
represents: "core_data_model",
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
label: "Workflows",
|
|
51
|
-
href: "/studio/workflows",
|
|
52
|
-
icon: Workflow,
|
|
53
|
-
represents: "core_workflows",
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
[],
|
|
57
|
-
[],
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
const navs = getDashboardNavs(ctx);
|
|
61
|
-
|
|
62
|
-
if (navs.top) {
|
|
63
|
-
rawSections[0] = [...rawSections[0], ...navs.top];
|
|
64
|
-
}
|
|
65
|
-
if (navs.middle) {
|
|
66
|
-
rawSections[1] = [...rawSections[1], ...navs.middle];
|
|
67
|
-
}
|
|
68
|
-
if (navs.bottom) {
|
|
69
|
-
rawSections[2] = [...rawSections[2], ...navs.bottom];
|
|
70
|
-
}
|
|
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
|
-
// SvelteKit's pathname can come back with a trailing slash (`/studio/`)
|
|
94
|
-
// depending on config, so we normalize it before comparing.
|
|
95
|
-
const currentPath = $derived(page.url.pathname.replace(/\/$/, "") || "/");
|
|
96
|
-
function isItemActive(item: any): boolean {
|
|
97
|
-
if (item.navs) return item.navs.some((c: any) => isItemActive(c));
|
|
98
|
-
if (!item.href) return false;
|
|
99
|
-
const itemHref = item.href.replace(/\/$/, "") || "/";
|
|
100
|
-
if (itemHref === "/studio") return currentPath === "/studio";
|
|
101
|
-
return currentPath === itemHref || currentPath.startsWith(itemHref + "/");
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// onMount is enough — Studio gets remounted on login/logout (see
|
|
105
|
-
// remountStudio in @lobb-js/studio), so by the time this component
|
|
106
|
-
// mounts, ctx.extensions.auth.user / permissions are already populated.
|
|
107
|
-
onMount(async () => {
|
|
108
|
-
const result: any[][] = [[], [], []];
|
|
109
|
-
for (let i = 0; i < rawSections.length; i++) {
|
|
110
|
-
for (const item of rawSections[i]) {
|
|
111
|
-
if (item.navs) {
|
|
112
|
-
const visibleChildren: any[] = [];
|
|
113
|
-
for (const child of item.navs) {
|
|
114
|
-
if (await isItemVisible(child)) visibleChildren.push(child);
|
|
115
|
-
}
|
|
116
|
-
if (visibleChildren.length && (await isItemVisible(item))) {
|
|
117
|
-
result[i].push({ ...item, navs: visibleChildren });
|
|
118
|
-
}
|
|
119
|
-
} else if (await isItemVisible(item)) {
|
|
120
|
-
result[i].push(item);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
sections = result;
|
|
125
|
-
});
|
|
126
|
-
</script>
|
|
127
|
-
|
|
128
|
-
{#snippet section(section: any)}
|
|
129
|
-
<div class="flex flex-col {isSmallScreen ? 'gap-0' : 'gap-2'}">
|
|
130
|
-
{#each section as item}
|
|
131
|
-
{@const active = isItemActive(item)}
|
|
132
|
-
{#if isSmallScreen}
|
|
133
|
-
{#if !item.navs}
|
|
134
|
-
<Button
|
|
135
|
-
onclick={() => {
|
|
136
|
-
if (item.onclick) {
|
|
137
|
-
item.onclick();
|
|
138
|
-
} else {
|
|
139
|
-
goto(item.href);
|
|
140
|
-
}
|
|
141
|
-
isCollapsed = true;
|
|
142
|
-
}}
|
|
143
|
-
class="flex items-center justify-start flex-nowrap text-nowrap h-10 w-full {active
|
|
144
|
-
? 'bg-accent text-accent-foreground'
|
|
145
|
-
: 'text-muted-foreground'}"
|
|
146
|
-
variant="ghost"
|
|
147
|
-
size="icon"
|
|
148
|
-
Icon={item.icon}
|
|
149
|
-
>
|
|
150
|
-
{item.label}
|
|
151
|
-
</Button>
|
|
152
|
-
{:else}
|
|
153
|
-
<Accordion.Root type="single">
|
|
154
|
-
<Accordion.Item class="border-b-0">
|
|
155
|
-
<Accordion.Trigger class="justify-between p-0 h-10">
|
|
156
|
-
<div
|
|
157
|
-
class="flex items-center gap-2 {active
|
|
158
|
-
? 'text-accent-foreground'
|
|
159
|
-
: 'text-muted-foreground'}"
|
|
160
|
-
>
|
|
161
|
-
<item.icon size="18" />
|
|
162
|
-
<div class="text-nowrap">{item.label}</div>
|
|
163
|
-
</div>
|
|
164
|
-
</Accordion.Trigger>
|
|
165
|
-
<Accordion.Content class="pl-2 border-l">
|
|
166
|
-
{#each item.navs as childItem}
|
|
167
|
-
{@const childActive = isItemActive(childItem)}
|
|
168
|
-
<Button
|
|
169
|
-
onclick={() => {
|
|
170
|
-
if (childItem.onclick) {
|
|
171
|
-
childItem.onclick();
|
|
172
|
-
} else {
|
|
173
|
-
goto(item.href);
|
|
174
|
-
}
|
|
175
|
-
isCollapsed = true;
|
|
176
|
-
}}
|
|
177
|
-
class="flex items-center justify-start flex-nowrap text-nowrap h-8 w-full {childActive
|
|
178
|
-
? 'bg-accent text-accent-foreground'
|
|
179
|
-
: 'text-muted-foreground'}"
|
|
180
|
-
variant="ghost"
|
|
181
|
-
size="icon"
|
|
182
|
-
Icon={childItem.icon}
|
|
183
|
-
>
|
|
184
|
-
{childItem.label}
|
|
185
|
-
</Button>
|
|
186
|
-
{/each}
|
|
187
|
-
</Accordion.Content>
|
|
188
|
-
</Accordion.Item>
|
|
189
|
-
</Accordion.Root>
|
|
190
|
-
{/if}
|
|
191
|
-
{:else}
|
|
192
|
-
<Tooltip.Root>
|
|
193
|
-
<Tooltip.Trigger>
|
|
194
|
-
{#if !item.navs}
|
|
195
|
-
<Button
|
|
196
|
-
onclick={() => {
|
|
197
|
-
if (item.onclick) {
|
|
198
|
-
item.onclick();
|
|
199
|
-
}
|
|
200
|
-
isCollapsed = true;
|
|
201
|
-
}}
|
|
202
|
-
href={item.href}
|
|
203
|
-
class={active
|
|
204
|
-
? 'bg-accent text-accent-foreground'
|
|
205
|
-
: 'text-muted-foreground'}
|
|
206
|
-
variant="ghost"
|
|
207
|
-
size="icon"
|
|
208
|
-
Icon={item.icon}
|
|
209
|
-
></Button>
|
|
210
|
-
{:else}
|
|
211
|
-
<Popover.Root>
|
|
212
|
-
<Popover.Trigger>
|
|
213
|
-
<Button
|
|
214
|
-
class={active
|
|
215
|
-
? 'bg-accent text-accent-foreground'
|
|
216
|
-
: 'text-muted-foreground'}
|
|
217
|
-
variant="ghost"
|
|
218
|
-
size="icon"
|
|
219
|
-
Icon={item.icon}
|
|
220
|
-
></Button>
|
|
221
|
-
</Popover.Trigger>
|
|
222
|
-
<Popover.Content
|
|
223
|
-
sideOffset={17.5}
|
|
224
|
-
side="right"
|
|
225
|
-
class="popover_content w-60 mb-4 p-0"
|
|
226
|
-
>
|
|
227
|
-
<div class="py-1">
|
|
228
|
-
{#each item.navs as childItem}
|
|
229
|
-
{@const childActive = isItemActive(childItem)}
|
|
230
|
-
<div
|
|
231
|
-
class="px-1 text-xs text-muted-foreground"
|
|
232
|
-
>
|
|
233
|
-
<Button
|
|
234
|
-
variant="ghost"
|
|
235
|
-
class="flex h-7 w-full justify-start p-2 text-xs font-normal {childActive
|
|
236
|
-
? 'bg-accent text-accent-foreground'
|
|
237
|
-
: 'text-muted-foreground'}"
|
|
238
|
-
Icon={childItem.icon}
|
|
239
|
-
onclick={() => {
|
|
240
|
-
if (childItem.onclick) {
|
|
241
|
-
childItem.onclick();
|
|
242
|
-
}
|
|
243
|
-
}}
|
|
244
|
-
href={childItem.href}
|
|
245
|
-
>
|
|
246
|
-
{childItem.label}
|
|
247
|
-
</Button>
|
|
248
|
-
</div>
|
|
249
|
-
{/each}
|
|
250
|
-
</div>
|
|
251
|
-
</Popover.Content>
|
|
252
|
-
</Popover.Root>
|
|
253
|
-
{/if}
|
|
254
|
-
</Tooltip.Trigger>
|
|
255
|
-
<Tooltip.Content side="right" sideOffset={15}>
|
|
256
|
-
{item.label}
|
|
257
|
-
</Tooltip.Content>
|
|
258
|
-
</Tooltip.Root>
|
|
259
|
-
{/if}
|
|
260
|
-
{/each}
|
|
261
|
-
</div>
|
|
262
|
-
{/snippet}
|
|
263
|
-
|
|
264
|
-
<div
|
|
265
|
-
class="
|
|
266
|
-
{isSmallScreen ? 'fixed top-0 left-0 h-full z-50' : 'relative'}
|
|
267
|
-
border-r bg-background w-screen
|
|
268
|
-
{isCollapsed
|
|
269
|
-
? 'max-w-0 p-0'
|
|
270
|
-
: `max-w-14 ${isSmallScreen ? 'px-3 pb-3' : 'p-2'}`}"
|
|
271
|
-
style="transition: max-width 150ms, padding 150ms; {isSmallScreen &&
|
|
272
|
-
!isCollapsed
|
|
273
|
-
? 'max-width: 100vw'
|
|
274
|
-
: ''}"
|
|
275
|
-
>
|
|
276
|
-
{#if isSmallScreen}
|
|
277
|
-
{#if !isCollapsed}
|
|
278
|
-
<X
|
|
279
|
-
class="absolute h-10 text-muted-foreground hover:bg-transparent cursor-pointer hover:text-foreground"
|
|
280
|
-
style="left: 0.6rem; top: 0rem;"
|
|
281
|
-
size="18"
|
|
282
|
-
onclick={() => (isCollapsed = !isCollapsed)}
|
|
283
|
-
/>
|
|
284
|
-
{/if}
|
|
285
|
-
{/if}
|
|
286
|
-
<div
|
|
287
|
-
class="flex h-full flex-col justify-between gap-2 w-full overflow-hidden"
|
|
288
|
-
>
|
|
289
|
-
<!-- upper part -->
|
|
290
|
-
<div class="flex flex-col gap-2 {isSmallScreen ? 'pt-8' : ''}">
|
|
291
|
-
{@render section(sections[0])}
|
|
292
|
-
<Separator />
|
|
293
|
-
{@render section(sections[1])}
|
|
294
|
-
</div>
|
|
295
|
-
<div class="flex flex-col gap-2">
|
|
296
|
-
<Separator />
|
|
297
|
-
{@render section(sections[2])}
|
|
298
|
-
</div>
|
|
299
|
-
</div>
|
|
300
|
-
</div>
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export declare let collapseMiniSideBar: () => void;
|
|
2
|
-
export declare let expandMiniSideBar: () => void;
|
|
3
|
-
declare const MiniSidebar: import("svelte").Component<Record<string, never>, {}, "">;
|
|
4
|
-
type MiniSidebar = ReturnType<typeof MiniSidebar>;
|
|
5
|
-
export default MiniSidebar;
|
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
let isSmallScreen = $derived(!mediaQueries.sm.current);
|
|
3
|
-
let isCollapsed = $derived(isSmallScreen);
|
|
4
|
-
|
|
5
|
-
export let collapseMiniSideBar = () => {
|
|
6
|
-
isCollapsed = true;
|
|
7
|
-
};
|
|
8
|
-
export let expandMiniSideBar = () => {
|
|
9
|
-
isCollapsed = false;
|
|
10
|
-
};
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
|
-
<script lang="ts">
|
|
14
|
-
import { House, Layers, Library, Workflow, X } from "lucide-svelte";
|
|
15
|
-
import Button from "./ui/button/button.svelte";
|
|
16
|
-
import Separator from "./ui/separator/separator.svelte";
|
|
17
|
-
import * as Tooltip from "./ui/tooltip";
|
|
18
|
-
import * as Accordion from "./ui/accordion/index.js";
|
|
19
|
-
import { onMount } from "svelte";
|
|
20
|
-
|
|
21
|
-
import { getStudioContext } from "../context";
|
|
22
|
-
import { getDashboardNavs } from "../extensions/extensionUtils";
|
|
23
|
-
import { emitEvent } from "../eventSystem";
|
|
24
|
-
|
|
25
|
-
const { lobb, ctx } = getStudioContext();
|
|
26
|
-
import { mediaQueries } from "../utils";
|
|
27
|
-
import * as Popover from "./ui/popover";
|
|
28
|
-
import { page } from "$app/state";
|
|
29
|
-
import { goto } from "$app/navigation";
|
|
30
|
-
|
|
31
|
-
const rawSections: any[][] = [
|
|
32
|
-
[
|
|
33
|
-
{
|
|
34
|
-
label: "Home",
|
|
35
|
-
href: "/studio",
|
|
36
|
-
icon: House,
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
label: "Collections",
|
|
40
|
-
href: "/studio/collections",
|
|
41
|
-
icon: Library,
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
label: "Data Model",
|
|
45
|
-
href: "/studio/datamodel",
|
|
46
|
-
icon: Layers,
|
|
47
|
-
represents: "core_data_model",
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
label: "Workflows",
|
|
51
|
-
href: "/studio/workflows",
|
|
52
|
-
icon: Workflow,
|
|
53
|
-
represents: "core_workflows",
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
[],
|
|
57
|
-
[],
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
const navs = getDashboardNavs(ctx);
|
|
61
|
-
|
|
62
|
-
if (navs.top) {
|
|
63
|
-
rawSections[0] = [...rawSections[0], ...navs.top];
|
|
64
|
-
}
|
|
65
|
-
if (navs.middle) {
|
|
66
|
-
rawSections[1] = [...rawSections[1], ...navs.middle];
|
|
67
|
-
}
|
|
68
|
-
if (navs.bottom) {
|
|
69
|
-
rawSections[2] = [...rawSections[2], ...navs.bottom];
|
|
70
|
-
}
|
|
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
|
-
// SvelteKit's pathname can come back with a trailing slash (`/studio/`)
|
|
94
|
-
// depending on config, so we normalize it before comparing.
|
|
95
|
-
const currentPath = $derived(page.url.pathname.replace(/\/$/, "") || "/");
|
|
96
|
-
function isItemActive(item: any): boolean {
|
|
97
|
-
if (item.navs) return item.navs.some((c: any) => isItemActive(c));
|
|
98
|
-
if (!item.href) return false;
|
|
99
|
-
const itemHref = item.href.replace(/\/$/, "") || "/";
|
|
100
|
-
if (itemHref === "/studio") return currentPath === "/studio";
|
|
101
|
-
return currentPath === itemHref || currentPath.startsWith(itemHref + "/");
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// onMount is enough — Studio gets remounted on login/logout (see
|
|
105
|
-
// remountStudio in @lobb-js/studio), so by the time this component
|
|
106
|
-
// mounts, ctx.extensions.auth.user / permissions are already populated.
|
|
107
|
-
onMount(async () => {
|
|
108
|
-
const result: any[][] = [[], [], []];
|
|
109
|
-
for (let i = 0; i < rawSections.length; i++) {
|
|
110
|
-
for (const item of rawSections[i]) {
|
|
111
|
-
if (item.navs) {
|
|
112
|
-
const visibleChildren: any[] = [];
|
|
113
|
-
for (const child of item.navs) {
|
|
114
|
-
if (await isItemVisible(child)) visibleChildren.push(child);
|
|
115
|
-
}
|
|
116
|
-
if (visibleChildren.length && (await isItemVisible(item))) {
|
|
117
|
-
result[i].push({ ...item, navs: visibleChildren });
|
|
118
|
-
}
|
|
119
|
-
} else if (await isItemVisible(item)) {
|
|
120
|
-
result[i].push(item);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
sections = result;
|
|
125
|
-
});
|
|
126
|
-
</script>
|
|
127
|
-
|
|
128
|
-
{#snippet section(section: any)}
|
|
129
|
-
<div class="flex flex-col {isSmallScreen ? 'gap-0' : 'gap-2'}">
|
|
130
|
-
{#each section as item}
|
|
131
|
-
{@const active = isItemActive(item)}
|
|
132
|
-
{#if isSmallScreen}
|
|
133
|
-
{#if !item.navs}
|
|
134
|
-
<Button
|
|
135
|
-
onclick={() => {
|
|
136
|
-
if (item.onclick) {
|
|
137
|
-
item.onclick();
|
|
138
|
-
} else {
|
|
139
|
-
goto(item.href);
|
|
140
|
-
}
|
|
141
|
-
isCollapsed = true;
|
|
142
|
-
}}
|
|
143
|
-
class="flex items-center justify-start flex-nowrap text-nowrap h-10 w-full {active
|
|
144
|
-
? 'bg-accent text-accent-foreground'
|
|
145
|
-
: 'text-muted-foreground'}"
|
|
146
|
-
variant="ghost"
|
|
147
|
-
size="icon"
|
|
148
|
-
Icon={item.icon}
|
|
149
|
-
>
|
|
150
|
-
{item.label}
|
|
151
|
-
</Button>
|
|
152
|
-
{:else}
|
|
153
|
-
<Accordion.Root type="single">
|
|
154
|
-
<Accordion.Item class="border-b-0">
|
|
155
|
-
<Accordion.Trigger class="justify-between p-0 h-10">
|
|
156
|
-
<div
|
|
157
|
-
class="flex items-center gap-2 {active
|
|
158
|
-
? 'text-accent-foreground'
|
|
159
|
-
: 'text-muted-foreground'}"
|
|
160
|
-
>
|
|
161
|
-
<item.icon size="18" />
|
|
162
|
-
<div class="text-nowrap">{item.label}</div>
|
|
163
|
-
</div>
|
|
164
|
-
</Accordion.Trigger>
|
|
165
|
-
<Accordion.Content class="pl-2 border-l">
|
|
166
|
-
{#each item.navs as childItem}
|
|
167
|
-
{@const childActive = isItemActive(childItem)}
|
|
168
|
-
<Button
|
|
169
|
-
onclick={() => {
|
|
170
|
-
if (childItem.onclick) {
|
|
171
|
-
childItem.onclick();
|
|
172
|
-
} else {
|
|
173
|
-
goto(item.href);
|
|
174
|
-
}
|
|
175
|
-
isCollapsed = true;
|
|
176
|
-
}}
|
|
177
|
-
class="flex items-center justify-start flex-nowrap text-nowrap h-8 w-full {childActive
|
|
178
|
-
? 'bg-accent text-accent-foreground'
|
|
179
|
-
: 'text-muted-foreground'}"
|
|
180
|
-
variant="ghost"
|
|
181
|
-
size="icon"
|
|
182
|
-
Icon={childItem.icon}
|
|
183
|
-
>
|
|
184
|
-
{childItem.label}
|
|
185
|
-
</Button>
|
|
186
|
-
{/each}
|
|
187
|
-
</Accordion.Content>
|
|
188
|
-
</Accordion.Item>
|
|
189
|
-
</Accordion.Root>
|
|
190
|
-
{/if}
|
|
191
|
-
{:else}
|
|
192
|
-
<Tooltip.Root>
|
|
193
|
-
<Tooltip.Trigger>
|
|
194
|
-
{#if !item.navs}
|
|
195
|
-
<Button
|
|
196
|
-
onclick={() => {
|
|
197
|
-
if (item.onclick) {
|
|
198
|
-
item.onclick();
|
|
199
|
-
}
|
|
200
|
-
isCollapsed = true;
|
|
201
|
-
}}
|
|
202
|
-
href={item.href}
|
|
203
|
-
class={active
|
|
204
|
-
? 'bg-accent text-accent-foreground'
|
|
205
|
-
: 'text-muted-foreground'}
|
|
206
|
-
variant="ghost"
|
|
207
|
-
size="icon"
|
|
208
|
-
Icon={item.icon}
|
|
209
|
-
></Button>
|
|
210
|
-
{:else}
|
|
211
|
-
<Popover.Root>
|
|
212
|
-
<Popover.Trigger>
|
|
213
|
-
<Button
|
|
214
|
-
class={active
|
|
215
|
-
? 'bg-accent text-accent-foreground'
|
|
216
|
-
: 'text-muted-foreground'}
|
|
217
|
-
variant="ghost"
|
|
218
|
-
size="icon"
|
|
219
|
-
Icon={item.icon}
|
|
220
|
-
></Button>
|
|
221
|
-
</Popover.Trigger>
|
|
222
|
-
<Popover.Content
|
|
223
|
-
sideOffset={17.5}
|
|
224
|
-
side="right"
|
|
225
|
-
class="popover_content w-60 mb-4 p-0"
|
|
226
|
-
>
|
|
227
|
-
<div class="py-1">
|
|
228
|
-
{#each item.navs as childItem}
|
|
229
|
-
{@const childActive = isItemActive(childItem)}
|
|
230
|
-
<div
|
|
231
|
-
class="px-1 text-xs text-muted-foreground"
|
|
232
|
-
>
|
|
233
|
-
<Button
|
|
234
|
-
variant="ghost"
|
|
235
|
-
class="flex h-7 w-full justify-start p-2 text-xs font-normal {childActive
|
|
236
|
-
? 'bg-accent text-accent-foreground'
|
|
237
|
-
: 'text-muted-foreground'}"
|
|
238
|
-
Icon={childItem.icon}
|
|
239
|
-
onclick={() => {
|
|
240
|
-
if (childItem.onclick) {
|
|
241
|
-
childItem.onclick();
|
|
242
|
-
}
|
|
243
|
-
}}
|
|
244
|
-
href={childItem.href}
|
|
245
|
-
>
|
|
246
|
-
{childItem.label}
|
|
247
|
-
</Button>
|
|
248
|
-
</div>
|
|
249
|
-
{/each}
|
|
250
|
-
</div>
|
|
251
|
-
</Popover.Content>
|
|
252
|
-
</Popover.Root>
|
|
253
|
-
{/if}
|
|
254
|
-
</Tooltip.Trigger>
|
|
255
|
-
<Tooltip.Content side="right" sideOffset={15}>
|
|
256
|
-
{item.label}
|
|
257
|
-
</Tooltip.Content>
|
|
258
|
-
</Tooltip.Root>
|
|
259
|
-
{/if}
|
|
260
|
-
{/each}
|
|
261
|
-
</div>
|
|
262
|
-
{/snippet}
|
|
263
|
-
|
|
264
|
-
<div
|
|
265
|
-
class="
|
|
266
|
-
{isSmallScreen ? 'fixed top-0 left-0 h-full z-50' : 'relative'}
|
|
267
|
-
border-r bg-background w-screen
|
|
268
|
-
{isCollapsed
|
|
269
|
-
? 'max-w-0 p-0'
|
|
270
|
-
: `max-w-14 ${isSmallScreen ? 'px-3 pb-3' : 'p-2'}`}"
|
|
271
|
-
style="transition: max-width 150ms, padding 150ms; {isSmallScreen &&
|
|
272
|
-
!isCollapsed
|
|
273
|
-
? 'max-width: 100vw'
|
|
274
|
-
: ''}"
|
|
275
|
-
>
|
|
276
|
-
{#if isSmallScreen}
|
|
277
|
-
{#if !isCollapsed}
|
|
278
|
-
<X
|
|
279
|
-
class="absolute h-10 text-muted-foreground hover:bg-transparent cursor-pointer hover:text-foreground"
|
|
280
|
-
style="left: 0.6rem; top: 0rem;"
|
|
281
|
-
size="18"
|
|
282
|
-
onclick={() => (isCollapsed = !isCollapsed)}
|
|
283
|
-
/>
|
|
284
|
-
{/if}
|
|
285
|
-
{/if}
|
|
286
|
-
<div
|
|
287
|
-
class="flex h-full flex-col justify-between gap-2 w-full overflow-hidden"
|
|
288
|
-
>
|
|
289
|
-
<!-- upper part -->
|
|
290
|
-
<div class="flex flex-col gap-2 {isSmallScreen ? 'pt-8' : ''}">
|
|
291
|
-
{@render section(sections[0])}
|
|
292
|
-
<Separator />
|
|
293
|
-
{@render section(sections[1])}
|
|
294
|
-
</div>
|
|
295
|
-
<div class="flex flex-col gap-2">
|
|
296
|
-
<Separator />
|
|
297
|
-
{@render section(sections[2])}
|
|
298
|
-
</div>
|
|
299
|
-
</div>
|
|
300
|
-
</div>
|