@lobb-js/studio 0.28.6 → 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 +46 -47
- 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/combobox.svelte +3 -3
- package/dist/components/confirmationDialog/confirmationDialog.svelte +1 -1
- package/dist/components/dataTable/dataTable.svelte +108 -101
- package/dist/components/dataTable/dataTable.svelte.d.ts +5 -20
- package/dist/components/dataTable/dataTableTabs.svelte +4 -2
- package/dist/components/dataTable/dataTableTabs.svelte.d.ts +2 -0
- package/dist/components/dataTable/filter.svelte +1 -1
- package/dist/components/dataTable/filterButton.svelte +1 -1
- package/dist/components/dataTable/header.svelte +30 -47
- package/dist/components/dataTable/header.svelte.d.ts +4 -2
- package/dist/components/dataTable/listViewChildren.svelte +4 -6
- package/dist/components/dataTable/listViewChildren.svelte.d.ts +0 -1
- package/dist/components/dataTable/sort.svelte +1 -1
- package/dist/components/dataTable/sortButton.svelte +2 -2
- 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 +2 -2
- package/dist/components/detailView/create/createDetailView.svelte +81 -88
- package/dist/components/detailView/create/createDetailView.svelte.d.ts +2 -2
- package/dist/components/detailView/create/createDetailViewButton.svelte +2 -2
- package/dist/components/detailView/create/createDetailViewButton.svelte.d.ts +1 -1
- package/dist/components/detailView/create/createManyView.svelte +12 -10
- package/dist/components/detailView/detailView.svelte +81 -0
- package/dist/components/detailView/detailView.svelte.d.ts +8 -0
- package/dist/components/detailView/fieldInput.svelte +11 -11
- package/dist/components/detailView/fieldInputReplacement.svelte +8 -8
- package/dist/components/detailView/passwordInput.svelte +1 -1
- package/dist/components/detailView/update/detailViewChildren.svelte +15 -26
- package/dist/components/detailView/update/detailViewChildren.svelte.d.ts +3 -8
- package/dist/components/detailView/update/updateDetailView.svelte +90 -69
- package/dist/components/detailView/update/updateDetailView.svelte.d.ts +2 -2
- package/dist/components/detailView/update/updateDetailViewButton.svelte +3 -2
- package/dist/components/detailView/update/updateDetailViewButton.svelte.d.ts +1 -1
- package/dist/components/detailView/utils.d.ts +17 -0
- 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 +90 -19
- package/dist/components/miniSidebar.svelte.d.ts +2 -17
- package/dist/components/polymorphicInput.svelte +1 -1
- package/dist/components/rangeCalendarButton.svelte +13 -13
- package/dist/components/richTextEditor.svelte +1 -1
- package/dist/components/routes/collections/collection.svelte +3 -3
- package/dist/components/routes/collections/collections.svelte +34 -12
- 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/extension.svelte +1 -1
- 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 +3 -3
- package/dist/components/routes/workflows/workflows.svelte +9 -9
- package/dist/components/selectRecord.svelte +2 -21
- package/dist/components/setServerPage.svelte +1 -1
- 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/alert-dialog/alert-dialog-action.svelte +1 -1
- package/dist/components/ui/alert-dialog/alert-dialog-cancel.svelte +1 -1
- package/dist/components/ui/button/button.svelte +2 -3
- package/dist/components/ui/command/command-dialog.svelte +1 -1
- package/dist/components/ui/range-calendar/range-calendar-day.svelte +1 -1
- package/dist/components/ui/range-calendar/range-calendar-next-button.svelte +1 -1
- package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte +1 -1
- package/dist/components/ui/select/select-separator.svelte +1 -1
- package/dist/components/workflowEditor.svelte +5 -5
- 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 +2 -2
- 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 +46 -47
- 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/combobox.svelte +3 -3
- package/src/lib/components/confirmationDialog/confirmationDialog.svelte +1 -1
- package/src/lib/components/dataTable/dataTable.svelte +108 -101
- package/src/lib/components/dataTable/dataTableTabs.svelte +4 -2
- package/src/lib/components/dataTable/filter.svelte +1 -1
- package/src/lib/components/dataTable/filterButton.svelte +1 -1
- package/src/lib/components/dataTable/header.svelte +30 -47
- package/src/lib/components/dataTable/listViewChildren.svelte +4 -6
- package/src/lib/components/dataTable/sort.svelte +1 -1
- package/src/lib/components/dataTable/sortButton.svelte +2 -2
- 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 +2 -2
- package/src/lib/components/detailView/create/createDetailView.svelte +81 -88
- package/src/lib/components/detailView/create/createDetailViewButton.svelte +2 -2
- package/src/lib/components/detailView/create/createManyView.svelte +12 -10
- package/src/lib/components/detailView/detailView.svelte +81 -0
- package/src/lib/components/detailView/fieldInput.svelte +11 -11
- package/src/lib/components/detailView/fieldInputReplacement.svelte +8 -8
- package/src/lib/components/detailView/passwordInput.svelte +1 -1
- package/src/lib/components/detailView/update/detailViewChildren.svelte +15 -26
- package/src/lib/components/detailView/update/updateDetailView.svelte +90 -69
- package/src/lib/components/detailView/update/updateDetailViewButton.svelte +3 -2
- package/src/lib/components/detailView/utils.ts +13 -0
- 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 +90 -19
- package/src/lib/components/polymorphicInput.svelte +1 -1
- package/src/lib/components/rangeCalendarButton.svelte +13 -13
- package/src/lib/components/richTextEditor.svelte +1 -1
- package/src/lib/components/routes/collections/collection.svelte +3 -3
- package/src/lib/components/routes/collections/collections.svelte +34 -12
- package/src/lib/components/routes/data_model/dataModel.svelte +6 -28
- package/src/lib/components/routes/extensions/extension.svelte +1 -1
- package/src/lib/components/routes/extensions/publicExtension.svelte +19 -0
- package/src/lib/components/routes/home.svelte +3 -3
- package/src/lib/components/routes/workflows/workflows.svelte +9 -9
- package/src/lib/components/selectRecord.svelte +2 -21
- package/src/lib/components/setServerPage.svelte +1 -1
- 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/alert-dialog/alert-dialog-action.svelte +1 -1
- package/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte +1 -1
- package/src/lib/components/ui/button/button.svelte +2 -3
- package/src/lib/components/ui/command/command-dialog.svelte +1 -1
- package/src/lib/components/ui/range-calendar/range-calendar-day.svelte +1 -1
- package/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte +1 -1
- package/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte +1 -1
- package/src/lib/components/ui/select/select-separator.svelte +1 -1
- package/src/lib/components/workflowEditor.svelte +5 -5
- 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 +2 -2
- package/src/lib/studioLifecycle.svelte.ts +17 -0
- package/vite-plugins/index.js +2 -4
- package/vite-plugins/utils.js +15 -0
- package/vite-plugins/{workspace-optimize.js → workspace-fs-allow.js} +4 -18
- 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
- package/vite-plugins/contextual-lib-alias.js +0 -67
package/README.md
CHANGED
package/dist/actions.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CreateDetailViewProp } from "./components/detailView/create/createDetailView.svelte";
|
|
2
2
|
import type { UpdateDetailViewProp } from "./components/detailView/update/updateDetailView.svelte";
|
|
3
3
|
import type { StudioContext } from "./context";
|
|
4
|
+
import type { CollectionTab } from "./store.types";
|
|
4
5
|
export interface OpenDataTableDrawerProps {
|
|
5
6
|
collectionName: string;
|
|
6
7
|
filter?: Record<string, any>;
|
|
@@ -8,6 +9,7 @@ export interface OpenDataTableDrawerProps {
|
|
|
8
9
|
showHeader?: boolean;
|
|
9
10
|
showFooter?: boolean;
|
|
10
11
|
position?: "side" | "bottom";
|
|
12
|
+
tabs?: CollectionTab[];
|
|
11
13
|
}
|
|
12
14
|
export declare function showDialog(title: string, description: string): Promise<boolean>;
|
|
13
15
|
export declare function openCreateDetailView(studioContext: StudioContext, props: CreateDetailViewProp): void;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Toaster } from "
|
|
3
|
-
import { onMount
|
|
2
|
+
import { Toaster } from "./ui/sonner";
|
|
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";
|
|
7
|
-
import Header from "
|
|
7
|
+
import Header from "./header.svelte";
|
|
8
8
|
import { LoaderCircle, ServerOff } from "lucide-svelte";
|
|
9
|
-
import MiniSidebar from "
|
|
10
|
-
import * as Tooltip from "
|
|
11
|
-
import {
|
|
9
|
+
import MiniSidebar from "./miniSidebar.svelte";
|
|
10
|
+
import * as Tooltip from "./ui/tooltip";
|
|
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,10 +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
|
|
50
|
+
// before JS loads to avoid a blank page, and is replaced by the Studio UI.
|
|
51
|
+
document.getElementById("app-loading")?.remove();
|
|
49
52
|
try {
|
|
50
53
|
ctx.meta = await lobb.getMeta();
|
|
51
54
|
ctx.extensions = await loadExtensions(lobb, ctx, extensionMap);
|
|
@@ -56,19 +59,13 @@
|
|
|
56
59
|
console.error(err);
|
|
57
60
|
status = "error";
|
|
58
61
|
}
|
|
59
|
-
|
|
60
|
-
// Fire onRouteChange hooks on every navigation
|
|
61
|
-
const onRouteChange = () => executeExtensionsOnRouteChange(lobb, ctx as any, window.location.pathname);
|
|
62
|
-
const originalPushState = history.pushState.bind(history);
|
|
63
|
-
history.pushState = function (...args) {
|
|
64
|
-
originalPushState(...args);
|
|
65
|
-
onRouteChange();
|
|
66
|
-
};
|
|
67
|
-
window.addEventListener("popstate", onRouteChange);
|
|
68
62
|
});
|
|
69
63
|
|
|
70
|
-
|
|
71
|
-
|
|
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);
|
|
72
69
|
});
|
|
73
70
|
</script>
|
|
74
71
|
|
|
@@ -91,6 +88,18 @@
|
|
|
91
88
|
<div class="text-xs">Could not connect to the lobb server at this endpoint ({ctx.lobbUrl})</div>
|
|
92
89
|
</div>
|
|
93
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>
|
|
94
103
|
{:else}
|
|
95
104
|
<Tooltip.Provider delayDuration={0} disableHoverableContent={true}>
|
|
96
105
|
<main
|
|
@@ -100,34 +109,24 @@
|
|
|
100
109
|
<MiniSidebar />
|
|
101
110
|
<div class="second_grid">
|
|
102
111
|
<Header />
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<Workflows workflowName={params?.workflow} />
|
|
122
|
-
{/snippet}
|
|
123
|
-
</Route>
|
|
124
|
-
<Route key="extensions" path="/extensions/:extension?/:page?/*">
|
|
125
|
-
{#snippet children(params)}
|
|
126
|
-
<Extension extension={params?.extension} page={params?.page} />
|
|
127
|
-
{/snippet}
|
|
128
|
-
</Route>
|
|
129
|
-
<Fallback>Not Found</Fallback>
|
|
130
|
-
</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}
|
|
131
130
|
</div>
|
|
132
131
|
</main>
|
|
133
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
|
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import CheckIcon from "@lucide/svelte/icons/check";
|
|
3
3
|
import ChevronsUpDownIcon from "@lucide/svelte/icons/chevrons-up-down";
|
|
4
4
|
import { tick } from "svelte";
|
|
5
|
-
import * as Command from "
|
|
6
|
-
import * as Popover from "
|
|
7
|
-
import { Button } from "
|
|
5
|
+
import * as Command from "./ui/command/index.js";
|
|
6
|
+
import * as Popover from "./ui/popover/index.js";
|
|
7
|
+
import { Button } from "./ui/button/index.js";
|
|
8
8
|
import { cn } from "../utils.js";
|
|
9
9
|
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
10
10
|
|
|
@@ -4,12 +4,6 @@
|
|
|
4
4
|
recordId: string | number;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export type RecordOperation =
|
|
8
|
-
| { type: "link"; record: any }
|
|
9
|
-
| { type: "unlink"; id: string | number }
|
|
10
|
-
| { type: "delete"; id: string | number }
|
|
11
|
-
| { type: "create"; record: any }
|
|
12
|
-
| { type: "update"; id: string | number; data: any };
|
|
13
7
|
</script>
|
|
14
8
|
|
|
15
9
|
<script lang="ts">
|
|
@@ -20,19 +14,21 @@
|
|
|
20
14
|
import Table, { type TableProps } from "./table.svelte";
|
|
21
15
|
import { getCollectionColumns, getCollectionParamsFields } from "./utils";
|
|
22
16
|
import { Pencil, Trash, Unlink } from "lucide-svelte";
|
|
23
|
-
import * as icons from "lucide-svelte";
|
|
24
17
|
import ListViewChildren from "./listViewChildren.svelte";
|
|
25
18
|
import FieldCell from "./fieldCell.svelte";
|
|
26
19
|
import Skeleton from "../ui/skeleton/skeleton.svelte";
|
|
27
20
|
import Button from "../ui/button/button.svelte";
|
|
28
21
|
import { showDialog } from "../../actions";
|
|
29
22
|
import UpdateDetailViewButton from "../detailView/update/updateDetailViewButton.svelte";
|
|
30
|
-
import { emitEvent } from "../../eventSystem";
|
|
31
23
|
import type { Snippet } from "svelte";
|
|
24
|
+
import type { Changes, ChildrenChanges } from "../detailView/utils";
|
|
32
25
|
import ExtensionsComponents from "../extensionsComponents.svelte";
|
|
33
26
|
import { getExtensionUtils, loadExtensionComponents } from "../../extensions/extensionUtils";
|
|
27
|
+
import { emitEvent } from "../../eventSystem";
|
|
28
|
+
import { onMount } from "svelte";
|
|
34
29
|
import Tabs from "./dataTableTabs.svelte";
|
|
35
30
|
import { fade } from "svelte/transition";
|
|
31
|
+
import type { CollectionTab } from "../../store.types";
|
|
36
32
|
|
|
37
33
|
const { lobb, ctx } = getStudioContext();
|
|
38
34
|
|
|
@@ -41,13 +37,13 @@
|
|
|
41
37
|
filter?: any;
|
|
42
38
|
searchParams?: Record<string, any>;
|
|
43
39
|
parentContext?: ParentContext;
|
|
44
|
-
|
|
40
|
+
changes?: ChildrenChanges;
|
|
45
41
|
showHeader?: boolean;
|
|
46
42
|
showFooter?: boolean;
|
|
47
43
|
showImport?: boolean;
|
|
48
|
-
unifiedBgColor?: "bg-muted/30" | "bg-background";
|
|
49
44
|
showDelete?: boolean;
|
|
50
45
|
tableProps?: Partial<TableProps>;
|
|
46
|
+
tabs?: CollectionTab[];
|
|
51
47
|
headerLeft?: Snippet<[]>;
|
|
52
48
|
}
|
|
53
49
|
|
|
@@ -56,16 +52,70 @@
|
|
|
56
52
|
filter,
|
|
57
53
|
searchParams,
|
|
58
54
|
parentContext,
|
|
59
|
-
|
|
55
|
+
changes = $bindable<ChildrenChanges | undefined>(undefined),
|
|
60
56
|
showHeader = true,
|
|
61
57
|
showFooter = true,
|
|
62
58
|
showImport = true,
|
|
63
|
-
unifiedBgColor,
|
|
64
59
|
showDelete = false,
|
|
65
60
|
tableProps,
|
|
61
|
+
tabs,
|
|
66
62
|
headerLeft,
|
|
67
63
|
}: Props = $props();
|
|
68
64
|
|
|
65
|
+
// Gate row/header buttons by the current user's permissions:
|
|
66
|
+
// - showUpdate → per-row edit button
|
|
67
|
+
// - showCreate → header's Create + Import buttons (passed to Header)
|
|
68
|
+
let showUpdate = $state(false);
|
|
69
|
+
let showCreate = $state(false);
|
|
70
|
+
onMount(async () => {
|
|
71
|
+
const [update, create] = await Promise.all([
|
|
72
|
+
emitEvent({ lobb, ctx }, "auth.canAccess", { collection: collectionName, action: "update" }),
|
|
73
|
+
emitEvent({ lobb, ctx }, "auth.canAccess", { collection: collectionName, action: "create" }),
|
|
74
|
+
]);
|
|
75
|
+
showUpdate = update === true;
|
|
76
|
+
showCreate = create === true;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
function getOrCreateUpdatedSlot(recordId: string): Changes | undefined {
|
|
80
|
+
if (!changes) return undefined;
|
|
81
|
+
let slot = changes.updated.find((u) => String(u.id) === String(recordId));
|
|
82
|
+
if (!slot) {
|
|
83
|
+
slot = { id: recordId, data: {}, children: {} };
|
|
84
|
+
changes.updated.push(slot);
|
|
85
|
+
}
|
|
86
|
+
return slot;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Derives the displayed rows by applying changes on top of server data.
|
|
90
|
+
// This is the single place responsible for optimistic UI — no handler touches data directly.
|
|
91
|
+
const data = $derived.by(() => {
|
|
92
|
+
if (!changes) return serverData;
|
|
93
|
+
|
|
94
|
+
const removedIds = new Set([
|
|
95
|
+
...changes.deleted.map((r) => String(r.id)),
|
|
96
|
+
...changes.unlinked.map((r) => String(r.id)),
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
let result = serverData.filter((r: any) => !removedIds.has(String(r.id)));
|
|
100
|
+
|
|
101
|
+
result = result.map((r: any) => {
|
|
102
|
+
const update = changes.updated.find((u) => String(u.id) === String(r.id));
|
|
103
|
+
return update && Object.keys(update.data).length ? { ...r, ...update.data } : r;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
for (const record of changes.linked) {
|
|
107
|
+
if (!result.some((r: any) => String(r.id) === String(record.id))) {
|
|
108
|
+
result = [...result, record];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const item of changes.created) {
|
|
113
|
+
result = [...result, { ...item.data, _pending: true }];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return result;
|
|
117
|
+
});
|
|
118
|
+
|
|
69
119
|
const hasRowActions = $derived(
|
|
70
120
|
loadExtensionComponents(ctx, "listView.entry.actions", undefined, { collectionName }).length > 0
|
|
71
121
|
);
|
|
@@ -89,7 +139,7 @@
|
|
|
89
139
|
|
|
90
140
|
let selectedRecords = $state([]);
|
|
91
141
|
let totalCount = $state(0);
|
|
92
|
-
let
|
|
142
|
+
let serverData: TableProps["data"] = $state([]);
|
|
93
143
|
let loading = $state(true);
|
|
94
144
|
const columns: TableProps["columns"] = $state(
|
|
95
145
|
getCollectionColumns(ctx, collectionName),
|
|
@@ -108,7 +158,6 @@
|
|
|
108
158
|
|
|
109
159
|
async function loadData(params: any) {
|
|
110
160
|
loading = true;
|
|
111
|
-
// parsing sort before sending the request
|
|
112
161
|
const paramsCopy = $state.snapshot(params);
|
|
113
162
|
const sort: TableProps["sort"] = paramsCopy.sort;
|
|
114
163
|
const sortStrings: string[] = [];
|
|
@@ -118,85 +167,48 @@
|
|
|
118
167
|
}
|
|
119
168
|
}
|
|
120
169
|
paramsCopy.sort = sortStrings.join(",");
|
|
121
|
-
|
|
122
|
-
// sending the request
|
|
123
170
|
const response = await lobb.findAll(collectionName, paramsCopy);
|
|
124
171
|
const res = await response.json();
|
|
125
|
-
|
|
126
|
-
data = res.data;
|
|
172
|
+
serverData = res.data;
|
|
127
173
|
totalCount = res.meta.totalCount;
|
|
128
|
-
|
|
129
174
|
loading = false;
|
|
130
175
|
}
|
|
131
176
|
|
|
132
|
-
// Internal handler: updates data optimistically then calls onOperation
|
|
133
|
-
function applyOperation(op: RecordOperation) {
|
|
134
|
-
if (op.type === "link") {
|
|
135
|
-
data = [...data, op.record];
|
|
136
|
-
} else if (op.type === "unlink" || op.type === "delete") {
|
|
137
|
-
data = data.filter((r: any) => String(r.id) !== String(op.id));
|
|
138
|
-
} else if (op.type === "create") {
|
|
139
|
-
data = [...data, { ...op.record, _pending: true }];
|
|
140
|
-
} else if (op.type === "update") {
|
|
141
|
-
data = data.map((r: any) => String(r.id) === String(op.id) ? { ...r, ...op.data } : r);
|
|
142
|
-
}
|
|
143
|
-
onOperation?.(op);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
177
|
async function handleDelete(entryId: string) {
|
|
147
178
|
const result = await showDialog("Are you sure?", "This will permanently delete the record.");
|
|
148
|
-
if (result)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
179
|
+
if (!result) return;
|
|
180
|
+
if (changes) {
|
|
181
|
+
const record = data.find((r: any) => String(r.id) === String(entryId));
|
|
182
|
+
if (record) changes.deleted.push($state.snapshot(record));
|
|
183
|
+
} else if (parentContext) {
|
|
184
|
+
serverData = serverData.filter((r: any) => String(r.id) !== String(entryId));
|
|
185
|
+
await lobb.updateOne(parentContext.collectionName, String(parentContext.recordId), {}, { [collectionName]: { delete: [entryId] } });
|
|
186
|
+
params = { ...params };
|
|
187
|
+
} else {
|
|
188
|
+
serverData = serverData.filter((r: any) => String(r.id) !== String(entryId));
|
|
189
|
+
await lobb.deleteOne(collectionName, entryId);
|
|
190
|
+
params = { ...params };
|
|
158
191
|
}
|
|
159
192
|
}
|
|
160
193
|
|
|
161
194
|
async function handleUnlink(entryId: string) {
|
|
162
195
|
const result = await showDialog("Are you sure?", "This will unlink the record without deleting it.");
|
|
163
|
-
if (result)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
196
|
+
if (!result) return;
|
|
197
|
+
if (changes) {
|
|
198
|
+
const record = data.find((r: any) => String(r.id) === String(entryId));
|
|
199
|
+
if (record) changes.unlinked.push($state.snapshot(record));
|
|
200
|
+
} else {
|
|
201
|
+
serverData = serverData.filter((r: any) => String(r.id) !== String(entryId));
|
|
202
|
+
await lobb.updateOne(parentContext!.collectionName, String(parentContext!.recordId), {}, { [collectionName]: { unlink: [entryId] } });
|
|
203
|
+
params = { ...params };
|
|
170
204
|
}
|
|
171
205
|
}
|
|
172
206
|
|
|
173
|
-
async function getWorkflowTools(
|
|
174
|
-
entry: Record<string, any>,
|
|
175
|
-
): Promise<any[]> {
|
|
176
|
-
// TODO: instead of firing the events like this. get them all the fire them one by one to get their results
|
|
177
|
-
const eventResult = await emitEvent(
|
|
178
|
-
{ lobb, ctx },
|
|
179
|
-
"studio.collections.listView.tools",
|
|
180
|
-
{
|
|
181
|
-
collectionName,
|
|
182
|
-
entry,
|
|
183
|
-
},
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
if (eventResult) {
|
|
187
|
-
return eventResult.tools;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return [];
|
|
191
|
-
}
|
|
192
207
|
</script>
|
|
193
208
|
|
|
194
209
|
<div
|
|
195
210
|
bind:clientWidth={dataTableContainerWidth}
|
|
196
|
-
class="
|
|
197
|
-
flex flex-col overflow-auto h-full w-full
|
|
198
|
-
{unifiedBgColor ? unifiedBgColor : ''}
|
|
199
|
-
"
|
|
211
|
+
class="flex flex-col overflow-auto h-full w-full"
|
|
200
212
|
>
|
|
201
213
|
{#snippet rowActionsSnippet(entry: Record<string, any>)}
|
|
202
214
|
<ExtensionsComponents
|
|
@@ -209,13 +221,21 @@
|
|
|
209
221
|
{/snippet}
|
|
210
222
|
|
|
211
223
|
{#if showHeader}
|
|
212
|
-
<Header
|
|
224
|
+
<Header
|
|
225
|
+
bind:params
|
|
226
|
+
{collectionName}
|
|
227
|
+
bind:selectedRecords
|
|
228
|
+
{showImport}
|
|
229
|
+
{showCreate}
|
|
230
|
+
{parentContext}
|
|
231
|
+
{changes}
|
|
232
|
+
>
|
|
213
233
|
{#snippet left()}
|
|
214
234
|
{@render headerLeft?.()}
|
|
215
235
|
{/snippet}
|
|
216
236
|
</Header>
|
|
217
237
|
{/if}
|
|
218
|
-
<Tabs {collectionName} {filter} bind:activeTabFilter />
|
|
238
|
+
<Tabs {collectionName} {filter} {tabs} bind:activeTabFilter />
|
|
219
239
|
<div class="relative flex-1 overflow-auto w-full">
|
|
220
240
|
{#key activeTabFilter}
|
|
221
241
|
<div class="h-full w-full" in:fade={{ duration: 120 }}>
|
|
@@ -235,21 +255,23 @@
|
|
|
235
255
|
showLastColumnBorder={true}
|
|
236
256
|
bind:sort={params.sort}
|
|
237
257
|
bind:selectedRecords
|
|
238
|
-
{unifiedBgColor}
|
|
239
258
|
bind:tableWidth={dataTableWidth}
|
|
240
259
|
{...tableProps}
|
|
241
260
|
rowActions={hasRowActions ? rowActionsSnippet : undefined}>
|
|
242
261
|
{#snippet tools(entry)}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
262
|
+
{#if showUpdate}
|
|
263
|
+
<UpdateDetailViewButton
|
|
264
|
+
{collectionName}
|
|
265
|
+
recordId={entry.id}
|
|
266
|
+
variant="ghost"
|
|
267
|
+
class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
|
|
268
|
+
Icon={Pencil}
|
|
269
|
+
changes={getOrCreateUpdatedSlot(String(entry.id))}
|
|
270
|
+
onSuccessfullSave={async () => {
|
|
271
|
+
params = { ...params };
|
|
272
|
+
}}
|
|
273
|
+
></UpdateDetailViewButton>
|
|
274
|
+
{/if}
|
|
253
275
|
{#if parentContext}
|
|
254
276
|
<Button
|
|
255
277
|
class="h-6 w-6 text-muted-foreground hover:bg-transparent"
|
|
@@ -270,20 +292,6 @@
|
|
|
270
292
|
title="Delete permanently"
|
|
271
293
|
></Button>
|
|
272
294
|
{/if}
|
|
273
|
-
{#await getWorkflowTools($state.snapshot(entry))}
|
|
274
|
-
<div></div>
|
|
275
|
-
{:then workflowTools}
|
|
276
|
-
{#each workflowTools as workflowTool}
|
|
277
|
-
<Button
|
|
278
|
-
variant="ghost"
|
|
279
|
-
class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
|
|
280
|
-
Icon={icons[
|
|
281
|
-
workflowTool.icon as keyof typeof icons
|
|
282
|
-
]}
|
|
283
|
-
onclick={workflowTool.onclick}
|
|
284
|
-
></Button>
|
|
285
|
-
{/each}
|
|
286
|
-
{/await}
|
|
287
295
|
{/snippet}
|
|
288
296
|
{#snippet overrideCell(value, column, entry)}
|
|
289
297
|
<FieldCell
|
|
@@ -301,7 +309,6 @@
|
|
|
301
309
|
width={dataTableWidth > dataTableContainerWidth
|
|
302
310
|
? dataTableContainerWidth
|
|
303
311
|
: dataTableWidth}
|
|
304
|
-
unifiedBgColor={unifiedBgColor ?? "bg-background"}
|
|
305
312
|
/>
|
|
306
313
|
{/snippet}
|
|
307
314
|
</Table>
|
|
@@ -2,39 +2,24 @@ export interface ParentContext {
|
|
|
2
2
|
collectionName: string;
|
|
3
3
|
recordId: string | number;
|
|
4
4
|
}
|
|
5
|
-
export type RecordOperation = {
|
|
6
|
-
type: "link";
|
|
7
|
-
record: any;
|
|
8
|
-
} | {
|
|
9
|
-
type: "unlink";
|
|
10
|
-
id: string | number;
|
|
11
|
-
} | {
|
|
12
|
-
type: "delete";
|
|
13
|
-
id: string | number;
|
|
14
|
-
} | {
|
|
15
|
-
type: "create";
|
|
16
|
-
record: any;
|
|
17
|
-
} | {
|
|
18
|
-
type: "update";
|
|
19
|
-
id: string | number;
|
|
20
|
-
data: any;
|
|
21
|
-
};
|
|
22
5
|
import { type TableProps } from "./table.svelte";
|
|
23
6
|
import type { Snippet } from "svelte";
|
|
7
|
+
import type { ChildrenChanges } from "../detailView/utils";
|
|
8
|
+
import type { CollectionTab } from "../../store.types";
|
|
24
9
|
interface Props {
|
|
25
10
|
collectionName: string;
|
|
26
11
|
filter?: any;
|
|
27
12
|
searchParams?: Record<string, any>;
|
|
28
13
|
parentContext?: ParentContext;
|
|
29
|
-
|
|
14
|
+
changes?: ChildrenChanges;
|
|
30
15
|
showHeader?: boolean;
|
|
31
16
|
showFooter?: boolean;
|
|
32
17
|
showImport?: boolean;
|
|
33
|
-
unifiedBgColor?: "bg-muted/30" | "bg-background";
|
|
34
18
|
showDelete?: boolean;
|
|
35
19
|
tableProps?: Partial<TableProps>;
|
|
20
|
+
tabs?: CollectionTab[];
|
|
36
21
|
headerLeft?: Snippet<[]>;
|
|
37
22
|
}
|
|
38
|
-
declare const DataTable: import("svelte").Component<Props, {}, "">;
|
|
23
|
+
declare const DataTable: import("svelte").Component<Props, {}, "changes">;
|
|
39
24
|
type DataTable = ReturnType<typeof DataTable>;
|
|
40
25
|
export default DataTable;
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getStudioContext } from "../../context";
|
|
3
|
+
import type { CollectionTab } from "../../store.types";
|
|
3
4
|
|
|
4
5
|
const { lobb, ctx } = getStudioContext();
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
collectionName: string;
|
|
8
9
|
filter?: any;
|
|
10
|
+
tabs?: CollectionTab[];
|
|
9
11
|
activeTabFilter?: any;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
let { collectionName, filter, activeTabFilter = $bindable() }: Props = $props();
|
|
14
|
+
let { collectionName, filter, tabs: tabsProp, activeTabFilter = $bindable() }: Props = $props();
|
|
13
15
|
|
|
14
|
-
const tabs = ctx.meta.collections[collectionName].ui?.tabs;
|
|
16
|
+
const tabs: CollectionTab[] | undefined = $derived(tabsProp ?? ctx.meta.collections[collectionName].ui?.tabs);
|
|
15
17
|
let activeTab = $state<string | null>(null);
|
|
16
18
|
let tabCounts = $state<Record<string, number>>({});
|
|
17
19
|
|