@lobb-js/studio 0.31.0 → 0.33.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/actions.d.ts +4 -0
- package/dist/applyStudioTheme.d.ts +2 -0
- package/dist/applyStudioTheme.js +36 -0
- package/dist/components/Studio.svelte +2 -0
- package/dist/components/canAccess.svelte +52 -0
- package/dist/components/canAccess.svelte.d.ts +10 -0
- package/dist/components/dataTable/dataTable.svelte +124 -58
- package/dist/components/dataTable/dataTable.svelte.d.ts +8 -1
- package/dist/components/dataTable/fieldCell.svelte +4 -4
- package/dist/components/dataTable/fieldCell.svelte.d.ts +2 -2
- package/dist/components/dataTable/header.svelte +33 -33
- package/dist/components/dataTable/header.svelte.d.ts +3 -3
- package/dist/components/dataTable/polymorphicFieldCell.svelte +3 -3
- package/dist/components/dataTable/polymorphicFieldCell.svelte.d.ts +2 -2
- package/dist/components/dataTablePopup/dataTablePopup.svelte +3 -0
- package/dist/components/dataTablePopup/dataTablePopup.svelte.d.ts +4 -0
- package/dist/components/detailView/create/createDetailView.svelte +28 -54
- package/dist/components/detailView/create/createDetailView.svelte.d.ts +4 -3
- package/dist/components/detailView/create/createDetailViewChildren.svelte +113 -0
- package/dist/components/detailView/create/createDetailViewChildren.svelte.d.ts +9 -0
- package/dist/components/detailView/create/createManyView.svelte +2 -2
- package/dist/components/detailView/update/updateDetailView.svelte +46 -40
- package/dist/components/detailView/update/updateDetailView.svelte.d.ts +5 -3
- package/dist/components/detailView/update/updateDetailViewButton.svelte +0 -1
- package/dist/components/detailView/update/updateDetailViewChildren.svelte +122 -0
- package/dist/components/detailView/update/updateDetailViewChildren.svelte.d.ts +10 -0
- package/dist/components/detailView/utils.d.ts +1 -2
- package/dist/components/importButton.svelte +1 -1
- package/dist/components/richTextEditor.svelte +2 -0
- package/dist/components/workflowEditor.svelte +6 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/store.types.d.ts +7 -0
- package/package.json +3 -3
- package/src/lib/actions.ts +1 -0
- package/src/lib/applyStudioTheme.ts +38 -0
- package/src/lib/components/Studio.svelte +2 -0
- package/src/lib/components/canAccess.svelte +52 -0
- package/src/lib/components/dataTable/dataTable.svelte +124 -58
- package/src/lib/components/dataTable/fieldCell.svelte +4 -4
- package/src/lib/components/dataTable/header.svelte +33 -33
- package/src/lib/components/dataTable/polymorphicFieldCell.svelte +3 -3
- package/src/lib/components/dataTablePopup/dataTablePopup.svelte +3 -0
- package/src/lib/components/detailView/create/createDetailView.svelte +28 -54
- package/src/lib/components/detailView/create/createDetailViewChildren.svelte +113 -0
- package/src/lib/components/detailView/create/createManyView.svelte +2 -2
- package/src/lib/components/detailView/update/updateDetailView.svelte +46 -40
- package/src/lib/components/detailView/update/updateDetailViewButton.svelte +0 -1
- package/src/lib/components/detailView/update/updateDetailViewChildren.svelte +122 -0
- package/src/lib/components/detailView/utils.ts +1 -1
- package/src/lib/components/importButton.svelte +1 -1
- package/src/lib/components/richTextEditor.svelte +2 -0
- package/src/lib/components/workflowEditor.svelte +6 -4
- package/src/lib/index.ts +2 -0
- package/src/lib/store.types.ts +6 -0
- package/dist/components/detailView/update/detailViewChildren.svelte +0 -61
- package/dist/components/detailView/update/detailViewChildren.svelte.d.ts +0 -9
- package/src/lib/components/detailView/update/detailViewChildren.svelte +0 -61
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import DataTable from "../../dataTable/dataTable.svelte";
|
|
3
|
+
import { getStudioContext } from "../../../context";
|
|
4
|
+
import { Table, Link, Plus } from "lucide-svelte";
|
|
5
|
+
import { untrack } from "svelte";
|
|
6
|
+
import CreateDetailViewButton from "../create/createDetailViewButton.svelte";
|
|
7
|
+
import SelectRecord from "../../selectRecord.svelte";
|
|
8
|
+
|
|
9
|
+
const { ctx } = getStudioContext();
|
|
10
|
+
|
|
11
|
+
import type { Changes, ChildrenChanges } from "../utils";
|
|
12
|
+
|
|
13
|
+
interface LocalProp {
|
|
14
|
+
collectionName: string;
|
|
15
|
+
entry: any;
|
|
16
|
+
changes?: Changes;
|
|
17
|
+
onChanges?: (children: Changes["children"]) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let { collectionName, entry, changes, onChanges }: LocalProp = $props();
|
|
21
|
+
|
|
22
|
+
const children = (ctx.meta.collections[collectionName]?.children ?? [])
|
|
23
|
+
.filter((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic");
|
|
24
|
+
|
|
25
|
+
let localChildren = $state<Changes["children"]>(untrack(() => changes?.children ?? {}));
|
|
26
|
+
let serverCounts = $state<Record<string, number | undefined>>({});
|
|
27
|
+
|
|
28
|
+
function handleChildChanges(collection: string, updated: ChildrenChanges) {
|
|
29
|
+
localChildren[collection] = updated;
|
|
30
|
+
onChanges?.($state.snapshot(localChildren));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function handleDataLoad(collection: string, total: number) {
|
|
34
|
+
serverCounts[collection] = total;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function handleEmptyCreate(collection: string, c: Changes) {
|
|
38
|
+
if (!localChildren[collection]) {
|
|
39
|
+
localChildren[collection] = { created: [], updated: [], deleted: [], linked: [], unlinked: [] };
|
|
40
|
+
}
|
|
41
|
+
localChildren[collection].created.push({ data: c.data });
|
|
42
|
+
onChanges?.($state.snapshot(localChildren));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function handleEmptyLink(collection: string, record: any) {
|
|
46
|
+
if (!localChildren[collection]) {
|
|
47
|
+
localChildren[collection] = { created: [], updated: [], deleted: [], linked: [], unlinked: [] };
|
|
48
|
+
}
|
|
49
|
+
localChildren[collection].linked.push(record);
|
|
50
|
+
onChanges?.($state.snapshot(localChildren));
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
{#if children.length}
|
|
55
|
+
<div class="flex flex-col gap-3 border-t p-4">
|
|
56
|
+
<div class="flex items-center gap-2">
|
|
57
|
+
<Link size="14" class="text-muted-foreground" />
|
|
58
|
+
<span class="text-sm font-medium">Sub Records</span>
|
|
59
|
+
</div>
|
|
60
|
+
{#each children as child}
|
|
61
|
+
{@const serverCount = serverCounts[child.collection]}
|
|
62
|
+
{@const localAdditions = (localChildren[child.collection]?.created.length ?? 0) + (localChildren[child.collection]?.linked.length ?? 0)}
|
|
63
|
+
{@const showEmpty = serverCount !== undefined && serverCount === 0 && localAdditions === 0}
|
|
64
|
+
{#if showEmpty}
|
|
65
|
+
<div class="rounded-lg border bg-muted-soft overflow-hidden flex flex-col">
|
|
66
|
+
<div class="flex flex-col items-center justify-center gap-3 py-6 px-4">
|
|
67
|
+
<div class="flex flex-col items-center gap-2 text-center">
|
|
68
|
+
<div class="flex items-center gap-1.5 text-sm font-medium text-muted-foreground">
|
|
69
|
+
<span>No records in</span>
|
|
70
|
+
<span class="rounded-md border bg-muted px-2 py-0.5 text-xs font-normal">{child.collection}</span>
|
|
71
|
+
</div>
|
|
72
|
+
<span class="text-xs text-muted-foreground/70">Create a new record or link an existing one.</span>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="flex gap-2">
|
|
75
|
+
<SelectRecord
|
|
76
|
+
collectionName={child.collection}
|
|
77
|
+
variant="outline"
|
|
78
|
+
class="h-7 px-3 text-xs font-normal"
|
|
79
|
+
Icon={Link}
|
|
80
|
+
onSelect={(r) => handleEmptyLink(child.collection, r)}
|
|
81
|
+
>
|
|
82
|
+
Link
|
|
83
|
+
</SelectRecord>
|
|
84
|
+
<CreateDetailViewButton
|
|
85
|
+
collectionName={child.collection}
|
|
86
|
+
variant="default"
|
|
87
|
+
class="h-7 px-3 text-xs font-normal"
|
|
88
|
+
Icon={Plus}
|
|
89
|
+
onChanges={(c) => handleEmptyCreate(child.collection, c)}
|
|
90
|
+
>
|
|
91
|
+
Create
|
|
92
|
+
</CreateDetailViewButton>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
{:else}
|
|
97
|
+
<div class="rounded-lg border bg-background overflow-hidden flex flex-col max-h-96">
|
|
98
|
+
<DataTable
|
|
99
|
+
collectionName={child.collection}
|
|
100
|
+
searchParams={{ children_of: collectionName, parent_id: entry.id }}
|
|
101
|
+
parentContext={{ collectionName, recordId: entry.id }}
|
|
102
|
+
onChanges={(updated) => handleChildChanges(child.collection, updated)}
|
|
103
|
+
changes={localChildren[child.collection]}
|
|
104
|
+
showImport={false}
|
|
105
|
+
showHeader={true}
|
|
106
|
+
showFooter={true}
|
|
107
|
+
showDelete={child.type === "fk" || child.type === "m2m"}
|
|
108
|
+
tableProps={{ showLastColumnBorder: false, showLastRowBorder: true }}
|
|
109
|
+
onDataLoad={(total) => handleDataLoad(child.collection, total)}
|
|
110
|
+
>
|
|
111
|
+
{#snippet headerLeft()}
|
|
112
|
+
<div class="flex items-center gap-2 px-1">
|
|
113
|
+
<Table size="14" class="text-muted-foreground" />
|
|
114
|
+
<span class="text-sm font-medium">{child.collection}</span>
|
|
115
|
+
</div>
|
|
116
|
+
{/snippet}
|
|
117
|
+
</DataTable>
|
|
118
|
+
</div>
|
|
119
|
+
{/if}
|
|
120
|
+
{/each}
|
|
121
|
+
</div>
|
|
122
|
+
{/if}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Changes } from "../utils";
|
|
2
|
+
interface LocalProp {
|
|
3
|
+
collectionName: string;
|
|
4
|
+
entry: any;
|
|
5
|
+
changes?: Changes;
|
|
6
|
+
onChanges?: (children: Changes["children"]) => void;
|
|
7
|
+
}
|
|
8
|
+
declare const UpdateDetailViewChildren: import("svelte").Component<LocalProp, {}, "">;
|
|
9
|
+
type UpdateDetailViewChildren = ReturnType<typeof UpdateDetailViewChildren>;
|
|
10
|
+
export default UpdateDetailViewChildren;
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
importResults = [];
|
|
133
133
|
let hasSuccess = false;
|
|
134
134
|
for (const row of finalRows) {
|
|
135
|
-
const response = await lobb.createOne(collectionName, row);
|
|
135
|
+
const response = await lobb.createOne(collectionName, { data: row });
|
|
136
136
|
if (response.ok) {
|
|
137
137
|
importResults.push({ row, error: null });
|
|
138
138
|
hasSuccess = true;
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
const reponse = await lobb.createOne("core_workflows", workflow);
|
|
96
|
+
const reponse = await lobb.createOne("core_workflows", { data: workflow });
|
|
97
97
|
const result = await reponse.json();
|
|
98
98
|
const workflowEntry = result.data;
|
|
99
99
|
goto(`/studio/workflows/${workflowEntry.name}`);
|
|
@@ -107,9 +107,11 @@
|
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
109
|
const reponse = await lobb.updateOne("core_workflows", workflow.id, {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
data: {
|
|
111
|
+
name: workflow.name,
|
|
112
|
+
event_name: workflow.event_name,
|
|
113
|
+
handler: workflow.handler,
|
|
114
|
+
},
|
|
113
115
|
});
|
|
114
116
|
const result = await reponse.json();
|
|
115
117
|
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export { default as SidebarTrigger } from "./components/sidebar/sidebarTrigger.s
|
|
|
12
12
|
export { default as CreateDetailViewButton } from "./components/detailView/create/createDetailViewButton.svelte";
|
|
13
13
|
export { default as UpdateDetailViewButton } from "./components/detailView/update/updateDetailViewButton.svelte";
|
|
14
14
|
export { default as DetailView } from "./components/detailView/detailView.svelte";
|
|
15
|
+
export { default as CanAccess } from "./components/canAccess.svelte";
|
|
16
|
+
export { default as ExtensionsComponents } from "./components/extensionsComponents.svelte";
|
|
15
17
|
export * as Tooltip from "./components/ui/tooltip";
|
|
16
18
|
export * as Breadcrumb from "./components/ui/breadcrumb";
|
|
17
19
|
export { ContextMenu } from "bits-ui";
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,8 @@ export { default as SidebarTrigger } from "./components/sidebar/sidebarTrigger.s
|
|
|
11
11
|
export { default as CreateDetailViewButton } from "./components/detailView/create/createDetailViewButton.svelte";
|
|
12
12
|
export { default as UpdateDetailViewButton } from "./components/detailView/update/updateDetailViewButton.svelte";
|
|
13
13
|
export { default as DetailView } from "./components/detailView/detailView.svelte";
|
|
14
|
+
export { default as CanAccess } from "./components/canAccess.svelte";
|
|
15
|
+
export { default as ExtensionsComponents } from "./components/extensionsComponents.svelte";
|
|
14
16
|
export * as Tooltip from "./components/ui/tooltip";
|
|
15
17
|
export * as Breadcrumb from "./components/ui/breadcrumb";
|
|
16
18
|
export { ContextMenu } from "bits-ui";
|
package/dist/store.types.d.ts
CHANGED
|
@@ -30,8 +30,15 @@ interface Collection {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
type Collections = Record<string, Collection>;
|
|
33
|
+
export interface StudioTheme {
|
|
34
|
+
light?: Record<string, string>;
|
|
35
|
+
dark?: Record<string, string>;
|
|
36
|
+
}
|
|
33
37
|
interface Meta {
|
|
34
38
|
version: string;
|
|
39
|
+
studio?: {
|
|
40
|
+
theme?: StudioTheme;
|
|
41
|
+
};
|
|
35
42
|
relations: Array<any>;
|
|
36
43
|
collections: Collections;
|
|
37
44
|
extensions: Record<string, any>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobb-js/studio",
|
|
3
3
|
"license": "UNLICENSED",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.33.0",
|
|
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.34.0",
|
|
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.
|
|
90
|
+
"@lobb-js/sdk": "^0.3.0",
|
|
91
91
|
"@lucide/svelte": "^0.563.1",
|
|
92
92
|
"@tailwindcss/vite": "^4.3.0",
|
|
93
93
|
"@tiptap/core": "^3.0.0",
|
package/src/lib/actions.ts
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { StudioTheme } from "./store.types";
|
|
2
|
+
|
|
3
|
+
// Studio theme injection. Writes the configured CSS-variable overrides
|
|
4
|
+
// into a single <style> tag — light overrides under `:root`, dark under
|
|
5
|
+
// `.dark` — so each mode picks up its own variant. Idempotent: re-runs
|
|
6
|
+
// replace the previous block.
|
|
7
|
+
|
|
8
|
+
const STYLE_ID = "lobb-studio-theme";
|
|
9
|
+
|
|
10
|
+
function buildDeclarations(vars: Record<string, string> | undefined): string {
|
|
11
|
+
if (!vars) return "";
|
|
12
|
+
const out: string[] = [];
|
|
13
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
14
|
+
if (!key.startsWith("--") || !value) continue;
|
|
15
|
+
out.push(`${key}: ${value};`);
|
|
16
|
+
}
|
|
17
|
+
return out.join(" ");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function applyStudioTheme(theme: StudioTheme | undefined): void {
|
|
21
|
+
if (typeof document === "undefined") return;
|
|
22
|
+
|
|
23
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
24
|
+
if (!theme) return;
|
|
25
|
+
|
|
26
|
+
const light = buildDeclarations(theme.light);
|
|
27
|
+
const dark = buildDeclarations(theme.dark);
|
|
28
|
+
if (!light && !dark) return;
|
|
29
|
+
|
|
30
|
+
const rules: string[] = [];
|
|
31
|
+
if (light) rules.push(`:root { ${light} }`);
|
|
32
|
+
if (dark) rules.push(`.dark { ${dark} }`);
|
|
33
|
+
|
|
34
|
+
const style = document.createElement("style");
|
|
35
|
+
style.id = STYLE_ID;
|
|
36
|
+
style.textContent = rules.join(" ");
|
|
37
|
+
document.head.appendChild(style);
|
|
38
|
+
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
} from "../extensions/extensionUtils";
|
|
18
18
|
import extensionMap from 'virtual:lobb-studio-extensions';
|
|
19
19
|
import { mediaQueries } from "../utils";
|
|
20
|
+
import { applyStudioTheme } from "../applyStudioTheme";
|
|
20
21
|
import Home from "./routes/home.svelte";
|
|
21
22
|
import DataModel from "./routes/data_model/dataModel.svelte";
|
|
22
23
|
import Collections from "./routes/collections/collections.svelte";
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
document.getElementById("app-loading")?.remove();
|
|
51
52
|
try {
|
|
52
53
|
ctx.meta = await lobb.getMeta();
|
|
54
|
+
applyStudioTheme(ctx.meta.studio?.theme);
|
|
53
55
|
ctx.extensions = await loadExtensions(lobb, ctx, extensionMap);
|
|
54
56
|
await executeExtensionsOnStartup(lobb, ctx);
|
|
55
57
|
loadExtensionWorkflows(ctx as any);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// Declarative permission gate. Replaces the recurring pattern of
|
|
3
|
+
// `let canX = $state(false); onMount(async () => canX = await emitEvent
|
|
4
|
+
// ("auth.canAccess", { collection, action }) === true);` plus an `{#if canX}`.
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// <CanAccess collection="risks" action="update">
|
|
8
|
+
// <EditButton ... />
|
|
9
|
+
// </CanAccess>
|
|
10
|
+
//
|
|
11
|
+
// Optional `fallback` snippet renders when the user is NOT allowed
|
|
12
|
+
// (defaults to nothing). The brief in-flight window before the answer
|
|
13
|
+
// is known renders nothing so we don't flash unauthorized content.
|
|
14
|
+
import type { Snippet } from "svelte";
|
|
15
|
+
import { onMount } from "svelte";
|
|
16
|
+
import { getStudioContext } from "../context";
|
|
17
|
+
import { emitEvent } from "../eventSystem";
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
collection: string;
|
|
21
|
+
action: "read" | "create" | "update" | "delete" | string;
|
|
22
|
+
children: Snippet;
|
|
23
|
+
fallback?: Snippet;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { collection, action, children, fallback }: Props = $props();
|
|
27
|
+
const { lobb, ctx } = getStudioContext();
|
|
28
|
+
|
|
29
|
+
// null = "haven't checked yet" — distinguished from false so the fallback
|
|
30
|
+
// doesn't flash during the async resolution.
|
|
31
|
+
let allowed = $state<boolean | null>(null);
|
|
32
|
+
|
|
33
|
+
onMount(async () => {
|
|
34
|
+
try {
|
|
35
|
+
const result = await emitEvent({ lobb, ctx }, "auth.canAccess", {
|
|
36
|
+
collection,
|
|
37
|
+
action,
|
|
38
|
+
});
|
|
39
|
+
allowed = result === true;
|
|
40
|
+
} catch {
|
|
41
|
+
// No handler registered (auth extension not loaded), or the
|
|
42
|
+
// handler threw — fail closed.
|
|
43
|
+
allowed = false;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
{#if allowed === true}
|
|
49
|
+
{@render children()}
|
|
50
|
+
{:else if allowed === false && fallback}
|
|
51
|
+
{@render fallback()}
|
|
52
|
+
{/if}
|