@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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { getStudioContext } from "../../context";
|
|
3
3
|
|
|
4
4
|
const { lobb, ctx } = getStudioContext();
|
|
5
|
-
import { Download, ListRestart, Plus, Trash } from "lucide-svelte";
|
|
5
|
+
import { Download, ListRestart, Plus, Trash, Link } from "lucide-svelte";
|
|
6
6
|
import * as Tooltip from "../ui/tooltip";
|
|
7
7
|
import LlmButton from "../LlmButton.svelte";
|
|
8
8
|
import FilterButton from "./filterButton.svelte";
|
|
@@ -11,14 +11,19 @@
|
|
|
11
11
|
import ImportButton from "../importButton.svelte";
|
|
12
12
|
import { showDialog } from "../../actions";
|
|
13
13
|
import CreateDetailViewButton from "../detailView/create/createDetailViewButton.svelte";
|
|
14
|
+
import SelectRecord from "../selectRecord.svelte";
|
|
14
15
|
import ExtensionsComponents from "../extensionsComponents.svelte";
|
|
15
16
|
import { getExtensionUtils } from "../../extensions/extensionUtils";
|
|
16
17
|
import type { Snippet } from "svelte";
|
|
18
|
+
import type { ParentContext, RecordOperation } from "./dataTable.svelte";
|
|
17
19
|
|
|
18
20
|
interface Props {
|
|
19
21
|
collectionName: string;
|
|
20
22
|
params: any;
|
|
21
23
|
selectedRecords: string[];
|
|
24
|
+
parentContext?: ParentContext;
|
|
25
|
+
onOperation?: (op: RecordOperation) => void;
|
|
26
|
+
showImport?: boolean;
|
|
22
27
|
left?: Snippet<[]>;
|
|
23
28
|
}
|
|
24
29
|
|
|
@@ -26,9 +31,42 @@
|
|
|
26
31
|
collectionName,
|
|
27
32
|
params = $bindable(),
|
|
28
33
|
selectedRecords = $bindable(),
|
|
34
|
+
parentContext,
|
|
35
|
+
onOperation,
|
|
36
|
+
showImport = true,
|
|
29
37
|
left
|
|
30
38
|
}: Props = $props();
|
|
31
39
|
|
|
40
|
+
async function handleLink(selected: any) {
|
|
41
|
+
if (!parentContext) return;
|
|
42
|
+
if (onOperation) {
|
|
43
|
+
onOperation({ type: "link", record: selected });
|
|
44
|
+
} else {
|
|
45
|
+
await lobb.updateOne(
|
|
46
|
+
parentContext.collectionName,
|
|
47
|
+
String(parentContext.recordId),
|
|
48
|
+
{},
|
|
49
|
+
{ [collectionName]: { link: [selected.id] } },
|
|
50
|
+
);
|
|
51
|
+
resetTable();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function handleChildCreate(formData: any) {
|
|
56
|
+
if (!parentContext) return;
|
|
57
|
+
if (onOperation) {
|
|
58
|
+
onOperation({ type: "create", record: formData });
|
|
59
|
+
} else {
|
|
60
|
+
await lobb.updateOne(
|
|
61
|
+
parentContext.collectionName,
|
|
62
|
+
String(parentContext.recordId),
|
|
63
|
+
{},
|
|
64
|
+
{ [collectionName]: { create: [formData] } },
|
|
65
|
+
);
|
|
66
|
+
resetTable();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
32
70
|
let headerWidth: number = $state(0);
|
|
33
71
|
let headerIsSmall: boolean = $derived(headerWidth < 560);
|
|
34
72
|
|
|
@@ -138,34 +176,60 @@
|
|
|
138
176
|
>
|
|
139
177
|
{headerIsSmall ? "" : "Refresh"}
|
|
140
178
|
</Button>
|
|
141
|
-
|
|
142
|
-
<Tooltip.
|
|
143
|
-
<Tooltip.
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
179
|
+
{#if showImport}
|
|
180
|
+
<Tooltip.Provider delayDuration={0}>
|
|
181
|
+
<Tooltip.Root>
|
|
182
|
+
<Tooltip.Trigger>
|
|
183
|
+
<ImportButton
|
|
184
|
+
{collectionName}
|
|
185
|
+
variant="outline"
|
|
186
|
+
class="h-7 px-2 text-xs font-normal"
|
|
187
|
+
Icon={Download}
|
|
188
|
+
onSuccessfullSave={() => (params = { ...params })}
|
|
189
|
+
/>
|
|
190
|
+
</Tooltip.Trigger>
|
|
191
|
+
<Tooltip.Content>Import</Tooltip.Content>
|
|
192
|
+
</Tooltip.Root>
|
|
193
|
+
</Tooltip.Provider>
|
|
194
|
+
{/if}
|
|
155
195
|
<ExtensionsComponents
|
|
156
196
|
name="listView.header.actions"
|
|
157
197
|
utils={getExtensionUtils(lobb, ctx)}
|
|
158
198
|
{collectionName}
|
|
159
199
|
refresh={() => { params = { ...params }; }}
|
|
160
200
|
/>
|
|
161
|
-
|
|
162
|
-
{
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
201
|
+
{#if parentContext}
|
|
202
|
+
{#if parentContext}
|
|
203
|
+
<SelectRecord
|
|
204
|
+
{collectionName}
|
|
205
|
+
variant="outline"
|
|
206
|
+
class="h-7 px-3 text-xs font-normal"
|
|
207
|
+
Icon={Link}
|
|
208
|
+
onSelect={handleLink}
|
|
209
|
+
>
|
|
210
|
+
{headerIsSmall ? "" : "Link"}
|
|
211
|
+
</SelectRecord>
|
|
212
|
+
{/if}
|
|
213
|
+
<CreateDetailViewButton
|
|
214
|
+
{collectionName}
|
|
215
|
+
variant="default"
|
|
216
|
+
class="h-7 px-3 text-xs font-normal"
|
|
217
|
+
Icon={Plus}
|
|
218
|
+
rollback={true}
|
|
219
|
+
onSuccessfullSave={handleChildCreate}
|
|
220
|
+
>
|
|
221
|
+
{headerIsSmall ? "" : "Create"}
|
|
222
|
+
</CreateDetailViewButton>
|
|
223
|
+
{:else}
|
|
224
|
+
<CreateDetailViewButton
|
|
225
|
+
{collectionName}
|
|
226
|
+
variant="default"
|
|
227
|
+
class="h-7 px-3 text-xs font-normal"
|
|
228
|
+
Icon={Plus}
|
|
229
|
+
onSuccessfullSave={() => (params = { ...params })}
|
|
230
|
+
>
|
|
231
|
+
{headerIsSmall ? "" : "Create"}
|
|
232
|
+
</CreateDetailViewButton>
|
|
233
|
+
{/if}
|
|
170
234
|
</div>
|
|
171
235
|
</div>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getStudioContext } from "../../context";
|
|
3
|
+
import { ChevronRight, Table, Plus, Link, ArrowLeftRight, GitFork } from "lucide-svelte";
|
|
4
|
+
import DataTable from "./dataTable.svelte";
|
|
5
|
+
import CreateDetailViewButton from "../detailView/create/createDetailViewButton.svelte";
|
|
6
|
+
import ExtensionsComponents from "../extensionsComponents.svelte";
|
|
7
|
+
import { getExtensionUtils } from "../../extensions/extensionUtils";
|
|
8
|
+
|
|
9
|
+
const { ctx, lobb } = getStudioContext();
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
collectionName: string;
|
|
13
|
+
recordId: string;
|
|
14
|
+
width: number;
|
|
15
|
+
unifiedBgColor?: "bg-muted/30" | "bg-background";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let { collectionName, recordId, width, unifiedBgColor }: Props = $props();
|
|
19
|
+
|
|
20
|
+
const children = (ctx.meta.collections[collectionName]?.children ?? [])
|
|
21
|
+
.filter((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic");
|
|
22
|
+
|
|
23
|
+
let expandedRows: boolean[] = $state(new Array(children.length).fill(false));
|
|
24
|
+
let refreshDataTable = $state(true);
|
|
25
|
+
let tableHeaderWidth = $state(0);
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<div class="flex" style="width: {width}px;">
|
|
29
|
+
<div
|
|
30
|
+
class="flex justify-center border-r {unifiedBgColor ? unifiedBgColor : 'bg-background'}"
|
|
31
|
+
style="width: 40px"
|
|
32
|
+
></div>
|
|
33
|
+
<div class="flex-1 flex flex-col">
|
|
34
|
+
{#each children as child, index}
|
|
35
|
+
{@const lastRow = children.length - 1 === index}
|
|
36
|
+
<div class="overflow-hidden {unifiedBgColor ? unifiedBgColor : 'bg-background'}">
|
|
37
|
+
<div
|
|
38
|
+
bind:clientWidth={tableHeaderWidth}
|
|
39
|
+
class="flex justify-between items-center gap-2 text-sm h-10 {expandedRows[index] || !lastRow ? 'border-b' : ''}"
|
|
40
|
+
>
|
|
41
|
+
<button
|
|
42
|
+
class="flex gap-2 px-2 flex-1 h-full items-center"
|
|
43
|
+
onclick={() => { expandedRows[index] = !expandedRows[index]; }}
|
|
44
|
+
>
|
|
45
|
+
<ChevronRight
|
|
46
|
+
size="17.5"
|
|
47
|
+
class="text-muted-foreground transition-transform"
|
|
48
|
+
style={expandedRows[index] ? "transform: rotate(90deg);" : "transform: rotate(0deg);"}
|
|
49
|
+
/>
|
|
50
|
+
<Table size="17.5" class="text-muted-foreground" />
|
|
51
|
+
<div class="text-muted-foreground">{child.collection}</div>
|
|
52
|
+
{#if child.type === "fk"}
|
|
53
|
+
<span title="Direct (FK)"><Link size="13" class="text-muted-foreground/50" /></span>
|
|
54
|
+
{:else if child.type === "m2m"}
|
|
55
|
+
<span title="Many to Many"><ArrowLeftRight size="13" class="text-muted-foreground/50" /></span>
|
|
56
|
+
{:else if child.type === "polymorphic"}
|
|
57
|
+
<span title="Polymorphic"><GitFork size="13" class="text-muted-foreground/50" /></span>
|
|
58
|
+
{/if}
|
|
59
|
+
</button>
|
|
60
|
+
{#if child.type === "fk"}
|
|
61
|
+
<div class="flex items-center px-2">
|
|
62
|
+
<CreateDetailViewButton
|
|
63
|
+
collectionName={child.collection}
|
|
64
|
+
variant="ghost"
|
|
65
|
+
class="h-7 px-3 text-xs font-normal"
|
|
66
|
+
Icon={Plus}
|
|
67
|
+
values={{ [child.field]: { id: recordId } }}
|
|
68
|
+
onSuccessfullSave={async () => { refreshDataTable = !refreshDataTable; }}
|
|
69
|
+
>
|
|
70
|
+
Create
|
|
71
|
+
</CreateDetailViewButton>
|
|
72
|
+
</div>
|
|
73
|
+
{/if}
|
|
74
|
+
</div>
|
|
75
|
+
{#if expandedRows[index]}
|
|
76
|
+
<div class="flex max-h-96 overflow-auto {lastRow ? '' : 'border-b'}">
|
|
77
|
+
<div
|
|
78
|
+
class="border-r {unifiedBgColor ? unifiedBgColor : ''}"
|
|
79
|
+
style="width: 100vw; max-width: 40px"
|
|
80
|
+
></div>
|
|
81
|
+
<div class="flex-1" style="width: {tableHeaderWidth - 40}px;">
|
|
82
|
+
{#key refreshDataTable}
|
|
83
|
+
<ExtensionsComponents
|
|
84
|
+
name="listView.entry.children.{child.collection}"
|
|
85
|
+
collectionName={child.collection}
|
|
86
|
+
searchParams={{ children_of: collectionName, parent_id: recordId }}
|
|
87
|
+
utils={getExtensionUtils(lobb, ctx)}
|
|
88
|
+
>
|
|
89
|
+
<DataTable
|
|
90
|
+
collectionName={child.collection}
|
|
91
|
+
searchParams={{ children_of: collectionName, parent_id: recordId }}
|
|
92
|
+
showHeader={false}
|
|
93
|
+
showFooter={false}
|
|
94
|
+
showDelete={child.type === "fk"}
|
|
95
|
+
{unifiedBgColor}
|
|
96
|
+
tableProps={{ showLastRowBorder: false, showLastColumnBorder: false, showCheckboxes: false }}
|
|
97
|
+
/>
|
|
98
|
+
</ExtensionsComponents>
|
|
99
|
+
{/key}
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
{/if}
|
|
103
|
+
</div>
|
|
104
|
+
{/each}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
<div
|
|
173
173
|
style="
|
|
174
174
|
display: grid;
|
|
175
|
-
grid-template-columns: minmax(auto,
|
|
175
|
+
grid-template-columns: minmax(auto, 10rem) repeat({columnsLength - 1}, minmax(auto, 15rem)){rowActionsExists ? ' minmax(auto, 7.5rem)' : ''};
|
|
176
176
|
grid-template-rows: 2.5rem;
|
|
177
177
|
"
|
|
178
178
|
>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import SelectRecord from "../../../components/selectRecord.svelte";
|
|
16
16
|
import FieldCell from "../../../components/dataTable/fieldCell.svelte";
|
|
17
17
|
import SubRecords from "./subRecords.svelte";
|
|
18
|
-
import
|
|
18
|
+
import ListViewChildren from "../../../components/dataTable/listViewChildren.svelte";
|
|
19
19
|
import { getStudioContext } from "../../../context";
|
|
20
20
|
|
|
21
21
|
const { ctx } = getStudioContext();
|
|
@@ -232,7 +232,7 @@
|
|
|
232
232
|
{/snippet}
|
|
233
233
|
{#snippet collapsible(entry, index)}
|
|
234
234
|
{#if entry.id}
|
|
235
|
-
<
|
|
235
|
+
<ListViewChildren
|
|
236
236
|
{collectionName}
|
|
237
237
|
recordId={entry.id}
|
|
238
238
|
width={tableWidth}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import DataTable, { type RecordOperation } from "../../../components/dataTable/dataTable.svelte";
|
|
3
|
+
import { getStudioContext } from "../../../context";
|
|
4
|
+
import { Table, Link } from "lucide-svelte";
|
|
5
|
+
|
|
6
|
+
const { ctx } = getStudioContext();
|
|
7
|
+
|
|
8
|
+
type PendingOps = {
|
|
9
|
+
link?: (string | number)[];
|
|
10
|
+
unlink?: (string | number)[];
|
|
11
|
+
delete?: (string | number)[];
|
|
12
|
+
create?: any[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
interface LocalProp {
|
|
16
|
+
collectionName: string;
|
|
17
|
+
entry: any;
|
|
18
|
+
pendingChildren?: Record<string, PendingOps>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let { collectionName, entry, pendingChildren = $bindable({}) }: LocalProp = $props();
|
|
22
|
+
|
|
23
|
+
function makeHandler(collection: string) {
|
|
24
|
+
return (op: RecordOperation) => {
|
|
25
|
+
if (!pendingChildren[collection]) pendingChildren[collection] = {};
|
|
26
|
+
const c = pendingChildren[collection];
|
|
27
|
+
if (op.type === "link") {
|
|
28
|
+
(c.link ??= []).push(op.record.id);
|
|
29
|
+
} else if (op.type === "unlink") {
|
|
30
|
+
(c.unlink ??= []).push(op.id);
|
|
31
|
+
} else if (op.type === "delete") {
|
|
32
|
+
(c.delete ??= []).push(op.id);
|
|
33
|
+
} else if (op.type === "create") {
|
|
34
|
+
(c.create ??= []).push(op.record);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const children = (ctx.meta.collections[collectionName]?.children ?? [])
|
|
40
|
+
.filter((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic");
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
{#if children.length}
|
|
44
|
+
<div class="flex flex-col gap-3 border-t p-4">
|
|
45
|
+
<div class="flex items-center gap-2">
|
|
46
|
+
<Link size="14" class="text-muted-foreground" />
|
|
47
|
+
<span class="text-sm font-medium">Sub Records</span>
|
|
48
|
+
</div>
|
|
49
|
+
{#each children as child}
|
|
50
|
+
<div class="rounded-lg border bg-background overflow-hidden flex flex-col max-h-96">
|
|
51
|
+
<DataTable
|
|
52
|
+
collectionName={child.collection}
|
|
53
|
+
searchParams={{ children_of: collectionName, parent_id: entry.id }}
|
|
54
|
+
parentContext={{ collectionName, recordId: entry.id }}
|
|
55
|
+
onOperation={makeHandler(child.collection)}
|
|
56
|
+
showImport={false}
|
|
57
|
+
showHeader={true}
|
|
58
|
+
showFooter={true}
|
|
59
|
+
showDelete={child.type === "fk" || child.type === "m2m"}
|
|
60
|
+
tableProps={{ showLastColumnBorder: false, showLastRowBorder: true }}
|
|
61
|
+
>
|
|
62
|
+
{#snippet headerLeft()}
|
|
63
|
+
<div class="flex items-center gap-2 px-1">
|
|
64
|
+
<Table size="14" class="text-muted-foreground" />
|
|
65
|
+
<span class="text-sm font-medium">{child.collection}</span>
|
|
66
|
+
</div>
|
|
67
|
+
{/snippet}
|
|
68
|
+
</DataTable>
|
|
69
|
+
</div>
|
|
70
|
+
{/each}
|
|
71
|
+
</div>
|
|
72
|
+
{/if}
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
const { lobb, ctx } = getStudioContext();
|
|
30
30
|
import { calculateDrawerWidth, getChangedProperties } from "../../../utils";
|
|
31
31
|
import { getField, getFieldIcon } from "../../dataTable/utils";
|
|
32
|
-
import
|
|
32
|
+
import DetailViewChildren from "../update/detailViewChildren.svelte";
|
|
33
33
|
import type { Snippet } from "svelte";
|
|
34
34
|
import { getDefaultEntry, parseDetailViewValues, serializeEntry } from "../utils";
|
|
35
35
|
import FieldInput from "../fieldInput.svelte";
|
|
@@ -57,15 +57,18 @@
|
|
|
57
57
|
getChangedProperties(initialEntry, $state.snapshot(entry)),
|
|
58
58
|
);
|
|
59
59
|
let fieldsErrors: Record<string, any> = $state({});
|
|
60
|
+
let pendingChildren = $state<Record<string, any>>({});
|
|
60
61
|
|
|
61
62
|
async function handleSave() {
|
|
62
63
|
delete localEntry.id;
|
|
63
64
|
localEntry = serializeEntry(ctx, collectionName, localEntry);
|
|
64
65
|
|
|
66
|
+
const children = Object.keys(pendingChildren).length ? pendingChildren : undefined;
|
|
65
67
|
const response = await lobb.updateOne(
|
|
66
68
|
collectionName,
|
|
67
69
|
recordId,
|
|
68
70
|
localEntry,
|
|
71
|
+
children,
|
|
69
72
|
);
|
|
70
73
|
|
|
71
74
|
if (!response.bodyUsed) {
|
|
@@ -145,7 +148,7 @@
|
|
|
145
148
|
{/each}
|
|
146
149
|
</div>
|
|
147
150
|
{#if showRelatedRecords}
|
|
148
|
-
<
|
|
151
|
+
<DetailViewChildren {collectionName} {entry} bind:pendingChildren />
|
|
149
152
|
{/if}
|
|
150
153
|
</div>
|
|
151
154
|
<div class="flex h-12 items-center justify-end gap-2 border-t px-4">
|
|
@@ -163,7 +166,7 @@
|
|
|
163
166
|
class="h-7 px-3 text-xs font-normal"
|
|
164
167
|
Icon={submitButton?.icon ? submitButton.icon : Pencil}
|
|
165
168
|
onclick={handleSave}
|
|
166
|
-
disabled={!Object.keys(localEntry).length}
|
|
169
|
+
disabled={!Object.keys(localEntry).length && !Object.keys(pendingChildren).length}
|
|
167
170
|
>
|
|
168
171
|
{submitButton?.text ? submitButton.text : "Update"}
|
|
169
172
|
</Button>
|
|
@@ -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>
|