@lobb-js/studio 0.25.0 → 0.27.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/components/dataTable/dataTable.svelte +77 -14
- package/dist/components/dataTable/dataTable.svelte.d.ts +25 -0
- package/dist/components/dataTable/header.svelte +88 -24
- package/dist/components/dataTable/header.svelte.d.ts +4 -0
- package/dist/components/dataTable/listViewChildren.svelte +106 -0
- package/dist/components/dataTable/listViewChildren.svelte.d.ts +9 -0
- package/dist/components/dataTable/table.svelte +1 -1
- package/dist/components/detailView/create/createManyView.svelte +2 -2
- package/dist/components/detailView/update/detailViewChildren.svelte +72 -0
- package/dist/components/detailView/update/detailViewChildren.svelte.d.ts +14 -0
- package/dist/components/detailView/update/updateDetailView.svelte +6 -3
- package/dist/components/routes/collections/collections.svelte +44 -23
- package/dist/components/routes/data_model/dataModel.svelte +2 -8
- package/dist/components/routes/workflows/workflows.svelte +24 -11
- package/dist/components/sidebar/sidebar.svelte +12 -5
- package/dist/components/sidebar/sidebar.svelte.d.ts +1 -2
- package/dist/components/sidebar/sidebarElements.svelte +50 -75
- package/dist/components/sidebar/sidebarElements.svelte.d.ts +10 -3
- package/dist/utils.js +2 -1
- package/package.json +2 -2
- package/src/lib/components/dataTable/dataTable.svelte +77 -14
- package/src/lib/components/dataTable/header.svelte +88 -24
- package/src/lib/components/dataTable/listViewChildren.svelte +106 -0
- package/src/lib/components/dataTable/table.svelte +1 -1
- package/src/lib/components/detailView/create/createManyView.svelte +2 -2
- package/src/lib/components/detailView/update/detailViewChildren.svelte +72 -0
- package/src/lib/components/detailView/update/updateDetailView.svelte +6 -3
- package/src/lib/components/routes/collections/collections.svelte +44 -23
- package/src/lib/components/routes/data_model/dataModel.svelte +2 -8
- package/src/lib/components/routes/workflows/workflows.svelte +24 -11
- package/src/lib/components/sidebar/sidebar.svelte +12 -5
- package/src/lib/components/sidebar/sidebarElements.svelte +50 -75
- package/src/lib/utils.ts +2 -1
- package/dist/components/dataTable/childRecords.svelte +0 -142
- package/dist/components/dataTable/childRecords.svelte.d.ts +0 -9
- package/dist/components/detailView/update/children.svelte +0 -96
- package/dist/components/detailView/update/children.svelte.d.ts +0 -7
- package/src/lib/components/dataTable/childRecords.svelte +0 -142
- package/src/lib/components/detailView/update/children.svelte +0 -96
|
@@ -1,40 +1,61 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { SideBarData } from "../../../components/sidebar/sidebarElements.svelte";
|
|
2
|
+
import type { SideBarData, SideBarNode } from "../../../components/sidebar/sidebarElements.svelte";
|
|
3
3
|
import Sidebar from "../../../components/sidebar/sidebar.svelte";
|
|
4
4
|
import { getStudioContext } from "../../../context";
|
|
5
5
|
import Collection from "./collection.svelte";
|
|
6
6
|
|
|
7
7
|
const { ctx } = getStudioContext();
|
|
8
|
-
import { Table } from "lucide-svelte";
|
|
8
|
+
import { Table, Cpu, LibraryBig } from "lucide-svelte";
|
|
9
|
+
import * as Icons from "lucide-svelte";
|
|
10
|
+
|
|
11
|
+
const directoryIcons: Record<string, any> = {
|
|
12
|
+
project: LibraryBig,
|
|
13
|
+
core: Cpu,
|
|
14
|
+
};
|
|
9
15
|
|
|
10
16
|
let { collectionName } = $props();
|
|
11
17
|
|
|
12
18
|
const collectionsList = $state(getCollectionsList());
|
|
13
19
|
|
|
14
|
-
function getCollectionsList() {
|
|
20
|
+
function getCollectionsList(): SideBarData {
|
|
15
21
|
const collections = ctx.meta.collections;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// updating the path from '__project' and '__core' to a more readable names
|
|
28
|
-
collectionsOwners = collectionsOwners.map((item) => {
|
|
29
|
-
if (item.path === "__project") {
|
|
30
|
-
item.path = "project";
|
|
31
|
-
} else if (item.path === "__core") {
|
|
32
|
-
item.path = "core";
|
|
22
|
+
|
|
23
|
+
const groups = new Map<string, SideBarNode[]>();
|
|
24
|
+
|
|
25
|
+
for (const [name, value] of Object.entries(collections)) {
|
|
26
|
+
let groupKey: string = (value as any).category ?? (value as any).owner;
|
|
27
|
+
if (groupKey === "__project") groupKey = "project";
|
|
28
|
+
else if (groupKey === "__core") groupKey = "core";
|
|
29
|
+
|
|
30
|
+
if (!groups.has(groupKey)) {
|
|
31
|
+
groups.set(groupKey, []);
|
|
33
32
|
}
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
groups.get(groupKey)!.push({
|
|
34
|
+
type: "element",
|
|
35
|
+
name,
|
|
36
|
+
icon: Table,
|
|
37
|
+
href: `/studio/collections/${name}`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const result: SideBarData = [];
|
|
42
|
+
for (const [groupKey, children] of groups) {
|
|
43
|
+
const extensionIconName = ctx.meta.extensions?.[groupKey]?.icon;
|
|
44
|
+
const isProject = groupKey === "project";
|
|
45
|
+
result.push({
|
|
46
|
+
type: "directory",
|
|
47
|
+
name: isProject ? "Collections" : groupKey,
|
|
48
|
+
icon: directoryIcons[groupKey] ?? (extensionIconName ? (Icons as any)[extensionIconName] : undefined),
|
|
49
|
+
collapsed: isProject ? false : true,
|
|
50
|
+
children,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
36
53
|
|
|
37
|
-
return
|
|
54
|
+
return result.sort((a, b) => {
|
|
55
|
+
if ((a as any).name === "core") return 1;
|
|
56
|
+
if ((b as any).name === "core") return -1;
|
|
57
|
+
return 0;
|
|
58
|
+
});
|
|
38
59
|
}
|
|
39
60
|
</script>
|
|
40
61
|
|
|
@@ -13,14 +13,8 @@
|
|
|
13
13
|
title="Data Model"
|
|
14
14
|
showSearch={false}
|
|
15
15
|
data={[
|
|
16
|
-
{
|
|
17
|
-
|
|
18
|
-
href: "/studio/datamodel/graph",
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
name: "query_editor",
|
|
22
|
-
href: "/studio/datamodel/query_editor",
|
|
23
|
-
},
|
|
16
|
+
{ type: "element", name: "graph", href: "/studio/datamodel/graph" },
|
|
17
|
+
{ type: "element", name: "query_editor", href: "/studio/datamodel/query_editor" },
|
|
24
18
|
]}
|
|
25
19
|
>
|
|
26
20
|
<div class="relative h-full w-full">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { SideBarData } from "../../../components/sidebar/sidebarElements.svelte";
|
|
2
|
+
import type { SideBarData, SideBarNode } from "../../../components/sidebar/sidebarElements.svelte";
|
|
3
3
|
import WorkflowEditor, {
|
|
4
4
|
type WorkflowEntry,
|
|
5
5
|
} from "../../../components/workflowEditor.svelte";
|
|
@@ -31,18 +31,31 @@
|
|
|
31
31
|
const response = await lobb.findAll("core_workflows", {});
|
|
32
32
|
const result = await response.json();
|
|
33
33
|
const workflows: any[] = result.data;
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
|
|
35
|
+
const groups = new Map<string, SideBarNode[]>();
|
|
36
|
+
const nodes: SideBarData = [];
|
|
37
|
+
|
|
38
|
+
for (const workflow of workflows) {
|
|
39
|
+
const item: SideBarNode = {
|
|
40
|
+
type: "element",
|
|
36
41
|
name: workflow.name,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
location.navigate(`/studio/workflows/${workflow.name}`);
|
|
40
|
-
},
|
|
41
|
-
meta: {
|
|
42
|
-
id: workflow.id,
|
|
43
|
-
},
|
|
42
|
+
onclick: () => location.navigate(`/studio/workflows/${workflow.name}`),
|
|
43
|
+
meta: { id: workflow.id },
|
|
44
44
|
};
|
|
45
|
-
|
|
45
|
+
|
|
46
|
+
if (workflow.directory) {
|
|
47
|
+
if (!groups.has(workflow.directory)) {
|
|
48
|
+
const children: SideBarNode[] = [];
|
|
49
|
+
groups.set(workflow.directory, children);
|
|
50
|
+
nodes.push({ type: "directory", name: workflow.directory, children });
|
|
51
|
+
}
|
|
52
|
+
groups.get(workflow.directory)!.push(item);
|
|
53
|
+
} else {
|
|
54
|
+
nodes.push(item);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
sidebarData = nodes;
|
|
46
59
|
}
|
|
47
60
|
|
|
48
61
|
async function fetchWorkflowData(workflowName: string) {
|
|
@@ -13,13 +13,12 @@
|
|
|
13
13
|
import SidebarElements, {
|
|
14
14
|
type SideBarData,
|
|
15
15
|
type SideBarElement,
|
|
16
|
-
type
|
|
16
|
+
type SideBarNode,
|
|
17
17
|
} from "./sidebarElements.svelte";
|
|
18
18
|
|
|
19
19
|
interface Props {
|
|
20
20
|
title: string;
|
|
21
21
|
data: SideBarData | null;
|
|
22
|
-
sidebarElementsProps?: Partial<SidebarElementsProps>;
|
|
23
22
|
showSearch?: boolean;
|
|
24
23
|
children: Snippet<[]>;
|
|
25
24
|
belowSearch?: Snippet;
|
|
@@ -29,7 +28,6 @@
|
|
|
29
28
|
let {
|
|
30
29
|
title,
|
|
31
30
|
data,
|
|
32
|
-
sidebarElementsProps,
|
|
33
31
|
showSearch = true,
|
|
34
32
|
children,
|
|
35
33
|
belowSearch,
|
|
@@ -67,7 +65,17 @@
|
|
|
67
65
|
}
|
|
68
66
|
|
|
69
67
|
function filterSidebarData(items: SideBarData, term: string): SideBarData {
|
|
70
|
-
return items.
|
|
68
|
+
return items.reduce<SideBarData>((acc, node) => {
|
|
69
|
+
if (node.type === "directory") {
|
|
70
|
+
const filteredChildren = filterSidebarData(node.children, term);
|
|
71
|
+
if (filteredChildren.length > 0) {
|
|
72
|
+
acc.push({ ...node, collapsed: false, children: filteredChildren });
|
|
73
|
+
}
|
|
74
|
+
} else if (node.name.toLowerCase().includes(term)) {
|
|
75
|
+
acc.push(node);
|
|
76
|
+
}
|
|
77
|
+
return acc;
|
|
78
|
+
}, []);
|
|
71
79
|
}
|
|
72
80
|
</script>
|
|
73
81
|
|
|
@@ -124,7 +132,6 @@
|
|
|
124
132
|
<SidebarElements
|
|
125
133
|
bind:data={visibleData}
|
|
126
134
|
{elementRightSide}
|
|
127
|
-
{...sidebarElementsProps}
|
|
128
135
|
/>
|
|
129
136
|
{/key}
|
|
130
137
|
</div>
|
|
@@ -2,11 +2,10 @@ export interface SidebarProperties {
|
|
|
2
2
|
collapsed: boolean;
|
|
3
3
|
}
|
|
4
4
|
import { type Snippet } from "svelte";
|
|
5
|
-
import { type SideBarData, type SideBarElement
|
|
5
|
+
import { type SideBarData, type SideBarElement } from "./sidebarElements.svelte";
|
|
6
6
|
interface Props {
|
|
7
7
|
title: string;
|
|
8
8
|
data: SideBarData | null;
|
|
9
|
-
sidebarElementsProps?: Partial<SidebarElementsProps>;
|
|
10
9
|
showSearch?: boolean;
|
|
11
10
|
children: Snippet<[]>;
|
|
12
11
|
belowSearch?: Snippet;
|
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
export interface SideBarElement {
|
|
3
|
+
type: "element";
|
|
3
4
|
name: string;
|
|
4
5
|
onclick?: () => Promise<void> | void;
|
|
5
6
|
href?: string;
|
|
6
7
|
icon?: any;
|
|
7
|
-
path?: string;
|
|
8
8
|
meta?: Record<string, any>;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export
|
|
11
|
+
export interface SideBarDirectory {
|
|
12
|
+
type: "directory";
|
|
13
|
+
name: string;
|
|
14
|
+
icon?: any;
|
|
15
|
+
collapsed?: boolean;
|
|
16
|
+
children: SideBarNode[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type SideBarNode = SideBarElement | SideBarDirectory;
|
|
20
|
+
export type SideBarData = SideBarNode[];
|
|
12
21
|
|
|
13
22
|
export interface SidebarElementsProps {
|
|
14
23
|
data: SideBarData;
|
|
15
|
-
path?: string[];
|
|
16
24
|
elementRightSide?: Snippet<[SideBarElement]>;
|
|
17
25
|
}
|
|
18
26
|
</script>
|
|
19
27
|
|
|
20
28
|
<script lang="ts">
|
|
21
|
-
import { Ban } from "lucide-svelte";
|
|
29
|
+
import { Ban, ChevronDown, Folder } from "lucide-svelte";
|
|
22
30
|
import type { Snippet } from "svelte";
|
|
23
31
|
import SidebarElements from "./sidebarElements.svelte";
|
|
24
32
|
import Button from "../ui/button/button.svelte";
|
|
@@ -26,117 +34,84 @@
|
|
|
26
34
|
|
|
27
35
|
let {
|
|
28
36
|
data = $bindable(),
|
|
29
|
-
path = [],
|
|
30
37
|
elementRightSide,
|
|
31
38
|
}: SidebarElementsProps = $props();
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (firstPath && item.path && !acc.includes(firstPath)) {
|
|
37
|
-
acc.push(firstPath);
|
|
38
|
-
} else if (!item.path) {
|
|
39
|
-
acc.push(item);
|
|
40
|
-
}
|
|
41
|
-
return acc;
|
|
42
|
-
},
|
|
43
|
-
[],
|
|
40
|
+
let expandedHeights: number[] = $state(Array(data.length).fill(0));
|
|
41
|
+
let collapsedDirs: boolean[] = $state(
|
|
42
|
+
data.map((node) => node.type === "directory" ? (node.collapsed ?? false) : false)
|
|
44
43
|
);
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
function toggleDir(index: number) {
|
|
46
|
+
collapsedDirs[index] = !collapsedDirs[index];
|
|
47
|
+
}
|
|
47
48
|
|
|
48
49
|
async function handleElementClick(element: SideBarElement) {
|
|
49
50
|
if (element.onclick) {
|
|
50
51
|
await element.onclick();
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
-
function getDirElements(dirName: string) {
|
|
55
|
-
let elements = data.filter((item) => item.path?.startsWith(dirName));
|
|
56
|
-
elements = elements.map((item) => {
|
|
57
|
-
return {
|
|
58
|
-
...item,
|
|
59
|
-
path: item.path?.split("/").slice(1).join("/"),
|
|
60
|
-
};
|
|
61
|
-
});
|
|
62
|
-
return elements;
|
|
63
|
-
}
|
|
64
54
|
</script>
|
|
65
55
|
|
|
66
56
|
<div class="flex flex-col">
|
|
67
|
-
{#if
|
|
68
|
-
{#each
|
|
69
|
-
{#if
|
|
70
|
-
{@const directoryName = element.split("/")[0]}
|
|
57
|
+
{#if data.length}
|
|
58
|
+
{#each data as node, index}
|
|
59
|
+
{#if node.type === "directory"}
|
|
71
60
|
<button
|
|
72
|
-
class="
|
|
73
|
-
|
|
74
|
-
rounded-md cursor-default
|
|
75
|
-
"
|
|
61
|
+
class="flex items-center justify-between p-2 gap-2 text-muted-foreground rounded-md hover:bg-muted/30 cursor-pointer"
|
|
62
|
+
onclick={() => toggleDir(index)}
|
|
76
63
|
>
|
|
77
64
|
<div class="flex items-center gap-2">
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
65
|
+
{#if node.icon}
|
|
66
|
+
<node.icon size="17.5" />
|
|
67
|
+
{:else}
|
|
68
|
+
<Folder size="17.5" />
|
|
69
|
+
{/if}
|
|
70
|
+
<div class="text-xs">{node.name}</div>
|
|
81
71
|
</div>
|
|
72
|
+
<ChevronDown
|
|
73
|
+
size="14"
|
|
74
|
+
class="transition-transform duration-200 {collapsedDirs[index] ? '-rotate-90' : ''}"
|
|
75
|
+
/>
|
|
82
76
|
</button>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
>
|
|
90
|
-
<div
|
|
91
|
-
bind:clientHeight={expandedHeights[index]}
|
|
92
|
-
class="border-l ml-4 pl-2"
|
|
93
|
-
>
|
|
94
|
-
<SidebarElements
|
|
95
|
-
data={getDirElements(element)}
|
|
96
|
-
path={[...path, element]}
|
|
97
|
-
{elementRightSide}
|
|
98
|
-
/>
|
|
99
|
-
</div>
|
|
77
|
+
<div
|
|
78
|
+
class="overflow-hidden transition-[height] duration-200"
|
|
79
|
+
style="height: {collapsedDirs[index] ? 0 : expandedHeights[index]}px;"
|
|
80
|
+
>
|
|
81
|
+
<div bind:clientHeight={expandedHeights[index]} class="border-l ml-4 pl-2">
|
|
82
|
+
<SidebarElements data={node.children} {elementRightSide} />
|
|
100
83
|
</div>
|
|
101
|
-
|
|
84
|
+
</div>
|
|
102
85
|
{:else}
|
|
103
|
-
{@const
|
|
104
|
-
{@const isselected = location.url.pathname === element.href}
|
|
86
|
+
{@const isselected = location.url.pathname === node.href}
|
|
105
87
|
<Button
|
|
106
|
-
onclick={() => handleElementClick(
|
|
107
|
-
href={
|
|
88
|
+
onclick={() => handleElementClick(node)}
|
|
89
|
+
href={node.href}
|
|
108
90
|
variant="ghost"
|
|
109
91
|
class="
|
|
110
92
|
flex items-center justify-between p-2 gap-2 hover:bg-muted/30 text-muted-foreground
|
|
111
93
|
rounded-md {isselected ? 'bg-muted' : ''}
|
|
112
94
|
"
|
|
113
|
-
title={
|
|
95
|
+
title={node.name}
|
|
114
96
|
>
|
|
115
97
|
<div class="flex items-center gap-2 truncate">
|
|
116
|
-
{#if
|
|
117
|
-
<
|
|
98
|
+
{#if node.icon}
|
|
99
|
+
<node.icon size="17.5" />
|
|
118
100
|
{/if}
|
|
119
|
-
<div
|
|
120
|
-
|
|
121
|
-
text-xs
|
|
122
|
-
{isselected ? 'text-primary font-medium' : ''}
|
|
123
|
-
"
|
|
124
|
-
>
|
|
125
|
-
{element.name}
|
|
101
|
+
<div class="text-xs {isselected ? 'text-primary font-medium' : ''}">
|
|
102
|
+
{node.name}
|
|
126
103
|
</div>
|
|
127
104
|
</div>
|
|
128
105
|
<div class="flex gap-2 items-center">
|
|
129
106
|
{#if elementRightSide}
|
|
130
|
-
{@render elementRightSide(
|
|
107
|
+
{@render elementRightSide(node)}
|
|
131
108
|
{/if}
|
|
132
109
|
</div>
|
|
133
110
|
</Button>
|
|
134
111
|
{/if}
|
|
135
112
|
{/each}
|
|
136
113
|
{:else}
|
|
137
|
-
<div
|
|
138
|
-
class="flex justify-center items-center gap-2 text-muted-foreground"
|
|
139
|
-
>
|
|
114
|
+
<div class="flex justify-center items-center gap-2 text-muted-foreground">
|
|
140
115
|
<Ban size="17.5" />
|
|
141
116
|
<div class="text-xs text-center">No result</div>
|
|
142
117
|
</div>
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
export interface SideBarElement {
|
|
2
|
+
type: "element";
|
|
2
3
|
name: string;
|
|
3
4
|
onclick?: () => Promise<void> | void;
|
|
4
5
|
href?: string;
|
|
5
6
|
icon?: any;
|
|
6
|
-
path?: string;
|
|
7
7
|
meta?: Record<string, any>;
|
|
8
8
|
}
|
|
9
|
-
export
|
|
9
|
+
export interface SideBarDirectory {
|
|
10
|
+
type: "directory";
|
|
11
|
+
name: string;
|
|
12
|
+
icon?: any;
|
|
13
|
+
collapsed?: boolean;
|
|
14
|
+
children: SideBarNode[];
|
|
15
|
+
}
|
|
16
|
+
export type SideBarNode = SideBarElement | SideBarDirectory;
|
|
17
|
+
export type SideBarData = SideBarNode[];
|
|
10
18
|
export interface SidebarElementsProps {
|
|
11
19
|
data: SideBarData;
|
|
12
|
-
path?: string[];
|
|
13
20
|
elementRightSide?: Snippet<[SideBarElement]>;
|
|
14
21
|
}
|
|
15
22
|
import type { Snippet } from "svelte";
|
package/dist/utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { clsx } from "clsx";
|
|
2
2
|
import { twMerge } from "tailwind-merge";
|
|
3
|
+
import { isEqual } from "lodash";
|
|
3
4
|
import { MediaQuery } from 'svelte/reactivity';
|
|
4
5
|
export function cn() {
|
|
5
6
|
var inputs = [];
|
|
@@ -26,7 +27,7 @@ export function getChangedProperties(oldObj, newObj) {
|
|
|
26
27
|
var changes = {};
|
|
27
28
|
for (var _i = 0, _a = Object.keys(newObj); _i < _a.length; _i++) {
|
|
28
29
|
var key = _a[_i];
|
|
29
|
-
if (oldObj[key]
|
|
30
|
+
if (!isEqual(oldObj[key], newObj[key])) {
|
|
30
31
|
changes[key] = newObj[key];
|
|
31
32
|
}
|
|
32
33
|
}
|
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.27.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.30.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",
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export interface ParentContext {
|
|
3
|
+
collectionName: string;
|
|
4
|
+
recordId: string | number;
|
|
5
|
+
}
|
|
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
|
+
</script>
|
|
14
|
+
|
|
1
15
|
<script lang="ts">
|
|
2
16
|
import _ from "lodash";
|
|
3
17
|
import { getStudioContext } from "../../context";
|
|
@@ -5,9 +19,9 @@
|
|
|
5
19
|
import Header from "./header.svelte";
|
|
6
20
|
import Table, { type TableProps } from "./table.svelte";
|
|
7
21
|
import { getCollectionColumns, getCollectionParamsFields } from "./utils";
|
|
8
|
-
import { Pencil, Trash } from "lucide-svelte";
|
|
22
|
+
import { Pencil, Trash, Unlink } from "lucide-svelte";
|
|
9
23
|
import * as icons from "lucide-svelte";
|
|
10
|
-
import
|
|
24
|
+
import ListViewChildren from "./listViewChildren.svelte";
|
|
11
25
|
import FieldCell from "./fieldCell.svelte";
|
|
12
26
|
import Skeleton from "../ui/skeleton/skeleton.svelte";
|
|
13
27
|
import Button from "../ui/button/button.svelte";
|
|
@@ -25,8 +39,12 @@
|
|
|
25
39
|
interface Props {
|
|
26
40
|
collectionName: string;
|
|
27
41
|
filter?: any;
|
|
42
|
+
searchParams?: Record<string, any>;
|
|
43
|
+
parentContext?: ParentContext;
|
|
44
|
+
onOperation?: (op: RecordOperation) => void;
|
|
28
45
|
showHeader?: boolean;
|
|
29
46
|
showFooter?: boolean;
|
|
47
|
+
showImport?: boolean;
|
|
30
48
|
unifiedBgColor?: "bg-muted/30" | "bg-background";
|
|
31
49
|
showDelete?: boolean;
|
|
32
50
|
tableProps?: Partial<TableProps>;
|
|
@@ -36,8 +54,12 @@
|
|
|
36
54
|
let {
|
|
37
55
|
collectionName,
|
|
38
56
|
filter,
|
|
57
|
+
searchParams,
|
|
58
|
+
parentContext,
|
|
59
|
+
onOperation,
|
|
39
60
|
showHeader = true,
|
|
40
61
|
showFooter = true,
|
|
62
|
+
showImport = true,
|
|
41
63
|
unifiedBgColor,
|
|
42
64
|
showDelete = false,
|
|
43
65
|
tableProps,
|
|
@@ -57,6 +79,7 @@
|
|
|
57
79
|
sort: {},
|
|
58
80
|
limit: "100",
|
|
59
81
|
page: 1,
|
|
82
|
+
...searchParams,
|
|
60
83
|
});
|
|
61
84
|
|
|
62
85
|
$effect(() => {
|
|
@@ -73,10 +96,9 @@
|
|
|
73
96
|
);
|
|
74
97
|
let dataTableContainerWidth: number = $state(0);
|
|
75
98
|
let dataTableWidth: number = $state(0);
|
|
76
|
-
const doesCollectionHasChildren =
|
|
77
|
-
ctx.meta.
|
|
78
|
-
(
|
|
79
|
-
),
|
|
99
|
+
const doesCollectionHasChildren = $derived(
|
|
100
|
+
(ctx.meta.collections[collectionName]?.children ?? [])
|
|
101
|
+
.some((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic")
|
|
80
102
|
);
|
|
81
103
|
|
|
82
104
|
// requests the data from the server when the params is changed
|
|
@@ -107,14 +129,44 @@
|
|
|
107
129
|
loading = false;
|
|
108
130
|
}
|
|
109
131
|
|
|
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
|
+
|
|
110
146
|
async function handleDelete(entryId: string) {
|
|
111
|
-
const result = await showDialog(
|
|
112
|
-
"Are you sure?",
|
|
113
|
-
"This will delete the record you selected.",
|
|
114
|
-
);
|
|
147
|
+
const result = await showDialog("Are you sure?", "This will permanently delete the record.");
|
|
115
148
|
if (result) {
|
|
116
|
-
|
|
117
|
-
|
|
149
|
+
if (onOperation) {
|
|
150
|
+
applyOperation({ type: "delete", id: entryId });
|
|
151
|
+
} else if (parentContext) {
|
|
152
|
+
await lobb.updateOne(parentContext.collectionName, String(parentContext.recordId), {}, { [collectionName]: { delete: [entryId] } });
|
|
153
|
+
params = { ...params };
|
|
154
|
+
} else {
|
|
155
|
+
await lobb.deleteOne(collectionName, entryId);
|
|
156
|
+
params = { ...params };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function handleUnlink(entryId: string) {
|
|
162
|
+
const result = await showDialog("Are you sure?", "This will unlink the record without deleting it.");
|
|
163
|
+
if (result) {
|
|
164
|
+
if (onOperation) {
|
|
165
|
+
applyOperation({ type: "unlink", id: entryId });
|
|
166
|
+
} else {
|
|
167
|
+
await lobb.updateOne(parentContext!.collectionName, String(parentContext!.recordId), {}, { [collectionName]: { unlink: [entryId] } });
|
|
168
|
+
params = { ...params };
|
|
169
|
+
}
|
|
118
170
|
}
|
|
119
171
|
}
|
|
120
172
|
|
|
@@ -157,7 +209,7 @@
|
|
|
157
209
|
{/snippet}
|
|
158
210
|
|
|
159
211
|
{#if showHeader}
|
|
160
|
-
<Header bind:params {collectionName} bind:selectedRecords>
|
|
212
|
+
<Header bind:params {collectionName} bind:selectedRecords {parentContext} {showImport} onOperation={onOperation ? applyOperation : undefined}>
|
|
161
213
|
{#snippet left()}
|
|
162
214
|
{@render headerLeft?.()}
|
|
163
215
|
{/snippet}
|
|
@@ -198,6 +250,16 @@
|
|
|
198
250
|
params = { ...params };
|
|
199
251
|
}}
|
|
200
252
|
></UpdateDetailViewButton>
|
|
253
|
+
{#if parentContext}
|
|
254
|
+
<Button
|
|
255
|
+
class="h-6 w-6 text-muted-foreground hover:bg-transparent"
|
|
256
|
+
variant="ghost"
|
|
257
|
+
size="icon"
|
|
258
|
+
onclick={() => handleUnlink(entry.id)}
|
|
259
|
+
Icon={Unlink}
|
|
260
|
+
title="Remove from this entry"
|
|
261
|
+
></Button>
|
|
262
|
+
{/if}
|
|
201
263
|
{#if showDelete}
|
|
202
264
|
<Button
|
|
203
265
|
class="h-6 w-6 text-muted-foreground hover:bg-transparent"
|
|
@@ -205,6 +267,7 @@
|
|
|
205
267
|
size="icon"
|
|
206
268
|
onclick={() => handleDelete(entry.id)}
|
|
207
269
|
Icon={Trash}
|
|
270
|
+
title="Delete permanently"
|
|
208
271
|
></Button>
|
|
209
272
|
{/if}
|
|
210
273
|
{#await getWorkflowTools($state.snapshot(entry))}
|
|
@@ -232,7 +295,7 @@
|
|
|
232
295
|
/>
|
|
233
296
|
{/snippet}
|
|
234
297
|
{#snippet collapsible(entry)}
|
|
235
|
-
<
|
|
298
|
+
<ListViewChildren
|
|
236
299
|
{collectionName}
|
|
237
300
|
recordId={entry.id}
|
|
238
301
|
width={dataTableWidth > dataTableContainerWidth
|