@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.
Files changed (39) hide show
  1. package/dist/components/dataTable/dataTable.svelte +77 -14
  2. package/dist/components/dataTable/dataTable.svelte.d.ts +25 -0
  3. package/dist/components/dataTable/header.svelte +88 -24
  4. package/dist/components/dataTable/header.svelte.d.ts +4 -0
  5. package/dist/components/dataTable/listViewChildren.svelte +106 -0
  6. package/dist/components/dataTable/listViewChildren.svelte.d.ts +9 -0
  7. package/dist/components/dataTable/table.svelte +1 -1
  8. package/dist/components/detailView/create/createManyView.svelte +2 -2
  9. package/dist/components/detailView/update/detailViewChildren.svelte +72 -0
  10. package/dist/components/detailView/update/detailViewChildren.svelte.d.ts +14 -0
  11. package/dist/components/detailView/update/updateDetailView.svelte +6 -3
  12. package/dist/components/routes/collections/collections.svelte +44 -23
  13. package/dist/components/routes/data_model/dataModel.svelte +2 -8
  14. package/dist/components/routes/workflows/workflows.svelte +24 -11
  15. package/dist/components/sidebar/sidebar.svelte +12 -5
  16. package/dist/components/sidebar/sidebar.svelte.d.ts +1 -2
  17. package/dist/components/sidebar/sidebarElements.svelte +50 -75
  18. package/dist/components/sidebar/sidebarElements.svelte.d.ts +10 -3
  19. package/dist/utils.js +2 -1
  20. package/package.json +2 -2
  21. package/src/lib/components/dataTable/dataTable.svelte +77 -14
  22. package/src/lib/components/dataTable/header.svelte +88 -24
  23. package/src/lib/components/dataTable/listViewChildren.svelte +106 -0
  24. package/src/lib/components/dataTable/table.svelte +1 -1
  25. package/src/lib/components/detailView/create/createManyView.svelte +2 -2
  26. package/src/lib/components/detailView/update/detailViewChildren.svelte +72 -0
  27. package/src/lib/components/detailView/update/updateDetailView.svelte +6 -3
  28. package/src/lib/components/routes/collections/collections.svelte +44 -23
  29. package/src/lib/components/routes/data_model/dataModel.svelte +2 -8
  30. package/src/lib/components/routes/workflows/workflows.svelte +24 -11
  31. package/src/lib/components/sidebar/sidebar.svelte +12 -5
  32. package/src/lib/components/sidebar/sidebarElements.svelte +50 -75
  33. package/src/lib/utils.ts +2 -1
  34. package/dist/components/dataTable/childRecords.svelte +0 -142
  35. package/dist/components/dataTable/childRecords.svelte.d.ts +0 -9
  36. package/dist/components/detailView/update/children.svelte +0 -96
  37. package/dist/components/detailView/update/children.svelte.d.ts +0 -7
  38. package/src/lib/components/dataTable/childRecords.svelte +0 -142
  39. 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>
package/src/lib/utils.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { clsx, type ClassValue } from "clsx";
2
2
  import { twMerge } from "tailwind-merge";
3
+ import { isEqual } from "lodash";
3
4
 
4
5
  import { MediaQuery } from 'svelte/reactivity';
5
6
 
@@ -34,7 +35,7 @@ export function calculateDrawerWidth() {
34
35
  export function getChangedProperties(oldObj: Record<string, any>, newObj: Record<string, any>) {
35
36
  const changes: Record<string, any> = {};
36
37
  for (const key of Object.keys(newObj)) {
37
- if (oldObj[key] !== newObj[key]) {
38
+ if (!isEqual(oldObj[key], newObj[key])) {
38
39
  changes[key] = newObj[key];
39
40
  }
40
41
  }
@@ -1,142 +0,0 @@
1
- <script lang="ts">
2
- import { getStudioContext } from "../../context";
3
- import { ChevronRight, Plus, Smartphone, Table } 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 relations = ctx.meta.relations.filter(
21
- (relation) => relation.to.collection === collectionName,
22
- );
23
- let expandedRows: boolean[] = $state(
24
- new Array(relations.length).fill(false),
25
- );
26
- let refreshDataTable = $state(true);
27
- let tableHeaderWidth = $state(0);
28
- </script>
29
-
30
- <div class="flex" style="width: {width}px;">
31
- <div
32
- class="
33
- flex justify-center border-r
34
- {unifiedBgColor ? unifiedBgColor : 'bg-background'}
35
- "
36
- style="width: 40px"
37
- ></div>
38
- <div class="flex-1 flex flex-col">
39
- {#each relations as relation, index}
40
- {@const lastRow = relations.length - 1 === index}
41
- {@const fromCollection = relation.from.collection}
42
- {@const fromField = relation.from.field}
43
- <div
44
- class="
45
- overflow-hidden
46
- {unifiedBgColor ? unifiedBgColor : 'bg-background'}
47
- "
48
- >
49
- <div
50
- bind:clientWidth={tableHeaderWidth}
51
- class="
52
- flex justify-between items-center gap-2 text-sm h-10
53
- {expandedRows[index] || !lastRow ? 'border-b' : ''}
54
- "
55
- >
56
- <button
57
- class="flex gap-2 px-2 flex-1 h-full items-center"
58
- onclick={() => {
59
- expandedRows[index] = !expandedRows[index];
60
- }}
61
- >
62
- <ChevronRight
63
- size="17.5"
64
- class="text-muted-foreground transition-transform"
65
- style={expandedRows[index]
66
- ? "transform: rotate(90deg);"
67
- : "transform: rotate(0deg);"}
68
- />
69
- <Table size="17.5" class="text-muted-foreground" />
70
- <div class="text-muted-foreground">
71
- {relation.from.collection}
72
- </div>
73
- </button>
74
- <div class="flex items-center px-2">
75
- <CreateDetailViewButton
76
- collectionName={relation.from.collection}
77
- variant="ghost"
78
- class="h-7 px-3 text-xs font-normal"
79
- Icon={Plus}
80
- values={{
81
- [fromField]: {
82
- id: recordId,
83
- },
84
- }}
85
- onSuccessfullSave={async () => {
86
- refreshDataTable = !refreshDataTable;
87
- }}
88
- >
89
- Create
90
- </CreateDetailViewButton>
91
- </div>
92
- </div>
93
- {#if expandedRows[index]}
94
- <div
95
- class="
96
- flex max-h-96 overflow-auto
97
- {lastRow ? '' : 'border-b'}
98
- "
99
- >
100
- <div
101
- class="border-r {unifiedBgColor
102
- ? unifiedBgColor
103
- : ''}"
104
- style="width: 100vw; max-width: 40px"
105
- ></div>
106
- <div
107
- class="flex-1"
108
- style="width: {tableHeaderWidth - 40}px;"
109
- >
110
- {#key refreshDataTable}
111
- <ExtensionsComponents
112
- name="listView.entry.children.{fromCollection}"
113
- collectionName={fromCollection}
114
- filter={{
115
- [fromField]: recordId,
116
- }}
117
- utils={getExtensionUtils(lobb, ctx)}
118
- >
119
- <DataTable
120
- collectionName={fromCollection}
121
- filter={{
122
- [fromField]: recordId,
123
- }}
124
- showHeader={false}
125
- showFooter={false}
126
- showDelete={true}
127
- {unifiedBgColor}
128
- tableProps={{
129
- showLastRowBorder: false,
130
- showLastColumnBorder: false,
131
- showCheckboxes: false,
132
- }}
133
- />
134
- </ExtensionsComponents>
135
- {/key}
136
- </div>
137
- </div>
138
- {/if}
139
- </div>
140
- {/each}
141
- </div>
142
- </div>
@@ -1,9 +0,0 @@
1
- interface Props {
2
- collectionName: string;
3
- recordId: string;
4
- width: number;
5
- unifiedBgColor?: "bg-muted/30" | "bg-background";
6
- }
7
- declare const ChildRecords: import("svelte").Component<Props, {}, "">;
8
- type ChildRecords = ReturnType<typeof ChildRecords>;
9
- export default ChildRecords;
@@ -1,96 +0,0 @@
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 childrenRelations = ctx.meta.relations.filter(
19
- (relation) => relation.to.collection === collectionName,
20
- );
21
- const refresh: boolean[] = $state(
22
- new Array(childrenRelations.length).fill(true),
23
- );
24
- </script>
25
-
26
- {#if childrenRelations.length}
27
- <div class="flex flex-col gap-4 border-t p-4">
28
- <div class="flex items-center gap-2">
29
- <Link size="17.5" />
30
- <div>Sub Records</div>
31
- </div>
32
- <div class="flex flex-col gap-4">
33
- {#each childrenRelations as relation, index}
34
- {@const childCollection = relation.from.collection}
35
- {@const childField = relation.from.field}
36
- <ExtensionsComponents
37
- name="detailView.update.subRecords.{childCollection}"
38
- utils={getExtensionUtils(lobb, ctx)}
39
- collectionName={childCollection}
40
- filter={{
41
- [childField]: entry.id,
42
- }}
43
- class="bg-muted/30 border rounded-md overflow-hidden"
44
- >
45
- <div class="border rounded-md overflow-clip">
46
- <div
47
- class="flex items-center justify-between px-2 h-10 bg-muted/30 border-b"
48
- >
49
- <div class="flex-1 flex h-full items-center gap-2">
50
- <TableIcon
51
- class="text-muted-foreground"
52
- size="17.5"
53
- />
54
- <div class="text-sm text-muted-foreground">
55
- {childCollection}
56
- </div>
57
- </div>
58
- <div class="flex gap-2">
59
- <CreateDetailViewButton
60
- variant="ghost"
61
- class="h-7 px-2 font-normal text-xs"
62
- Icon={Plus}
63
- collectionName={childCollection}
64
- onSuccessfullSave={async () => {
65
- refresh[index] = !refresh[index];
66
- }}
67
- >
68
- Create
69
- </CreateDetailViewButton>
70
- </div>
71
- </div>
72
- <div class="max-h-72 overflow-auto rounded-md">
73
- {#key refresh[index]}
74
- <DataTable
75
- collectionName={childCollection}
76
- filter={{
77
- [childField]: entry.id,
78
- }}
79
- unifiedBgColor="bg-muted/30"
80
- showHeader={false}
81
- showFooter={false}
82
- showDelete={true}
83
- tableProps={{
84
- showLastColumnBorder: false,
85
- showLastRowBorder: false,
86
- showCheckboxes: false,
87
- }}
88
- />
89
- {/key}
90
- </div>
91
- </div>
92
- </ExtensionsComponents>
93
- {/each}
94
- </div>
95
- </div>
96
- {/if}
@@ -1,7 +0,0 @@
1
- interface LocalProp {
2
- collectionName: string;
3
- entry: any;
4
- }
5
- declare const Children: import("svelte").Component<LocalProp, {}, "">;
6
- type Children = ReturnType<typeof Children>;
7
- export default Children;
@@ -1,142 +0,0 @@
1
- <script lang="ts">
2
- import { getStudioContext } from "../../context";
3
- import { ChevronRight, Plus, Smartphone, Table } 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 relations = ctx.meta.relations.filter(
21
- (relation) => relation.to.collection === collectionName,
22
- );
23
- let expandedRows: boolean[] = $state(
24
- new Array(relations.length).fill(false),
25
- );
26
- let refreshDataTable = $state(true);
27
- let tableHeaderWidth = $state(0);
28
- </script>
29
-
30
- <div class="flex" style="width: {width}px;">
31
- <div
32
- class="
33
- flex justify-center border-r
34
- {unifiedBgColor ? unifiedBgColor : 'bg-background'}
35
- "
36
- style="width: 40px"
37
- ></div>
38
- <div class="flex-1 flex flex-col">
39
- {#each relations as relation, index}
40
- {@const lastRow = relations.length - 1 === index}
41
- {@const fromCollection = relation.from.collection}
42
- {@const fromField = relation.from.field}
43
- <div
44
- class="
45
- overflow-hidden
46
- {unifiedBgColor ? unifiedBgColor : 'bg-background'}
47
- "
48
- >
49
- <div
50
- bind:clientWidth={tableHeaderWidth}
51
- class="
52
- flex justify-between items-center gap-2 text-sm h-10
53
- {expandedRows[index] || !lastRow ? 'border-b' : ''}
54
- "
55
- >
56
- <button
57
- class="flex gap-2 px-2 flex-1 h-full items-center"
58
- onclick={() => {
59
- expandedRows[index] = !expandedRows[index];
60
- }}
61
- >
62
- <ChevronRight
63
- size="17.5"
64
- class="text-muted-foreground transition-transform"
65
- style={expandedRows[index]
66
- ? "transform: rotate(90deg);"
67
- : "transform: rotate(0deg);"}
68
- />
69
- <Table size="17.5" class="text-muted-foreground" />
70
- <div class="text-muted-foreground">
71
- {relation.from.collection}
72
- </div>
73
- </button>
74
- <div class="flex items-center px-2">
75
- <CreateDetailViewButton
76
- collectionName={relation.from.collection}
77
- variant="ghost"
78
- class="h-7 px-3 text-xs font-normal"
79
- Icon={Plus}
80
- values={{
81
- [fromField]: {
82
- id: recordId,
83
- },
84
- }}
85
- onSuccessfullSave={async () => {
86
- refreshDataTable = !refreshDataTable;
87
- }}
88
- >
89
- Create
90
- </CreateDetailViewButton>
91
- </div>
92
- </div>
93
- {#if expandedRows[index]}
94
- <div
95
- class="
96
- flex max-h-96 overflow-auto
97
- {lastRow ? '' : 'border-b'}
98
- "
99
- >
100
- <div
101
- class="border-r {unifiedBgColor
102
- ? unifiedBgColor
103
- : ''}"
104
- style="width: 100vw; max-width: 40px"
105
- ></div>
106
- <div
107
- class="flex-1"
108
- style="width: {tableHeaderWidth - 40}px;"
109
- >
110
- {#key refreshDataTable}
111
- <ExtensionsComponents
112
- name="listView.entry.children.{fromCollection}"
113
- collectionName={fromCollection}
114
- filter={{
115
- [fromField]: recordId,
116
- }}
117
- utils={getExtensionUtils(lobb, ctx)}
118
- >
119
- <DataTable
120
- collectionName={fromCollection}
121
- filter={{
122
- [fromField]: recordId,
123
- }}
124
- showHeader={false}
125
- showFooter={false}
126
- showDelete={true}
127
- {unifiedBgColor}
128
- tableProps={{
129
- showLastRowBorder: false,
130
- showLastColumnBorder: false,
131
- showCheckboxes: false,
132
- }}
133
- />
134
- </ExtensionsComponents>
135
- {/key}
136
- </div>
137
- </div>
138
- {/if}
139
- </div>
140
- {/each}
141
- </div>
142
- </div>