@lobb-js/studio 0.25.0 → 0.26.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.
Files changed (32) hide show
  1. package/dist/components/dataTable/dataTable.svelte +8 -6
  2. package/dist/components/dataTable/dataTable.svelte.d.ts +1 -0
  3. package/dist/components/dataTable/listViewChildren.svelte +99 -0
  4. package/dist/components/dataTable/listViewChildren.svelte.d.ts +9 -0
  5. package/dist/components/detailView/create/createManyView.svelte +2 -2
  6. package/dist/components/detailView/update/detailViewChildren.svelte +77 -0
  7. package/dist/components/detailView/update/detailViewChildren.svelte.d.ts +7 -0
  8. package/dist/components/detailView/update/updateDetailView.svelte +2 -2
  9. package/dist/components/routes/collections/collections.svelte +44 -23
  10. package/dist/components/routes/data_model/dataModel.svelte +2 -8
  11. package/dist/components/routes/workflows/workflows.svelte +24 -11
  12. package/dist/components/sidebar/sidebar.svelte +12 -5
  13. package/dist/components/sidebar/sidebar.svelte.d.ts +1 -2
  14. package/dist/components/sidebar/sidebarElements.svelte +50 -75
  15. package/dist/components/sidebar/sidebarElements.svelte.d.ts +10 -3
  16. package/package.json +2 -2
  17. package/src/lib/components/dataTable/dataTable.svelte +8 -6
  18. package/src/lib/components/dataTable/listViewChildren.svelte +99 -0
  19. package/src/lib/components/detailView/create/createManyView.svelte +2 -2
  20. package/src/lib/components/detailView/update/detailViewChildren.svelte +77 -0
  21. package/src/lib/components/detailView/update/updateDetailView.svelte +2 -2
  22. package/src/lib/components/routes/collections/collections.svelte +44 -23
  23. package/src/lib/components/routes/data_model/dataModel.svelte +2 -8
  24. package/src/lib/components/routes/workflows/workflows.svelte +24 -11
  25. package/src/lib/components/sidebar/sidebar.svelte +12 -5
  26. package/src/lib/components/sidebar/sidebarElements.svelte +50 -75
  27. package/dist/components/dataTable/childRecords.svelte +0 -142
  28. package/dist/components/dataTable/childRecords.svelte.d.ts +0 -9
  29. package/dist/components/detailView/update/children.svelte +0 -96
  30. package/dist/components/detailView/update/children.svelte.d.ts +0 -7
  31. package/src/lib/components/dataTable/childRecords.svelte +0 -142
  32. package/src/lib/components/detailView/update/children.svelte +0 -96
@@ -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 type SideBarData = Array<SideBarElement>;
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
- const elementsToShow = data.reduce<Array<string | SideBarElement>>(
34
- (acc, item, index) => {
35
- const firstPath = item.path?.split("/")[0];
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
- let expandedHeights: number[] = $state(Array(data?.length).fill(0));
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 elementsToShow.length}
68
- {#each elementsToShow as element, index}
69
- {#if typeof element === "string"}
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
- flex items-center justify-between p-2 gap-2 text-muted-foreground
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
- <div class="text-xs">
79
- {directoryName}
80
- </div>
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
- {#if getDirElements(directoryName)}
84
- <div
85
- class="overflow-hidden"
86
- style="
87
- height: {true ? expandedHeights[index] : 0}px;
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
- {/if}
84
+ </div>
102
85
  {:else}
103
- {@const elementPath = [...path, element]}
104
- {@const isselected = location.url.pathname === element.href}
86
+ {@const isselected = location.url.pathname === node.href}
105
87
  <Button
106
- onclick={() => handleElementClick(element)}
107
- href={element.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={element.name}
95
+ title={node.name}
114
96
  >
115
97
  <div class="flex items-center gap-2 truncate">
116
- {#if element.icon}
117
- <element.icon size="17.5" />
98
+ {#if node.icon}
99
+ <node.icon size="17.5" />
118
100
  {/if}
119
- <div
120
- class="
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(element)}
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 type SideBarData = Array<SideBarElement>;
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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lobb-js/studio",
3
3
  "license": "UNLICENSED",
4
- "version": "0.25.0",
4
+ "version": "0.26.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.28.0",
45
+ "@lobb-js/core": "^0.29.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",
@@ -7,7 +7,7 @@
7
7
  import { getCollectionColumns, getCollectionParamsFields } from "./utils";
8
8
  import { Pencil, Trash } from "lucide-svelte";
9
9
  import * as icons from "lucide-svelte";
10
- import ChildRecords from "./childRecords.svelte";
10
+ import ListViewChildren from "./listViewChildren.svelte";
11
11
  import FieldCell from "./fieldCell.svelte";
12
12
  import Skeleton from "../ui/skeleton/skeleton.svelte";
13
13
  import Button from "../ui/button/button.svelte";
@@ -25,6 +25,7 @@
25
25
  interface Props {
26
26
  collectionName: string;
27
27
  filter?: any;
28
+ searchParams?: Record<string, any>;
28
29
  showHeader?: boolean;
29
30
  showFooter?: boolean;
30
31
  unifiedBgColor?: "bg-muted/30" | "bg-background";
@@ -36,6 +37,7 @@
36
37
  let {
37
38
  collectionName,
38
39
  filter,
40
+ searchParams,
39
41
  showHeader = true,
40
42
  showFooter = true,
41
43
  unifiedBgColor,
@@ -57,6 +59,7 @@
57
59
  sort: {},
58
60
  limit: "100",
59
61
  page: 1,
62
+ ...searchParams,
60
63
  });
61
64
 
62
65
  $effect(() => {
@@ -73,10 +76,9 @@
73
76
  );
74
77
  let dataTableContainerWidth: number = $state(0);
75
78
  let dataTableWidth: number = $state(0);
76
- const doesCollectionHasChildren = Boolean(
77
- ctx.meta.relations.find(
78
- (relation) => relation.to.collection === collectionName,
79
- ),
79
+ const doesCollectionHasChildren = $derived(
80
+ (ctx.meta.collections[collectionName]?.children ?? [])
81
+ .some((c: any) => c.type === "fk" || c.type === "m2m")
80
82
  );
81
83
 
82
84
  // requests the data from the server when the params is changed
@@ -232,7 +234,7 @@
232
234
  />
233
235
  {/snippet}
234
236
  {#snippet collapsible(entry)}
235
- <ChildRecords
237
+ <ListViewChildren
236
238
  {collectionName}
237
239
  recordId={entry.id}
238
240
  width={dataTableWidth > dataTableContainerWidth
@@ -0,0 +1,99 @@
1
+ <script lang="ts">
2
+ import { getStudioContext } from "../../context";
3
+ import { ChevronRight, Table, Plus } 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");
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
+ </button>
53
+ {#if child.type === "fk"}
54
+ <div class="flex items-center px-2">
55
+ <CreateDetailViewButton
56
+ collectionName={child.collection}
57
+ variant="ghost"
58
+ class="h-7 px-3 text-xs font-normal"
59
+ Icon={Plus}
60
+ values={{ [child.field]: { id: recordId } }}
61
+ onSuccessfullSave={async () => { refreshDataTable = !refreshDataTable; }}
62
+ >
63
+ Create
64
+ </CreateDetailViewButton>
65
+ </div>
66
+ {/if}
67
+ </div>
68
+ {#if expandedRows[index]}
69
+ <div class="flex max-h-96 overflow-auto {lastRow ? '' : 'border-b'}">
70
+ <div
71
+ class="border-r {unifiedBgColor ? unifiedBgColor : ''}"
72
+ style="width: 100vw; max-width: 40px"
73
+ ></div>
74
+ <div class="flex-1" style="width: {tableHeaderWidth - 40}px;">
75
+ {#key refreshDataTable}
76
+ <ExtensionsComponents
77
+ name="listView.entry.children.{child.collection}"
78
+ collectionName={child.collection}
79
+ searchParams={{ children_of: collectionName, parent_id: recordId }}
80
+ utils={getExtensionUtils(lobb, ctx)}
81
+ >
82
+ <DataTable
83
+ collectionName={child.collection}
84
+ searchParams={{ children_of: collectionName, parent_id: recordId }}
85
+ showHeader={false}
86
+ showFooter={false}
87
+ showDelete={child.type === "fk"}
88
+ {unifiedBgColor}
89
+ tableProps={{ showLastRowBorder: false, showLastColumnBorder: false, showCheckboxes: false }}
90
+ />
91
+ </ExtensionsComponents>
92
+ {/key}
93
+ </div>
94
+ </div>
95
+ {/if}
96
+ </div>
97
+ {/each}
98
+ </div>
99
+ </div>
@@ -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 ChildRecords from "../../../components/dataTable/childRecords.svelte";
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
- <ChildRecords
235
+ <ListViewChildren
236
236
  {collectionName}
237
237
  recordId={entry.id}
238
238
  width={tableWidth}
@@ -0,0 +1,77 @@
1
+ <script lang="ts">
2
+ import DataTable from "../../../components/dataTable/dataTable.svelte";
3
+ import { getStudioContext } from "../../../context";
4
+ import { Link, Plus, TableIcon } from "lucide-svelte";
5
+ import CreateDetailViewButton from "../create/createDetailViewButton.svelte";
6
+ import ExtensionsComponents from "../../../components/extensionsComponents.svelte";
7
+ import { getExtensionUtils } from "../../../extensions/extensionUtils";
8
+
9
+ const { ctx, lobb } = getStudioContext();
10
+
11
+ interface LocalProp {
12
+ collectionName: string;
13
+ entry: any;
14
+ }
15
+
16
+ let { collectionName, entry }: LocalProp = $props();
17
+
18
+ const children = (ctx.meta.collections[collectionName]?.children ?? [])
19
+ .filter((c: any) => c.type === "fk" || c.type === "m2m");
20
+
21
+ const refresh: boolean[] = $state(new Array(children.length).fill(true));
22
+ </script>
23
+
24
+ {#if children.length}
25
+ <div class="flex flex-col gap-4 border-t p-4">
26
+ <div class="flex items-center gap-2">
27
+ <Link size="17.5" />
28
+ <div>Sub Records</div>
29
+ </div>
30
+ <div class="flex flex-col gap-4">
31
+ {#each children as child, index}
32
+ <ExtensionsComponents
33
+ name="detailView.update.subRecords.{child.collection}"
34
+ utils={getExtensionUtils(lobb, ctx)}
35
+ collectionName={child.collection}
36
+ searchParams={{ children_of: collectionName, parent_id: entry.id }}
37
+ class="bg-muted/30 border rounded-md overflow-hidden"
38
+ >
39
+ <div class="border rounded-md overflow-clip">
40
+ <div class="flex items-center justify-between px-2 h-10 bg-muted/30 border-b">
41
+ <div class="flex-1 flex h-full items-center gap-2">
42
+ <TableIcon class="text-muted-foreground" size="17.5" />
43
+ <div class="text-sm text-muted-foreground">{child.collection}</div>
44
+ </div>
45
+ {#if child.type === "fk"}
46
+ <div class="flex gap-2">
47
+ <CreateDetailViewButton
48
+ variant="ghost"
49
+ class="h-7 px-2 font-normal text-xs"
50
+ Icon={Plus}
51
+ collectionName={child.collection}
52
+ onSuccessfullSave={async () => { refresh[index] = !refresh[index]; }}
53
+ >
54
+ Create
55
+ </CreateDetailViewButton>
56
+ </div>
57
+ {/if}
58
+ </div>
59
+ <div class="max-h-72 overflow-auto rounded-md">
60
+ {#key refresh[index]}
61
+ <DataTable
62
+ collectionName={child.collection}
63
+ searchParams={{ children_of: collectionName, parent_id: entry.id }}
64
+ unifiedBgColor="bg-muted/30"
65
+ showHeader={false}
66
+ showFooter={false}
67
+ showDelete={child.type === "fk"}
68
+ tableProps={{ showLastColumnBorder: false, showLastRowBorder: false, showCheckboxes: false }}
69
+ />
70
+ {/key}
71
+ </div>
72
+ </div>
73
+ </ExtensionsComponents>
74
+ {/each}
75
+ </div>
76
+ </div>
77
+ {/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 Children from "../update/children.svelte";
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";
@@ -145,7 +145,7 @@
145
145
  {/each}
146
146
  </div>
147
147
  {#if showRelatedRecords}
148
- <Children {collectionName} {entry} />
148
+ <DetailViewChildren {collectionName} {entry} />
149
149
  {/if}
150
150
  </div>
151
151
  <div class="flex h-12 items-center justify-end gap-2 border-t px-4">
@@ -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
- let collectionsOwners: SideBarData = Object.entries(collections).map(
17
- ([collectionName, collectionValue]) => {
18
- return {
19
- name: collectionName,
20
- path: collectionValue.category ?? collectionValue.owner,
21
- icon: Table,
22
- href: `/studio/collections/${collectionName}`,
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
- return item;
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 collectionsOwners;
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
- name: "graph",
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
- sidebarData = workflows.map((workflow) => {
35
- return {
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
- path: workflow.directory,
38
- onclick: () => {
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) {