@lobb-js/studio 0.29.0 → 0.29.1

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 (109) hide show
  1. package/README.md +1 -0
  2. package/dist/actions.d.ts +2 -0
  3. package/dist/components/Studio.svelte +39 -43
  4. package/dist/components/StudioRoot.svelte +19 -0
  5. package/dist/components/StudioRoot.svelte.d.ts +6 -0
  6. package/dist/components/breadCrumbs.svelte +5 -4
  7. package/dist/components/codeEditor.svelte +1 -1
  8. package/dist/components/dataTable/dataTable.svelte +35 -20
  9. package/dist/components/dataTable/dataTable.svelte.d.ts +2 -1
  10. package/dist/components/dataTable/dataTableTabs.svelte +4 -2
  11. package/dist/components/dataTable/dataTableTabs.svelte.d.ts +2 -0
  12. package/dist/components/dataTable/header.svelte +15 -11
  13. package/dist/components/dataTable/header.svelte.d.ts +1 -0
  14. package/dist/components/dataTable/listViewChildren.svelte +4 -6
  15. package/dist/components/dataTable/listViewChildren.svelte.d.ts +0 -1
  16. package/dist/components/dataTable/table.svelte +8 -10
  17. package/dist/components/dataTable/table.svelte.d.ts +0 -1
  18. package/dist/components/dataTableDrawer/dataTableDrawer.svelte +4 -1
  19. package/dist/components/dataTableDrawer/dataTableDrawer.svelte.d.ts +2 -0
  20. package/dist/components/detailView/create/children.svelte +1 -1
  21. package/dist/components/detailView/create/createDetailView.svelte +19 -61
  22. package/dist/components/detailView/create/createManyView.svelte +2 -4
  23. package/dist/components/detailView/detailView.svelte +81 -0
  24. package/dist/components/detailView/detailView.svelte.d.ts +8 -0
  25. package/dist/components/detailView/fieldInput.svelte +10 -10
  26. package/dist/components/detailView/fieldInputReplacement.svelte +7 -7
  27. package/dist/components/detailView/passwordInput.svelte +1 -1
  28. package/dist/components/detailView/update/updateDetailView.svelte +32 -69
  29. package/dist/components/diffViewer.svelte +1 -1
  30. package/dist/components/extensionsComponents.svelte +3 -1
  31. package/dist/components/foreingKeyInput.svelte +2 -2
  32. package/dist/components/importButton.svelte +12 -9
  33. package/dist/components/landing.svelte +7 -0
  34. package/dist/components/landing.svelte.d.ts +6 -14
  35. package/dist/components/miniSidebar.svelte +86 -15
  36. package/dist/components/miniSidebar.svelte.d.ts +2 -17
  37. package/dist/components/polymorphicInput.svelte +1 -1
  38. package/dist/components/rangeCalendarButton.svelte +10 -10
  39. package/dist/components/richTextEditor.svelte +1 -1
  40. package/dist/components/routes/collections/collections.svelte +32 -10
  41. package/dist/components/routes/data_model/dataModel.svelte +6 -28
  42. package/dist/components/routes/data_model/dataModel.svelte.d.ts +17 -2
  43. package/dist/components/routes/extensions/publicExtension.svelte +19 -0
  44. package/dist/components/routes/extensions/publicExtension.svelte.d.ts +13 -0
  45. package/dist/components/routes/home.svelte +2 -2
  46. package/dist/components/routes/workflows/workflows.svelte +4 -4
  47. package/dist/components/sidebar/sidebar.svelte +1 -1
  48. package/dist/components/sidebar/sidebarElements.svelte +4 -4
  49. package/dist/components/singletone.svelte +4 -6
  50. package/dist/components/ui/button/button.svelte +2 -3
  51. package/dist/components/workflowEditor.svelte +2 -2
  52. package/dist/eventSystem.d.ts +1 -1
  53. package/dist/eventSystem.js +7 -5
  54. package/dist/extensions/extension.types.d.ts +38 -14
  55. package/dist/extensions/extensionUtils.js +4 -2
  56. package/dist/index.d.ts +3 -1
  57. package/dist/index.js +3 -1
  58. package/dist/store.types.d.ts +1 -1
  59. package/dist/studioLifecycle.svelte.d.ts +2 -0
  60. package/dist/studioLifecycle.svelte.js +15 -0
  61. package/package.json +3 -4
  62. package/src/app.css +3 -0
  63. package/src/lib/actions.ts +2 -0
  64. package/src/lib/components/Studio.svelte +39 -43
  65. package/src/lib/components/StudioRoot.svelte +19 -0
  66. package/src/lib/components/breadCrumbs.svelte +5 -4
  67. package/src/lib/components/codeEditor.svelte +1 -1
  68. package/src/lib/components/dataTable/dataTable.svelte +35 -20
  69. package/src/lib/components/dataTable/dataTableTabs.svelte +4 -2
  70. package/src/lib/components/dataTable/header.svelte +15 -11
  71. package/src/lib/components/dataTable/listViewChildren.svelte +4 -6
  72. package/src/lib/components/dataTable/table.svelte +8 -10
  73. package/src/lib/components/dataTableDrawer/dataTableDrawer.svelte +4 -1
  74. package/src/lib/components/detailView/create/children.svelte +1 -1
  75. package/src/lib/components/detailView/create/createDetailView.svelte +19 -61
  76. package/src/lib/components/detailView/create/createManyView.svelte +2 -4
  77. package/src/lib/components/detailView/detailView.svelte +81 -0
  78. package/src/lib/components/detailView/fieldInput.svelte +10 -10
  79. package/src/lib/components/detailView/fieldInputReplacement.svelte +7 -7
  80. package/src/lib/components/detailView/passwordInput.svelte +1 -1
  81. package/src/lib/components/detailView/update/updateDetailView.svelte +32 -69
  82. package/src/lib/components/diffViewer.svelte +1 -1
  83. package/src/lib/components/extensionsComponents.svelte +3 -1
  84. package/src/lib/components/foreingKeyInput.svelte +2 -2
  85. package/src/lib/components/importButton.svelte +12 -9
  86. package/src/lib/components/landing.svelte +7 -0
  87. package/src/lib/components/miniSidebar.svelte +86 -15
  88. package/src/lib/components/polymorphicInput.svelte +1 -1
  89. package/src/lib/components/rangeCalendarButton.svelte +10 -10
  90. package/src/lib/components/richTextEditor.svelte +1 -1
  91. package/src/lib/components/routes/collections/collections.svelte +32 -10
  92. package/src/lib/components/routes/data_model/dataModel.svelte +6 -28
  93. package/src/lib/components/routes/extensions/publicExtension.svelte +19 -0
  94. package/src/lib/components/routes/home.svelte +2 -2
  95. package/src/lib/components/routes/workflows/workflows.svelte +4 -4
  96. package/src/lib/components/sidebar/sidebar.svelte +1 -1
  97. package/src/lib/components/sidebar/sidebarElements.svelte +4 -4
  98. package/src/lib/components/singletone.svelte +4 -6
  99. package/src/lib/components/ui/button/button.svelte +2 -3
  100. package/src/lib/components/workflowEditor.svelte +2 -2
  101. package/src/lib/eventSystem.ts +8 -7
  102. package/src/lib/extensions/extension.types.ts +39 -6
  103. package/src/lib/extensions/extensionUtils.ts +4 -2
  104. package/src/lib/index.ts +3 -1
  105. package/src/lib/store.types.ts +1 -1
  106. package/src/lib/studioLifecycle.svelte.ts +17 -0
  107. package/dist/components/routes/data_model/syncManager.svelte +0 -94
  108. package/dist/components/routes/data_model/syncManager.svelte.d.ts +0 -3
  109. package/src/lib/components/routes/data_model/syncManager.svelte +0 -94
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { AlertCircle, Check, FileText, LoaderCircle, Upload, X } from "lucide-svelte";
2
+ import { AlertCircle, Check, Download, FileText, LoaderCircle, Upload, X } from "lucide-svelte";
3
3
  import Button, { type ButtonProps } from "./ui/button/button.svelte";
4
4
  import { toast } from "svelte-sonner";
5
5
  import { getStudioContext } from "../context";
@@ -170,10 +170,15 @@
170
170
  }
171
171
  </script>
172
172
 
173
- <Button variant={rest.variant} class={rest.class} Icon={rest.Icon} onclick={showDrawer}>
174
- {#if rest.children}
175
- {@render rest.children()}
176
- {/if}
173
+ <Button variant={rest.variant} class={rest.class} onclick={showDrawer}>
174
+ <ExtensionsComponents
175
+ name="collections.import.button.content"
176
+ utils={getExtensionUtils(lobb, ctx)}
177
+ {collectionName}
178
+ >
179
+ <rest.Icon />
180
+ Import
181
+ </ExtensionsComponents>
177
182
  </Button>
178
183
 
179
184
  <Dialog.Root
@@ -231,7 +236,7 @@
231
236
  tabindex="0"
232
237
  onkeydown={(e) => e.key === "Enter" && fileInput.click()}
233
238
  >
234
- <Upload class="mb-3 h-8 w-8 text-muted-foreground" />
239
+ <Download class="mb-3 h-8 w-8 text-muted-foreground" />
235
240
  <p class="text-sm font-medium">Drop a file here or click to browse</p>
236
241
  <p class="mt-1 text-xs text-muted-foreground">Supports .csv and .json</p>
237
242
  </div>
@@ -244,7 +249,7 @@
244
249
  />
245
250
  {:else}
246
251
  <textarea
247
- class="block h-56 w-full resize-none rounded-md border bg-muted/30 p-3 font-mono text-sm focus:outline-none focus:ring-1 focus:ring-ring"
252
+ class="block h-56 w-full resize-none rounded-md border bg-muted-soft p-3 font-mono text-sm focus:outline-none focus:ring-1 focus:ring-ring"
248
253
  placeholder="Paste CSV or JSON here..."
249
254
  bind:value={pasteContent}
250
255
  ></textarea>
@@ -279,7 +284,6 @@
279
284
  data={transformedRows}
280
285
  columns={collectionColumns.filter((c) => c.id !== "id")}
281
286
  showCheckboxes={false}
282
- unifiedBgColor="bg-background"
283
287
  headerBorderTop={true}
284
288
  />
285
289
  </div>
@@ -328,7 +332,6 @@
328
332
  data={failedData}
329
333
  columns={[{ id: "__error", icon: AlertCircle }, ...collectionColumns.filter((c) => c.id !== "id")]}
330
334
  showCheckboxes={false}
331
- unifiedBgColor="bg-background"
332
335
  headerBorderTop={true}
333
336
  >
334
337
  {#snippet overrideCell(value, column)}
@@ -1,3 +1,10 @@
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+ onMount(() => {
4
+ document.getElementById("app-loading")?.remove();
5
+ });
6
+ </script>
7
+
1
8
  <div class="min-h-screen flex items-center justify-center bg-background text-foreground p-6">
2
9
  <div class="max-w-md text-center space-y-4">
3
10
  <h1 class="text-2xl font-semibold">Lobb Studio</h1>
@@ -1,18 +1,5 @@
1
- export default Landing;
2
- type Landing = SvelteComponent<{
3
- [x: string]: never;
4
- }, {
5
- [evt: string]: CustomEvent<any>;
6
- }, {}> & {
7
- $$bindings?: string | undefined;
8
- };
9
- declare const Landing: $$__sveltets_2_IsomorphicComponent<{
10
- [x: string]: never;
11
- }, {
12
- [evt: string]: CustomEvent<any>;
13
- }, {}, {}, string>;
14
1
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
15
- new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
16
3
  $$bindings?: Bindings;
17
4
  } & Exports;
18
5
  (internal: unknown, props: {
@@ -24,3 +11,8 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
24
11
  };
25
12
  z_$$bindings?: Bindings;
26
13
  }
14
+ declare const Landing: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type Landing = InstanceType<typeof Landing>;
18
+ export default Landing;
@@ -16,16 +16,19 @@
16
16
  import Separator from "./ui/separator/separator.svelte";
17
17
  import * as Tooltip from "./ui/tooltip";
18
18
  import * as Accordion from "./ui/accordion/index.js";
19
+ import { onMount } from "svelte";
19
20
 
20
21
  import { getStudioContext } from "../context";
21
22
  import { getDashboardNavs } from "../extensions/extensionUtils";
23
+ import { emitEvent } from "../eventSystem";
22
24
 
23
- const { ctx } = getStudioContext();
25
+ const { lobb, ctx } = getStudioContext();
24
26
  import { mediaQueries } from "../utils";
25
27
  import * as Popover from "./ui/popover";
26
- import { location } from "@wjfe/n-savant";
28
+ import { page } from "$app/state";
29
+ import { goto } from "$app/navigation";
27
30
 
28
- const sections: any = [
31
+ const rawSections: any[][] = [
29
32
  [
30
33
  {
31
34
  label: "Home",
@@ -39,13 +42,15 @@
39
42
  },
40
43
  {
41
44
  label: "Data Model",
42
- href: "/studio/datamodel/graph",
45
+ href: "/studio/datamodel",
43
46
  icon: Layers,
47
+ represents: "core_data_model",
44
48
  },
45
49
  {
46
50
  label: "Workflows",
47
51
  href: "/studio/workflows",
48
52
  icon: Workflow,
53
+ represents: "core_workflows",
49
54
  },
50
55
  ],
51
56
  [],
@@ -55,20 +60,72 @@
55
60
  const navs = getDashboardNavs(ctx);
56
61
 
57
62
  if (navs.top) {
58
- sections[0] = [...sections[0], ...navs.top];
63
+ rawSections[0] = [...rawSections[0], ...navs.top];
59
64
  }
60
65
  if (navs.middle) {
61
- sections[1] = [...sections[1], ...navs.middle];
66
+ rawSections[1] = [...rawSections[1], ...navs.middle];
62
67
  }
63
68
  if (navs.bottom) {
64
- sections[2] = [...sections[2], ...navs.bottom];
69
+ rawSections[2] = [...rawSections[2], ...navs.bottom];
65
70
  }
66
71
 
72
+ // Items without a `represents` are always visible. Items with one are
73
+ // gated by emitting auth.canAccess — the auth extension (or any
74
+ // drop-in replacement) decides based on the current user's session.
75
+ // Start empty so nothing flashes before the answers come back.
76
+ let sections: any[][] = $state([[], [], []]);
77
+
78
+ async function isItemVisible(item: any): Promise<boolean> {
79
+ if (!item.represents) return true;
80
+ const res = await emitEvent(
81
+ { lobb, ctx },
82
+ "auth.canAccess",
83
+ { collection: item.represents, action: "read" },
84
+ );
85
+ return res === true;
86
+ }
87
+
88
+ // Highlight the nav item matching the current URL. "/studio" requires an
89
+ // exact match (otherwise it would light up on every sub-route since it's a
90
+ // prefix of everything); other items use startsWith so sub-paths
91
+ // (e.g. /studio/collections/risks) still highlight their parent.
92
+ // Popover items with children are active when any of their children match.
93
+ const currentPath = $derived(page.url.pathname);
94
+ function isItemActive(item: any): boolean {
95
+ if (item.navs) return item.navs.some((c: any) => isItemActive(c));
96
+ if (!item.href) return false;
97
+ if (item.href === "/studio") return currentPath === "/studio";
98
+ return currentPath === item.href || currentPath.startsWith(item.href + "/");
99
+ }
100
+
101
+ // onMount is enough — Studio gets remounted on login/logout (see
102
+ // remountStudio in @lobb-js/studio), so by the time this component
103
+ // mounts, ctx.extensions.auth.user / permissions are already populated.
104
+ onMount(async () => {
105
+ const result: any[][] = [[], [], []];
106
+ for (let i = 0; i < rawSections.length; i++) {
107
+ for (const item of rawSections[i]) {
108
+ if (item.navs) {
109
+ const visibleChildren: any[] = [];
110
+ for (const child of item.navs) {
111
+ if (await isItemVisible(child)) visibleChildren.push(child);
112
+ }
113
+ if (visibleChildren.length && (await isItemVisible(item))) {
114
+ result[i].push({ ...item, navs: visibleChildren });
115
+ }
116
+ } else if (await isItemVisible(item)) {
117
+ result[i].push(item);
118
+ }
119
+ }
120
+ }
121
+ sections = result;
122
+ });
67
123
  </script>
68
124
 
69
125
  {#snippet section(section: any)}
70
126
  <div class="flex flex-col {isSmallScreen ? 'gap-0' : 'gap-2'}">
71
127
  {#each section as item}
128
+ {@const active = isItemActive(item)}
72
129
  {#if isSmallScreen}
73
130
  {#if !item.navs}
74
131
  <Button
@@ -76,11 +133,13 @@
76
133
  if (item.onclick) {
77
134
  item.onclick();
78
135
  } else {
79
- location.navigate(item.href);
136
+ goto(item.href);
80
137
  }
81
138
  isCollapsed = true;
82
139
  }}
83
- class="flex items-center justify-start flex-nowrap text-muted-foreground text-nowrap h-10 w-full"
140
+ class="flex items-center justify-start flex-nowrap text-nowrap h-10 w-full {active
141
+ ? 'bg-accent text-accent-foreground'
142
+ : 'text-muted-foreground'}"
84
143
  variant="ghost"
85
144
  size="icon"
86
145
  Icon={item.icon}
@@ -92,7 +151,9 @@
92
151
  <Accordion.Item class="border-b-0">
93
152
  <Accordion.Trigger class="justify-between p-0 h-10">
94
153
  <div
95
- class="flex items-center gap-2 text-muted-foreground"
154
+ class="flex items-center gap-2 {active
155
+ ? 'text-accent-foreground'
156
+ : 'text-muted-foreground'}"
96
157
  >
97
158
  <item.icon size="18" />
98
159
  <div class="text-nowrap">{item.label}</div>
@@ -100,16 +161,19 @@
100
161
  </Accordion.Trigger>
101
162
  <Accordion.Content class="pl-2 border-l">
102
163
  {#each item.navs as childItem}
164
+ {@const childActive = isItemActive(childItem)}
103
165
  <Button
104
166
  onclick={() => {
105
167
  if (childItem.onclick) {
106
168
  childItem.onclick();
107
169
  } else {
108
- location.navigate(item.href);
170
+ goto(item.href);
109
171
  }
110
172
  isCollapsed = true;
111
173
  }}
112
- class="flex items-center justify-start flex-nowrap text-muted-foreground text-nowrap h-8 w-full"
174
+ class="flex items-center justify-start flex-nowrap text-nowrap h-8 w-full {childActive
175
+ ? 'bg-accent text-accent-foreground'
176
+ : 'text-muted-foreground'}"
113
177
  variant="ghost"
114
178
  size="icon"
115
179
  Icon={childItem.icon}
@@ -133,7 +197,9 @@
133
197
  isCollapsed = true;
134
198
  }}
135
199
  href={item.href}
136
- class="text-muted-foreground"
200
+ class={active
201
+ ? 'bg-accent text-accent-foreground'
202
+ : 'text-muted-foreground'}
137
203
  variant="ghost"
138
204
  size="icon"
139
205
  Icon={item.icon}
@@ -142,7 +208,9 @@
142
208
  <Popover.Root>
143
209
  <Popover.Trigger>
144
210
  <Button
145
- class="text-muted-foreground"
211
+ class={active
212
+ ? 'bg-accent text-accent-foreground'
213
+ : 'text-muted-foreground'}
146
214
  variant="ghost"
147
215
  size="icon"
148
216
  Icon={item.icon}
@@ -155,12 +223,15 @@
155
223
  >
156
224
  <div class="py-1">
157
225
  {#each item.navs as childItem}
226
+ {@const childActive = isItemActive(childItem)}
158
227
  <div
159
228
  class="px-1 text-xs text-muted-foreground"
160
229
  >
161
230
  <Button
162
231
  variant="ghost"
163
- class="flex h-7 w-full justify-start p-2 text-xs font-normal text-muted-foreground"
232
+ class="flex h-7 w-full justify-start p-2 text-xs font-normal {childActive
233
+ ? 'bg-accent text-accent-foreground'
234
+ : 'text-muted-foreground'}"
164
235
  Icon={childItem.icon}
165
236
  onclick={() => {
166
237
  if (childItem.onclick) {
@@ -1,20 +1,5 @@
1
1
  export declare let collapseMiniSideBar: () => void;
2
2
  export declare let expandMiniSideBar: () => void;
3
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
4
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
5
- $$bindings?: Bindings;
6
- } & Exports;
7
- (internal: unknown, props: {
8
- $$events?: Events;
9
- $$slots?: Slots;
10
- }): Exports & {
11
- $set?: any;
12
- $on?: any;
13
- };
14
- z_$$bindings?: Bindings;
15
- }
16
- declare const MiniSidebar: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
17
- [evt: string]: CustomEvent<any>;
18
- }, {}, {}, string>;
19
- type MiniSidebar = InstanceType<typeof MiniSidebar>;
3
+ declare const MiniSidebar: import("svelte").Component<Record<string, never>, {}, "">;
4
+ type MiniSidebar = ReturnType<typeof MiniSidebar>;
20
5
  export default MiniSidebar;
@@ -70,7 +70,7 @@
70
70
  }
71
71
  </script>
72
72
 
73
- <div class="flex h-9 w-full items-center gap-1.5 rounded-md border pl-1.5 pr-9 text-xs bg-muted/30 {destructive ? 'border-destructive bg-destructive/10' : ''}">
73
+ <div class="flex h-9 w-full items-center gap-1.5 rounded-md border pl-1.5 pr-9 text-xs bg-muted-soft {destructive ? 'border-destructive bg-destructive/10' : ''}">
74
74
  <!-- Collection picker -->
75
75
  <Popover.Root bind:open={collectionPopoverOpen}>
76
76
  <Popover.Trigger>
@@ -59,7 +59,7 @@
59
59
  class="flex flex-col overflow-hidden text-muted-foreground"
60
60
  >
61
61
  <button
62
- class="text-start text-sm py-2 px-2 hover:bg-muted/30 hover:text-primary"
62
+ class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
63
63
  onclick={() => {
64
64
  const currentDate = today(getLocalTimeZone());
65
65
  value = {
@@ -71,7 +71,7 @@
71
71
  Today
72
72
  </button>
73
73
  <button
74
- class="text-start text-sm py-2 px-2 hover:bg-muted/30 hover:text-primary"
74
+ class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
75
75
  onclick={() => {
76
76
  const currentDate = today(getLocalTimeZone());
77
77
  value = {
@@ -83,7 +83,7 @@
83
83
  Yesterday
84
84
  </button>
85
85
  <button
86
- class="text-start text-sm py-2 px-2 hover:bg-muted/30 hover:text-primary"
86
+ class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
87
87
  onclick={() => {
88
88
  const currentDate = today(getLocalTimeZone());
89
89
  const weekStart = startOfWeek(currentDate, "en-US");
@@ -96,7 +96,7 @@
96
96
  This week (Sun - Today)
97
97
  </button>
98
98
  <button
99
- class="text-start text-sm py-2 px-2 hover:bg-muted/30 hover:text-primary"
99
+ class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
100
100
  onclick={() => {
101
101
  const currentDate = today(getLocalTimeZone());
102
102
  const thisWeekStart = startOfWeek(
@@ -119,7 +119,7 @@
119
119
  Last week (Sun - Sat)
120
120
  </button>
121
121
  <button
122
- class="text-start text-sm py-2 px-2 hover:bg-muted/30 hover:text-primary"
122
+ class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
123
123
  onclick={() => {
124
124
  const currentDate = today(getLocalTimeZone());
125
125
  value = {
@@ -131,7 +131,7 @@
131
131
  Last 7 days
132
132
  </button>
133
133
  <button
134
- class="text-start text-sm py-2 px-2 hover:bg-muted/30 hover:text-primary"
134
+ class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
135
135
  onclick={() => {
136
136
  const currentDate = today(getLocalTimeZone());
137
137
  value = {
@@ -143,7 +143,7 @@
143
143
  Last 30 days
144
144
  </button>
145
145
  <button
146
- class="text-start text-sm py-2 px-2 hover:bg-muted/30 hover:text-primary"
146
+ class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
147
147
  onclick={() => {
148
148
  const currentDate = today(getLocalTimeZone());
149
149
  value = {
@@ -155,7 +155,7 @@
155
155
  Last 90 days
156
156
  </button>
157
157
  <button
158
- class="text-start text-sm py-2 px-2 hover:bg-muted/30 hover:text-primary"
158
+ class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
159
159
  onclick={() => {
160
160
  const currentDate = today(getLocalTimeZone());
161
161
  value = {
@@ -167,7 +167,7 @@
167
167
  Last 12 months
168
168
  </button>
169
169
  <button
170
- class="text-start text-sm py-2 px-2 hover:bg-muted/30 hover:text-primary"
170
+ class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
171
171
  onclick={() => {
172
172
  const currentDate = today(getLocalTimeZone());
173
173
  const lastYearStart = currentDate
@@ -185,7 +185,7 @@
185
185
  Last Calendar year
186
186
  </button>
187
187
  <button
188
- class="text-start text-sm py-2 px-2 hover:bg-muted/30 hover:text-primary"
188
+ class="text-start text-sm py-2 px-2 hover:bg-muted-soft hover:text-primary"
189
189
  onclick={() => {
190
190
  const currentDate = today(getLocalTimeZone());
191
191
  const yearStart = currentDate.set({
@@ -120,7 +120,7 @@
120
120
  }
121
121
  </script>
122
122
 
123
- <div class="flex flex-col rounded-md border bg-muted/30">
123
+ <div class="flex flex-col rounded-md border bg-muted-soft">
124
124
  <div class="flex flex-wrap items-center gap-0.5 border-b p-1.5">
125
125
  {#if editor}
126
126
  <button
@@ -2,9 +2,11 @@
2
2
  import type { SideBarData, SideBarNode } from "../../sidebar/sidebarElements.svelte";
3
3
  import Sidebar from "../../sidebar/sidebar.svelte";
4
4
  import { getStudioContext } from "../../../context";
5
+ import { emitEvent } from "../../../eventSystem";
5
6
  import Collection from "./collection.svelte";
7
+ import { onMount } from "svelte";
6
8
 
7
- const { ctx } = getStudioContext();
9
+ const { lobb, ctx } = getStudioContext();
8
10
  import { Table, Cpu, LibraryBig } from "lucide-svelte";
9
11
  import * as Icons from "lucide-svelte";
10
12
 
@@ -15,21 +17,40 @@
15
17
 
16
18
  let { collectionName } = $props();
17
19
 
18
- const collectionsList = $state(getCollectionsList());
20
+ // Start empty so unreadable collections never flash. Populated after the
21
+ // auth.canAccess checks resolve below.
22
+ let collectionsList = $state<SideBarData>([]);
19
23
 
20
- function getCollectionsList(): SideBarData {
24
+ onMount(async () => {
21
25
  const collections = ctx.meta.collections;
22
26
 
23
- const groups = new Map<string, SideBarNode[]>();
27
+ // 1. Determine which collections the current user can read.
28
+ // Virtual collections (identifiers only — no rows) are excluded.
29
+ const allNames = Object.keys(collections).filter(
30
+ (name) => !collections[name].virtual,
31
+ );
32
+ const visibleNames = (
33
+ await Promise.all(
34
+ allNames.map(async (name) => {
35
+ const res = await emitEvent(
36
+ { lobb, ctx },
37
+ "auth.canAccess",
38
+ { collection: name, action: "read" },
39
+ );
40
+ return res === true ? name : null;
41
+ }),
42
+ )
43
+ ).filter((n): n is string => n !== null);
24
44
 
25
- for (const [name, value] of Object.entries(collections)) {
45
+ // 2. Group the visible collections by their category/owner.
46
+ const groups = new Map<string, SideBarNode[]>();
47
+ for (const name of visibleNames) {
48
+ const value = collections[name];
26
49
  let groupKey: string = (value as any).category ?? (value as any).owner;
27
50
  if (groupKey === "__project") groupKey = "project";
28
51
  else if (groupKey === "__core") groupKey = "core";
29
52
 
30
- if (!groups.has(groupKey)) {
31
- groups.set(groupKey, []);
32
- }
53
+ if (!groups.has(groupKey)) groups.set(groupKey, []);
33
54
  groups.get(groupKey)!.push({
34
55
  type: "element",
35
56
  name,
@@ -38,6 +59,7 @@
38
59
  });
39
60
  }
40
61
 
62
+ // 3. Build the sidebar tree and put `core` last.
41
63
  const result: SideBarData = [];
42
64
  for (const [groupKey, children] of groups) {
43
65
  const extensionIconName = ctx.meta.extensions?.[groupKey]?.icon;
@@ -51,12 +73,12 @@
51
73
  });
52
74
  }
53
75
 
54
- return result.sort((a, b) => {
76
+ collectionsList = result.sort((a, b) => {
55
77
  if ((a as any).name === "core") return 1;
56
78
  if ((b as any).name === "core") return -1;
57
79
  return 0;
58
80
  });
59
- }
81
+ });
60
82
  </script>
61
83
 
62
84
  <Sidebar title="Collections" data={collectionsList}>
@@ -1,34 +1,12 @@
1
1
  <script lang="ts">
2
2
  import { SvelteFlowProvider } from "@xyflow/svelte";
3
3
  import Flow from "./flow.svelte";
4
- import Sidebar from "../../sidebar/sidebar.svelte";
5
- import { location } from "@wjfe/n-savant";
6
- import SyncManager from "./syncManager.svelte";
7
- import SidebarTrigger from "../../sidebar/sidebarTrigger.svelte";
8
-
9
- const currentPage = $derived(location.url.pathname.replace("/studio", "").split("/")[2]);
10
4
  </script>
11
5
 
12
- <Sidebar
13
- title="Data Model"
14
- showSearch={false}
15
- data={[
16
- { type: "element", name: "graph", href: "/studio/datamodel/graph" },
17
- { type: "element", name: "query_editor", href: "/studio/datamodel/query_editor" },
18
- ]}
19
- >
20
- <div class="relative h-full w-full">
21
- {#if currentPage === "graph"}
22
- <SvelteFlowProvider>
23
- <div style:width="100%" style:height="100%">
24
- <Flow />
25
- </div>
26
- </SvelteFlowProvider>
27
- {:else if currentPage === "query_editor"}
28
- <SyncManager />
29
- {/if}
30
- <div class="absolute top-0 left-0 p-2.5">
31
- <SidebarTrigger />
6
+ <div class="relative h-full w-full">
7
+ <SvelteFlowProvider>
8
+ <div style:width="100%" style:height="100%">
9
+ <Flow />
32
10
  </div>
33
- </div>
34
- </Sidebar>
11
+ </SvelteFlowProvider>
12
+ </div>
@@ -1,3 +1,18 @@
1
- declare const DataModel: import("svelte").Component<Record<string, never>, {}, "">;
2
- type DataModel = ReturnType<typeof DataModel>;
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const DataModel: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type DataModel = InstanceType<typeof DataModel>;
3
18
  export default DataModel;
@@ -0,0 +1,19 @@
1
+ <script>
2
+ import ExtensionsComponents from "../../extensionsComponents.svelte";
3
+ import { getExtensionUtils } from "../../../extensions/extensionUtils";
4
+ import { getStudioContext } from "../../../context";
5
+
6
+ let { extension, page } = $props();
7
+
8
+ const { lobb, ctx } = getStudioContext();
9
+ </script>
10
+
11
+ <div class="grid h-full w-full overflow-auto bg-background">
12
+ {#key extension && page}
13
+ <ExtensionsComponents
14
+ name="publicPages.{page}"
15
+ utils={getExtensionUtils(lobb, ctx)}
16
+ filterByExtensions={[extension]}
17
+ />
18
+ {/key}
19
+ </div>
@@ -0,0 +1,13 @@
1
+ export default PublicExtension;
2
+ type PublicExtension = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ declare const PublicExtension: import("svelte").Component<{
7
+ extension: any;
8
+ page: any;
9
+ }, {}, "">;
10
+ type $$ComponentProps = {
11
+ extension: any;
12
+ page: any;
13
+ };
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import Button from "../ui/button/button.svelte";
3
- import { location } from "@wjfe/n-savant";
3
+ import { goto } from "$app/navigation";
4
4
  import { ArrowRight } from "lucide-svelte";
5
5
  import HomeFooter from "./homeFooter.svelte";
6
6
  </script>
@@ -21,7 +21,7 @@
21
21
  Icon={ArrowRight}
22
22
  variant="outline"
23
23
  class="h-7 px-3 text-xs font-normal"
24
- onclick={() => location.navigate("/studio/collections")}
24
+ onclick={() => goto("/studio/collections")}
25
25
  >
26
26
  Go to collections
27
27
  </Button>