@lobb-js/studio 0.29.0 → 0.29.1
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 +2 -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/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 +38 -14
- package/dist/extensions/extensionUtils.js +4 -2
- 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 +2 -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/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 +39 -6
- package/src/lib/extensions/extensionUtils.ts +4 -2
- 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
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
const { lobb, ctx } = getStudioContext();
|
|
9
9
|
import Sidebar from "../../sidebar/sidebar.svelte";
|
|
10
10
|
import Button from "../../ui/button/button.svelte";
|
|
11
|
-
import {
|
|
11
|
+
import { goto } from "$app/navigation";
|
|
12
12
|
import { CircleSlash2, Plus, Trash2 } from "lucide-svelte";
|
|
13
13
|
import { onMount } from "svelte";
|
|
14
14
|
import { showDialog } from "../../../actions";
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
const item: SideBarNode = {
|
|
40
40
|
type: "element",
|
|
41
41
|
name: workflow.name,
|
|
42
|
-
onclick: () =>
|
|
42
|
+
onclick: () => goto(`/studio/workflows/${workflow.name}`),
|
|
43
43
|
meta: { id: workflow.id },
|
|
44
44
|
};
|
|
45
45
|
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
await lobb.deleteOne("core_workflows", workflowId);
|
|
94
94
|
getSidebarData();
|
|
95
95
|
if (workflowEntry && workflowName === workflowEntry.name) {
|
|
96
|
-
|
|
96
|
+
goto("/studio/workflows");
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
<Button
|
|
106
106
|
class="h-7 px-3 text-xs font-normal w-full"
|
|
107
107
|
variant="outline"
|
|
108
|
-
onclick={() =>
|
|
108
|
+
onclick={() => goto("/studio/workflows/new")}
|
|
109
109
|
Icon={Plus}
|
|
110
110
|
>
|
|
111
111
|
Create a Workflow
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
{#if showSearch}
|
|
108
108
|
<div class="p-2">
|
|
109
109
|
<div
|
|
110
|
-
class="flex items-center px-4 py-1 text-muted-foreground bg-muted
|
|
110
|
+
class="flex items-center px-4 py-1 text-muted-foreground bg-muted-soft border rounded-md"
|
|
111
111
|
>
|
|
112
112
|
<input
|
|
113
113
|
type="text"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
import type { Snippet } from "svelte";
|
|
31
31
|
import SidebarElements from "./sidebarElements.svelte";
|
|
32
32
|
import Button from "../ui/button/button.svelte";
|
|
33
|
-
import {
|
|
33
|
+
import { page } from "$app/state";
|
|
34
34
|
|
|
35
35
|
let {
|
|
36
36
|
data = $bindable(),
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
{#each data as node, index}
|
|
59
59
|
{#if node.type === "directory"}
|
|
60
60
|
<button
|
|
61
|
-
class="flex items-center justify-between p-2 gap-2 text-muted-foreground rounded-md hover:bg-muted
|
|
61
|
+
class="flex items-center justify-between p-2 gap-2 text-muted-foreground rounded-md hover:bg-muted-soft cursor-pointer"
|
|
62
62
|
onclick={() => toggleDir(index)}
|
|
63
63
|
>
|
|
64
64
|
<div class="flex items-center gap-2">
|
|
@@ -83,13 +83,13 @@
|
|
|
83
83
|
</div>
|
|
84
84
|
</div>
|
|
85
85
|
{:else}
|
|
86
|
-
{@const isselected =
|
|
86
|
+
{@const isselected = page.url.pathname === node.href}
|
|
87
87
|
<Button
|
|
88
88
|
onclick={() => handleElementClick(node)}
|
|
89
89
|
href={node.href}
|
|
90
90
|
variant="ghost"
|
|
91
91
|
class="
|
|
92
|
-
flex items-center justify-between p-2 gap-2 hover:bg-muted
|
|
92
|
+
flex items-center justify-between p-2 gap-2 hover:bg-muted-soft text-muted-foreground
|
|
93
93
|
rounded-md {isselected ? 'bg-muted' : ''}
|
|
94
94
|
"
|
|
95
95
|
title={node.name}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Save } from "lucide-svelte";
|
|
3
|
-
import
|
|
4
|
-
import { getCollectionFields } from "./detailView/utils";
|
|
3
|
+
import DetailView from "./detailView/detailView.svelte";
|
|
5
4
|
import SidebarTrigger from "./sidebar/sidebarTrigger.svelte";
|
|
6
5
|
import Button from "./ui/button/button.svelte";
|
|
7
6
|
import { onMount } from "svelte";
|
|
@@ -9,7 +8,7 @@
|
|
|
9
8
|
import { toast } from "svelte-sonner";
|
|
10
9
|
import Skeleton from "./ui/skeleton/skeleton.svelte";
|
|
11
10
|
|
|
12
|
-
const { lobb
|
|
11
|
+
const { lobb } = getStudioContext();
|
|
13
12
|
|
|
14
13
|
interface Props {
|
|
15
14
|
collectionName: string;
|
|
@@ -17,9 +16,8 @@
|
|
|
17
16
|
|
|
18
17
|
let { collectionName }: Props = $props();
|
|
19
18
|
|
|
20
|
-
let entry = $state({});
|
|
19
|
+
let entry = $state<Record<string, any>>({});
|
|
21
20
|
let loading = $state(true);
|
|
22
|
-
const formFields = getCollectionFields(ctx, collectionName);
|
|
23
21
|
|
|
24
22
|
onMount(async () => {
|
|
25
23
|
const result = await fetch(`${lobb.lobbUrl}/api/collections/${collectionName}/singleton`, {
|
|
@@ -60,7 +58,7 @@
|
|
|
60
58
|
</div>
|
|
61
59
|
{:else}
|
|
62
60
|
<div class="flex-1 overflow-y-auto max-w-xl">
|
|
63
|
-
<
|
|
61
|
+
<DetailView {collectionName} bind:entry />
|
|
64
62
|
</div>
|
|
65
63
|
{/if}
|
|
66
64
|
</div>
|
|
@@ -49,7 +49,6 @@
|
|
|
49
49
|
<script lang="ts">
|
|
50
50
|
import { cn } from "../../../utils.js";
|
|
51
51
|
import { LoaderCircle } from "lucide-svelte";
|
|
52
|
-
import { Link, location } from "@wjfe/n-savant";
|
|
53
52
|
|
|
54
53
|
let {
|
|
55
54
|
class: className,
|
|
@@ -94,9 +93,9 @@
|
|
|
94
93
|
{/snippet}
|
|
95
94
|
|
|
96
95
|
{#if href}
|
|
97
|
-
<
|
|
96
|
+
<a {href} class={cn(buttonVariants({ variant, size }), className)}>
|
|
98
97
|
{@render innerPart()}
|
|
99
|
-
</
|
|
98
|
+
</a>
|
|
100
99
|
{:else}
|
|
101
100
|
<button
|
|
102
101
|
bind:this={ref}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
const { lobb, ctx } = getStudioContext();
|
|
18
18
|
import Button from "./ui/button/button.svelte";
|
|
19
19
|
import Input from "./ui/input/input.svelte";
|
|
20
|
-
import {
|
|
20
|
+
import { goto } from "$app/navigation";
|
|
21
21
|
import { Edit, Plus } from "lucide-svelte";
|
|
22
22
|
import { toast } from "svelte-sonner";
|
|
23
23
|
import { isEqual } from "lodash-es";
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
const reponse = await lobb.createOne("core_workflows", workflow);
|
|
97
97
|
const result = await reponse.json();
|
|
98
98
|
const workflowEntry = result.data;
|
|
99
|
-
|
|
99
|
+
goto(`/studio/workflows/${workflowEntry.name}`);
|
|
100
100
|
await refreshSidebar();
|
|
101
101
|
}
|
|
102
102
|
|
package/dist/eventSystem.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { StudioContext } from "./context";
|
|
2
|
-
export declare function emitEvent(studioContext: StudioContext, eventName: string, input:
|
|
2
|
+
export declare function emitEvent(studioContext: StudioContext, eventName: string, input: any): Promise<any>;
|
package/dist/eventSystem.js
CHANGED
|
@@ -3,15 +3,17 @@ import { openCreateDetailView, openUpdateDetailView } from "./actions";
|
|
|
3
3
|
export async function emitEvent(studioContext, eventName, input) {
|
|
4
4
|
const { ctx } = studioContext;
|
|
5
5
|
const workflows = ctx.meta.studio_workflows.filter((workflow) => {
|
|
6
|
-
return eventName
|
|
6
|
+
return eventName === workflow.eventName;
|
|
7
7
|
});
|
|
8
8
|
for (let index = 0; index < workflows.length; index++) {
|
|
9
9
|
const workflow = workflows[index];
|
|
10
10
|
try {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// Whatever the handler returns becomes the input for the next
|
|
12
|
+
// subscriber and the final emit() return. No special handling of
|
|
13
|
+
// undefined — chain handlers should return their input, override
|
|
14
|
+
// handlers return undefined to opt out, observer handlers can
|
|
15
|
+
// return nothing if no one downstream reads input.
|
|
16
|
+
input = await workflow.handler(input, await getEventContext(studioContext));
|
|
15
17
|
}
|
|
16
18
|
catch (error) {
|
|
17
19
|
toast.error(error.message);
|
|
@@ -11,7 +11,8 @@ import type CreateDetailViewButton from "../components/detailView/create/createD
|
|
|
11
11
|
import type UpdateDetailViewButton from "../components/detailView/update/updateDetailViewButton.svelte";
|
|
12
12
|
import type { mediaQueries } from "../utils";
|
|
13
13
|
import type Table from "../components/dataTable/table.svelte";
|
|
14
|
-
import type {
|
|
14
|
+
import type { page } from "$app/state";
|
|
15
|
+
import type { goto } from "$app/navigation";
|
|
15
16
|
import type RangeCalendarButton from "../components/rangeCalendarButton.svelte";
|
|
16
17
|
import type DataTable from "../components/dataTable/dataTable.svelte";
|
|
17
18
|
import type Drawer from "../components/drawer.svelte";
|
|
@@ -22,7 +23,7 @@ import * as Icons from "lucide-svelte";
|
|
|
22
23
|
import { ContextMenu } from "bits-ui";
|
|
23
24
|
import * as Tooltip from "../components/ui/tooltip";
|
|
24
25
|
import * as Breadcrumb from "../components/ui/breadcrumb";
|
|
25
|
-
import { showDialog } from "../actions";
|
|
26
|
+
import { showDialog, type OpenDataTableDrawerProps } from "../actions";
|
|
26
27
|
import { toast } from "svelte-sonner";
|
|
27
28
|
import { Switch } from "../components/ui/switch";
|
|
28
29
|
export interface Components {
|
|
@@ -50,18 +51,23 @@ export interface Components {
|
|
|
50
51
|
export interface ExtensionUtils {
|
|
51
52
|
ctx: CTX;
|
|
52
53
|
lobb: LobbClient;
|
|
53
|
-
|
|
54
|
+
/**
|
|
55
|
+
* SvelteKit's reactive page proxy — exposes `url`, `params`, `route`,
|
|
56
|
+
* `data`, `state`, `error`. Access fields through the proxy
|
|
57
|
+
* (`utils.page.url.pathname`); don't destructure if you need
|
|
58
|
+
* reactivity (`const { url } = utils.page` is a one-time snapshot).
|
|
59
|
+
*/
|
|
60
|
+
page: typeof page;
|
|
61
|
+
/**
|
|
62
|
+
* SvelteKit's client-side navigation. Same signature as
|
|
63
|
+
* `goto(href, options?)` — supports `replaceState`, `invalidateAll`,
|
|
64
|
+
* `noScroll`, `keepFocus`, etc.
|
|
65
|
+
*/
|
|
66
|
+
goto: typeof goto;
|
|
54
67
|
toast: typeof toast;
|
|
55
68
|
showDialog: typeof showDialog;
|
|
56
|
-
openDataTableDrawer: (props:
|
|
57
|
-
|
|
58
|
-
filter?: Record<string, any>;
|
|
59
|
-
title?: string;
|
|
60
|
-
showHeader?: boolean;
|
|
61
|
-
showFooter?: boolean;
|
|
62
|
-
position?: "side" | "bottom";
|
|
63
|
-
}) => void;
|
|
64
|
-
emitEvent: (eventName: string, input: Record<string, any>) => Promise<Record<string, any>>;
|
|
69
|
+
openDataTableDrawer: (props: OpenDataTableDrawerProps) => void;
|
|
70
|
+
emitEvent: (eventName: string, input: any) => Promise<any>;
|
|
65
71
|
components: Components;
|
|
66
72
|
mediaQueries: typeof mediaQueries;
|
|
67
73
|
intlDate: typeof intlDate;
|
|
@@ -72,6 +78,24 @@ interface DashboardNav {
|
|
|
72
78
|
href?: string;
|
|
73
79
|
onclick?: () => void;
|
|
74
80
|
navs?: DashboardNav[];
|
|
81
|
+
/**
|
|
82
|
+
* The collection(s) this nav item represents — i.e. the UI surface it
|
|
83
|
+
* exposes for that data.
|
|
84
|
+
*
|
|
85
|
+
* Visibility is derived from this: the auth extension's `auth.canAccess`
|
|
86
|
+
* workflow checks the current user's read permission on the represented
|
|
87
|
+
* collection(s), and the item is hidden if denied. Items without a
|
|
88
|
+
* `represents` value are always visible.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* // single collection
|
|
92
|
+
* { label: "Reports", href: "/reports", icon: BarChart, represents: "reports_dashboards" }
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* // multiple collections — user must be able to read all of them
|
|
96
|
+
* { label: "Audit", href: "/audit", icon: ShieldCheck, represents: ["audit_log", "auth_users"] }
|
|
97
|
+
*/
|
|
98
|
+
represents?: string | string[];
|
|
75
99
|
}
|
|
76
100
|
export interface DashboardNavs {
|
|
77
101
|
top?: DashboardNav[];
|
|
@@ -82,7 +106,7 @@ export interface ExtensionProps {
|
|
|
82
106
|
utils: ExtensionUtils;
|
|
83
107
|
[key: string]: any;
|
|
84
108
|
}
|
|
85
|
-
export type ExtensionComponentKey = `pages.${string}` | "studio.listView" | `dvFields.topRight.${string}.${string}` | `detailView.update.subRecords.${string}` | `detailView.create.subRecords.${string}` | `detailView.fields.topRight.${string}.${string}` | `detailView.fields.foreignKey.${string}` | `listView.entry.children.${string}` | `listView.entry.actions` | `listView.header.actions` | (string & {});
|
|
109
|
+
export type ExtensionComponentKey = `pages.${string}` | `publicPages.${string}` | "studio.listView" | `dvFields.topRight.${string}.${string}` | `detailView.update.subRecords.${string}` | `detailView.create.subRecords.${string}` | `detailView.fields.topRight.${string}.${string}` | "detailView.field.label" | `detailView.fields.foreignKey.${string}` | `listView.entry.children.${string}` | `listView.entry.actions` | `listView.header.actions` | (string & {});
|
|
86
110
|
export type ExtensionComponent = any | {
|
|
87
111
|
component: any;
|
|
88
112
|
when: (props: Record<string, any>) => boolean;
|
|
@@ -94,7 +118,7 @@ export interface StudioWorkflowContext {
|
|
|
94
118
|
}
|
|
95
119
|
export interface StudioWorkflow {
|
|
96
120
|
eventName: string;
|
|
97
|
-
handler: (input:
|
|
121
|
+
handler: (input: any, context: StudioWorkflowContext) => Promise<any>;
|
|
98
122
|
}
|
|
99
123
|
export interface Extension {
|
|
100
124
|
name: string;
|
|
@@ -18,7 +18,8 @@ import * as Breadcrumb from "../components/ui/breadcrumb";
|
|
|
18
18
|
import { ContextMenu } from "bits-ui";
|
|
19
19
|
import * as Popover from "../components/ui/popover";
|
|
20
20
|
import * as Icons from "lucide-svelte";
|
|
21
|
-
import {
|
|
21
|
+
import { page } from "$app/state";
|
|
22
|
+
import { goto } from "$app/navigation";
|
|
22
23
|
import RangeCalendarButton from "../components/rangeCalendarButton.svelte";
|
|
23
24
|
import DataTable from "../components/dataTable/dataTable.svelte";
|
|
24
25
|
import Drawer from "../components/drawer.svelte";
|
|
@@ -52,7 +53,8 @@ export function getExtensionUtils(lobb, ctx) {
|
|
|
52
53
|
return {
|
|
53
54
|
ctx: ctx,
|
|
54
55
|
lobb: lobb,
|
|
55
|
-
|
|
56
|
+
page: page,
|
|
57
|
+
goto: goto,
|
|
56
58
|
toast: toast,
|
|
57
59
|
showDialog: showDialog,
|
|
58
60
|
openDataTableDrawer: (props) => openDataTableDrawer({ lobb, ctx }, props),
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type { ExtensionProps, Extension, ExtensionUtils } from "./extensions/extension.types";
|
|
2
|
-
export { default as
|
|
2
|
+
export { default as StudioRoot } from "./components/StudioRoot.svelte";
|
|
3
3
|
export { default as Landing } from "./components/landing.svelte";
|
|
4
|
+
export { remountStudio } from "./studioLifecycle.svelte";
|
|
4
5
|
export { Button } from "./components/ui/button";
|
|
5
6
|
export { Input } from "./components/ui/input";
|
|
6
7
|
export { Separator } from "./components/ui/separator";
|
|
@@ -10,6 +11,7 @@ export { default as Sidebar } from "./components/sidebar/sidebar.svelte";
|
|
|
10
11
|
export { default as SidebarTrigger } from "./components/sidebar/sidebarTrigger.svelte";
|
|
11
12
|
export { default as CreateDetailViewButton } from "./components/detailView/create/createDetailViewButton.svelte";
|
|
12
13
|
export { default as UpdateDetailViewButton } from "./components/detailView/update/updateDetailViewButton.svelte";
|
|
14
|
+
export { default as DetailView } from "./components/detailView/detailView.svelte";
|
|
13
15
|
export * as Tooltip from "./components/ui/tooltip";
|
|
14
16
|
export * as Breadcrumb from "./components/ui/breadcrumb";
|
|
15
17
|
export { ContextMenu } from "bits-ui";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export { default as
|
|
1
|
+
export { default as StudioRoot } from "./components/StudioRoot.svelte";
|
|
2
2
|
export { default as Landing } from "./components/landing.svelte";
|
|
3
|
+
export { remountStudio } from "./studioLifecycle.svelte";
|
|
3
4
|
export { Button } from "./components/ui/button";
|
|
4
5
|
export { Input } from "./components/ui/input";
|
|
5
6
|
export { Separator } from "./components/ui/separator";
|
|
@@ -9,6 +10,7 @@ export { default as Sidebar } from "./components/sidebar/sidebar.svelte";
|
|
|
9
10
|
export { default as SidebarTrigger } from "./components/sidebar/sidebarTrigger.svelte";
|
|
10
11
|
export { default as CreateDetailViewButton } from "./components/detailView/create/createDetailViewButton.svelte";
|
|
11
12
|
export { default as UpdateDetailViewButton } from "./components/detailView/update/updateDetailViewButton.svelte";
|
|
13
|
+
export { default as DetailView } from "./components/detailView/detailView.svelte";
|
|
12
14
|
export * as Tooltip from "./components/ui/tooltip";
|
|
13
15
|
export * as Breadcrumb from "./components/ui/breadcrumb";
|
|
14
16
|
export { ContextMenu } from "bits-ui";
|
package/dist/store.types.d.ts
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Module-level reactive signal that the Studio root listens to via {#key ...}.
|
|
2
|
+
// Bumping this value tears down the entire Studio component tree and mounts
|
|
3
|
+
// a fresh one, so onStartup re-runs and everything downstream (ctx,
|
|
4
|
+
// extensions, sidebar) initialises with the new session state.
|
|
5
|
+
//
|
|
6
|
+
// Use cases: after login, after logout, after a role/permissions change.
|
|
7
|
+
// Consumers (e.g. the auth extension) call `remountStudio()` instead of
|
|
8
|
+
// having every UI surface listen for auth changes reactively.
|
|
9
|
+
let mountKey = $state(0);
|
|
10
|
+
export function getStudioMountKey() {
|
|
11
|
+
return mountKey;
|
|
12
|
+
}
|
|
13
|
+
export function remountStudio() {
|
|
14
|
+
mountKey += 1;
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobb-js/studio",
|
|
3
3
|
"license": "UNLICENSED",
|
|
4
|
-
"version": "0.29.
|
|
4
|
+
"version": "0.29.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"postpublish": "./scripts/postpublish.sh"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@lobb-js/core": "^0.
|
|
45
|
+
"@lobb-js/core": "^0.32.1",
|
|
46
46
|
"@chromatic-com/storybook": "^4.1.2",
|
|
47
47
|
"@storybook/addon-a11y": "^10.0.1",
|
|
48
48
|
"@storybook/addon-docs": "^10.0.1",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"@codemirror/view": "^6.39.12",
|
|
88
88
|
"@dagrejs/dagre": "^1.1.5",
|
|
89
89
|
"@internationalized/date": "^3.12.0",
|
|
90
|
-
"@lobb-js/sdk": "^0.2.
|
|
90
|
+
"@lobb-js/sdk": "^0.2.1",
|
|
91
91
|
"@lucide/svelte": "^0.563.1",
|
|
92
92
|
"@tailwindcss/vite": "^4.3.0",
|
|
93
93
|
"@tiptap/core": "^3.0.0",
|
|
@@ -95,7 +95,6 @@
|
|
|
95
95
|
"@tiptap/extension-underline": "^3.0.0",
|
|
96
96
|
"@tiptap/pm": "^3.0.0",
|
|
97
97
|
"@tiptap/starter-kit": "^3.0.0",
|
|
98
|
-
"@wjfe/n-savant": "^0.3.0",
|
|
99
98
|
"@xyflow/svelte": "^1.2.0",
|
|
100
99
|
"bits-ui": "^1.8.0",
|
|
101
100
|
"clsx": "^2.1.1",
|
package/src/app.css
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
--secondary-foreground: oklch(0.208 0.042 265.755);
|
|
21
21
|
--muted: oklch(0.968 0.007 247.896);
|
|
22
22
|
--muted-foreground: oklch(0.554 0.046 257.417);
|
|
23
|
+
--muted-soft: oklch(0.9904 0.0021 247.896);
|
|
23
24
|
--accent: oklch(0.968 0.007 247.896);
|
|
24
25
|
--accent-foreground: oklch(0.208 0.042 265.755);
|
|
25
26
|
--destructive: oklch(0.577 0.245 27.325);
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
--secondary-foreground: oklch(0.984 0.003 247.858);
|
|
55
56
|
--muted: oklch(0.279 0.041 260.031);
|
|
56
57
|
--muted-foreground: oklch(0.704 0.04 256.788);
|
|
58
|
+
--muted-soft: oklch(0.174 0.042 263.3);
|
|
57
59
|
--accent: oklch(0.279 0.041 260.031);
|
|
58
60
|
--accent-foreground: oklch(0.984 0.003 247.858);
|
|
59
61
|
--destructive: oklch(0.704 0.191 22.216);
|
|
@@ -92,6 +94,7 @@
|
|
|
92
94
|
--color-secondary-foreground: var(--secondary-foreground);
|
|
93
95
|
--color-muted: var(--muted);
|
|
94
96
|
--color-muted-foreground: var(--muted-foreground);
|
|
97
|
+
--color-muted-soft: var(--muted-soft);
|
|
95
98
|
--color-accent: var(--accent);
|
|
96
99
|
--color-accent-foreground: var(--accent-foreground);
|
|
97
100
|
--color-destructive: var(--destructive);
|
package/src/lib/actions.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { UpdateDetailViewProp } from "./components/detailView/update/update
|
|
|
8
8
|
import type { StudioContext } from "./context";
|
|
9
9
|
import { createStudioContextMap } from "./context";
|
|
10
10
|
import { getCollectionParamsFields } from "./components/dataTable/utils";
|
|
11
|
+
import type { CollectionTab } from "./store.types";
|
|
11
12
|
|
|
12
13
|
export interface OpenDataTableDrawerProps {
|
|
13
14
|
collectionName: string;
|
|
@@ -16,6 +17,7 @@ export interface OpenDataTableDrawerProps {
|
|
|
16
17
|
showHeader?: boolean;
|
|
17
18
|
showFooter?: boolean;
|
|
18
19
|
position?: "side" | "bottom";
|
|
20
|
+
tabs?: CollectionTab[];
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export function showDialog(title: string, description: string): Promise<boolean> {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Toaster } from "./ui/sonner";
|
|
3
|
-
import { onMount
|
|
3
|
+
import { onMount } from "svelte";
|
|
4
4
|
import { ModeWatcher } from "mode-watcher";
|
|
5
5
|
import { createLobb } from "../store.svelte";
|
|
6
6
|
import { setStudioContext } from "../context";
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
import { LoaderCircle, ServerOff } from "lucide-svelte";
|
|
9
9
|
import MiniSidebar from "./miniSidebar.svelte";
|
|
10
10
|
import * as Tooltip from "./ui/tooltip";
|
|
11
|
-
import {
|
|
11
|
+
import { page } from "$app/state";
|
|
12
|
+
import { afterNavigate } from "$app/navigation";
|
|
12
13
|
import {
|
|
13
14
|
executeExtensionsOnStartup,
|
|
14
15
|
executeExtensionsOnRouteChange,
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
import Collections from "./routes/collections/collections.svelte";
|
|
23
24
|
import Workflows from "./routes/workflows/workflows.svelte";
|
|
24
25
|
import Extension from "./routes/extensions/extension.svelte";
|
|
26
|
+
import PublicExtension from "./routes/extensions/publicExtension.svelte";
|
|
25
27
|
|
|
26
28
|
interface StudioProps {
|
|
27
29
|
lobbUrl?: string;
|
|
@@ -42,13 +44,11 @@
|
|
|
42
44
|
|
|
43
45
|
let status: "loading" | "error" | "ready" = $state("loading");
|
|
44
46
|
let isSmallScreen = $derived(!mediaQueries.sm.current);
|
|
45
|
-
let cleanupRouter: (() => void) | undefined;
|
|
46
47
|
|
|
47
48
|
onMount(async () => {
|
|
48
49
|
// Remove the static loading screen defined in app.html — it shows instantly
|
|
49
50
|
// before JS loads to avoid a blank page, and is replaced by the Studio UI.
|
|
50
51
|
document.getElementById("app-loading")?.remove();
|
|
51
|
-
cleanupRouter = initRouter();
|
|
52
52
|
try {
|
|
53
53
|
ctx.meta = await lobb.getMeta();
|
|
54
54
|
ctx.extensions = await loadExtensions(lobb, ctx, extensionMap);
|
|
@@ -59,19 +59,13 @@
|
|
|
59
59
|
console.error(err);
|
|
60
60
|
status = "error";
|
|
61
61
|
}
|
|
62
|
-
|
|
63
|
-
// Fire onRouteChange hooks on every navigation
|
|
64
|
-
const onRouteChange = () => executeExtensionsOnRouteChange(lobb, ctx as any, window.location.pathname);
|
|
65
|
-
const originalPushState = history.pushState.bind(history);
|
|
66
|
-
history.pushState = function (...args) {
|
|
67
|
-
originalPushState(...args);
|
|
68
|
-
onRouteChange();
|
|
69
|
-
};
|
|
70
|
-
window.addEventListener("popstate", onRouteChange);
|
|
71
62
|
});
|
|
72
63
|
|
|
73
|
-
|
|
74
|
-
|
|
64
|
+
// Fire onRouteChange hooks via SvelteKit's afterNavigate lifecycle instead
|
|
65
|
+
// of monkey-patching history.pushState. Runs both on initial mount and on
|
|
66
|
+
// every client-side navigation.
|
|
67
|
+
afterNavigate(() => {
|
|
68
|
+
executeExtensionsOnRouteChange(lobb, ctx as any, page.url.pathname);
|
|
75
69
|
});
|
|
76
70
|
</script>
|
|
77
71
|
|
|
@@ -94,6 +88,18 @@
|
|
|
94
88
|
<div class="text-xs">Could not connect to the lobb server at this endpoint ({ctx.lobbUrl})</div>
|
|
95
89
|
</div>
|
|
96
90
|
</div>
|
|
91
|
+
{:else if page.url.pathname.startsWith("/studio/public/")}
|
|
92
|
+
<!-- Public extension pages skip the dashboard chrome (no sidebar, no
|
|
93
|
+
header) since the viewer is unauthenticated and shouldn't see any
|
|
94
|
+
navigation to gated areas. -->
|
|
95
|
+
<Tooltip.Provider delayDuration={0} disableHoverableContent={true}>
|
|
96
|
+
<main class="bg-background h-screen w-screen">
|
|
97
|
+
<PublicExtension
|
|
98
|
+
extension={page.url.pathname.split("/")[3]}
|
|
99
|
+
page={page.url.pathname.split("/")[4]}
|
|
100
|
+
/>
|
|
101
|
+
</main>
|
|
102
|
+
</Tooltip.Provider>
|
|
97
103
|
{:else}
|
|
98
104
|
<Tooltip.Provider delayDuration={0} disableHoverableContent={true}>
|
|
99
105
|
<main
|
|
@@ -103,34 +109,24 @@
|
|
|
103
109
|
<MiniSidebar />
|
|
104
110
|
<div class="second_grid">
|
|
105
111
|
<Header />
|
|
106
|
-
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
<Workflows workflowName={params?.workflow} />
|
|
125
|
-
{/snippet}
|
|
126
|
-
</Route>
|
|
127
|
-
<Route key="extensions" path="/extensions/:extension?/:page?/*">
|
|
128
|
-
{#snippet children(params)}
|
|
129
|
-
<Extension extension={params?.extension} page={params?.page} />
|
|
130
|
-
{/snippet}
|
|
131
|
-
</Route>
|
|
132
|
-
<Fallback>Not Found</Fallback>
|
|
133
|
-
</Router>
|
|
112
|
+
{#if page.url.pathname.replace(/\/$/, "") === "/studio"}
|
|
113
|
+
<Home />
|
|
114
|
+
{:else if page.url.pathname.startsWith("/studio/collections")}
|
|
115
|
+
<Collections collectionName={page.url.pathname.split("/")[3]} />
|
|
116
|
+
{:else if page.url.pathname.startsWith("/studio/datamodel")}
|
|
117
|
+
<DataModel />
|
|
118
|
+
{:else if page.url.pathname.startsWith("/studio/workflows")}
|
|
119
|
+
<Workflows workflowName={page.url.pathname.split("/")[3]} />
|
|
120
|
+
{:else if page.url.pathname.startsWith("/studio/extensions")}
|
|
121
|
+
<Extension
|
|
122
|
+
extension={page.url.pathname.split("/")[3]}
|
|
123
|
+
page={page.url.pathname.split("/")[4]}
|
|
124
|
+
/>
|
|
125
|
+
{:else}
|
|
126
|
+
<div class="flex h-full w-full items-center justify-center text-muted-foreground">
|
|
127
|
+
Not Found
|
|
128
|
+
</div>
|
|
129
|
+
{/if}
|
|
134
130
|
</div>
|
|
135
131
|
</main>
|
|
136
132
|
</Tooltip.Provider>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Studio from "./Studio.svelte";
|
|
3
|
+
import { getStudioMountKey } from "../studioLifecycle.svelte";
|
|
4
|
+
|
|
5
|
+
interface StudioRootProps {
|
|
6
|
+
lobbUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { lobbUrl }: StudioRootProps = $props();
|
|
10
|
+
|
|
11
|
+
// Tracked so any change to the mount key tears down the Studio tree and
|
|
12
|
+
// mounts a fresh one — fresh ctx, fresh extension load, fresh /me, etc.
|
|
13
|
+
// Bumped by remountStudio() on login/logout.
|
|
14
|
+
const mountKey = $derived(getStudioMountKey());
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
{#key mountKey}
|
|
18
|
+
<Studio {lobbUrl} />
|
|
19
|
+
{/key}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import * as Breadcrumb from "./ui/breadcrumb";
|
|
3
3
|
import { mediaQueries } from "../utils";
|
|
4
|
-
import {
|
|
4
|
+
import { page } from "$app/state";
|
|
5
|
+
import { goto } from "$app/navigation";
|
|
5
6
|
|
|
6
7
|
const isSmall = $derived(!mediaQueries.sm.current);
|
|
7
8
|
const pathNames = $derived(
|
|
8
|
-
|
|
9
|
+
page.url.pathname
|
|
9
10
|
.replace("/studio", "")
|
|
10
11
|
.split("/")
|
|
11
12
|
.filter((el: any) => el !== "")
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
{:else}
|
|
27
28
|
<Breadcrumb.Link
|
|
28
29
|
class="cursor-pointer"
|
|
29
|
-
onclick={() =>
|
|
30
|
+
onclick={() => goto("/studio")}
|
|
30
31
|
>
|
|
31
32
|
Home
|
|
32
33
|
</Breadcrumb.Link>
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
<Breadcrumb.Link
|
|
46
47
|
class="cursor-pointer"
|
|
47
48
|
onclick={() =>
|
|
48
|
-
|
|
49
|
+
goto(`/studio/${currentFullPaths}`)}
|
|
49
50
|
>
|
|
50
51
|
{path}
|
|
51
52
|
</Breadcrumb.Link>
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
});
|
|
133
133
|
</script>
|
|
134
134
|
|
|
135
|
-
<div class={cn('resize-y rounded-md border bg-muted
|
|
135
|
+
<div class={cn('resize-y rounded-md border bg-muted-soft h-60', className)}>
|
|
136
136
|
<div bind:this={editorContainer} class="h-full w-full pl-2" />
|
|
137
137
|
</div>
|
|
138
138
|
|