@lobb-js/lobb-ext-storage 0.8.2 → 0.8.4

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 (45) hide show
  1. package/dist/extension.types.d.ts +25 -0
  2. package/dist/extension.types.js +1 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +25 -0
  5. package/dist/lib/components/childrenFileExplorer.svelte +12 -0
  6. package/dist/lib/components/childrenFileExplorer.svelte.d.ts +14 -0
  7. package/dist/lib/components/explorerNotSupported.svelte +13 -0
  8. package/dist/lib/components/explorerNotSupported.svelte.d.ts +14 -0
  9. package/dist/lib/components/fileExplorer.svelte +399 -0
  10. package/dist/lib/components/fileExplorer.svelte.d.ts +22 -0
  11. package/dist/lib/components/fileIcon.svelte +45 -0
  12. package/dist/lib/components/fileIcon.svelte.d.ts +14 -0
  13. package/dist/lib/components/fileManagerBreadCrumbs.svelte +50 -0
  14. package/dist/lib/components/fileManagerBreadCrumbs.svelte.d.ts +14 -0
  15. package/dist/lib/components/foreignKeyComponent.svelte +44 -0
  16. package/dist/lib/components/foreignKeyComponent.svelte.d.ts +14 -0
  17. package/dist/lib/components/pages/fileExplorerPage.svelte +66 -0
  18. package/dist/lib/components/pages/fileExplorerPage.svelte.d.ts +14 -0
  19. package/dist/lib/index.d.ts +0 -0
  20. package/dist/lib/index.js +2 -0
  21. package/dist/lib/utils.d.ts +12 -0
  22. package/dist/lib/utils.js +5 -0
  23. package/dist/tests/fileManager.spec.d.ts +1 -0
  24. package/dist/tests/fileManager.spec.js +18 -0
  25. package/dist/tests/package.json +1 -0
  26. package/dist/tests/playwright.config.cjs +27 -0
  27. package/dist/tests/playwright.config.d.cts +2 -0
  28. package/package.json +7 -3
  29. package/.github/workflows/create_tag_on_version_change.yaml +0 -45
  30. package/.github/workflows/publish_npm_package.yaml +0 -26
  31. package/.vscode/settings.json +0 -5
  32. package/CHANGELOG.md +0 -207
  33. package/lobb.ts +0 -54
  34. package/scripts/postpublish.sh +0 -12
  35. package/scripts/prepublish.sh +0 -17
  36. package/studio/app.html +0 -12
  37. package/studio/routes/+layout.svelte +0 -7
  38. package/studio/routes/+layout.ts +0 -1
  39. package/studio/routes/[...path]/+page.svelte +0 -6
  40. package/svelte.config.js +0 -24
  41. package/todo.md +0 -36
  42. package/tsconfig.app.json +0 -27
  43. package/tsconfig.json +0 -13
  44. package/tsconfig.node.json +0 -26
  45. package/vite.config.ts +0 -8
@@ -0,0 +1,25 @@
1
+ export interface Extension {
2
+ name: string;
3
+ onStartup?: (utils: ExtensionUtils) => void;
4
+ components?: Record<string, any>;
5
+ dashboardNavs?: {
6
+ top?: NavItem[];
7
+ middle?: NavItem[];
8
+ bottom?: NavItem[];
9
+ };
10
+ }
11
+ export interface NavItem {
12
+ label: string;
13
+ icon?: any;
14
+ href?: string;
15
+ onclick?: () => void;
16
+ navs?: NavItem[];
17
+ }
18
+ export interface ExtensionUtils {
19
+ components: {
20
+ Icons: Record<string, any>;
21
+ };
22
+ location: {
23
+ navigate: (path: string) => void;
24
+ };
25
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { Extension, ExtensionUtils } from "@lobb-js/studio";
2
+ export default function extension(utils: ExtensionUtils): Extension;
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ import FileExplorerPage from "./lib/components/pages/fileExplorerPage.svelte";
2
+ import ForeignKeyComponent from "./lib/components/foreignKeyComponent.svelte";
3
+ import ChildrenFileExplorer from "./lib/components/childrenFileExplorer.svelte";
4
+ import ExplorerNotSupported from "./lib/components/explorerNotSupported.svelte";
5
+ export default function extension(utils) {
6
+ return {
7
+ name: "storage",
8
+ components: {
9
+ "pages.file_manager": FileExplorerPage,
10
+ "detailView.fields.foreignKey.storage_fs": ForeignKeyComponent,
11
+ "detailView.create.subRecords.storage_fs": ExplorerNotSupported,
12
+ "detailView.update.subRecords.storage_fs": ChildrenFileExplorer,
13
+ "listView.entry.children.storage_fs": ChildrenFileExplorer,
14
+ },
15
+ dashboardNavs: {
16
+ middle: [
17
+ {
18
+ href: "/studio/extensions/storage/file_manager",
19
+ icon: utils.components.Icons.Folders,
20
+ label: "File Manager",
21
+ },
22
+ ],
23
+ },
24
+ };
25
+ }
@@ -0,0 +1,12 @@
1
+ <script lang="ts">
2
+ import type { ExtensionProps } from "@lobb-js/studio";
3
+ import FileExplorer from "./fileExplorer.svelte";
4
+
5
+ interface Props extends ExtensionProps {
6
+ class: string;
7
+ }
8
+
9
+ const { utils, filter, class: className }: Props = $props();
10
+ </script>
11
+
12
+ <FileExplorer foreignKeyObject={filter} utils={utils} class={className} />
@@ -0,0 +1,14 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: Record<string, never>;
4
+ events: {
5
+ [evt: string]: CustomEvent<any>;
6
+ };
7
+ slots: {};
8
+ };
9
+ export type ChildrenFileExplorerProps = typeof __propDef.props;
10
+ export type ChildrenFileExplorerEvents = typeof __propDef.events;
11
+ export type ChildrenFileExplorerSlots = typeof __propDef.slots;
12
+ export default class ChildrenFileExplorer extends SvelteComponentTyped<ChildrenFileExplorerProps, ChildrenFileExplorerEvents, ChildrenFileExplorerSlots> {
13
+ }
14
+ export {};
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ import { FolderX } from "lucide-svelte";
3
+ </script>
4
+
5
+ <div class="border rounded-md p-4 bg-muted/30 flex items-center flex-col text-muted-foreground gap-2">
6
+ <FolderX size={35} />
7
+ <div class="flex flex-col justify-center items-center">
8
+ <div class="text-sm font-bold">Storage file system is not supported here</div>
9
+ <div class="text-xs text-center max-w-96">
10
+ To access the storage file system for this record, please create the record first, then edit it to enable storage access.
11
+ </div>
12
+ </div>
13
+ </div>
@@ -0,0 +1,14 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: Record<string, never>;
4
+ events: {
5
+ [evt: string]: CustomEvent<any>;
6
+ };
7
+ slots: {};
8
+ };
9
+ export type ExplorerNotSupportedProps = typeof __propDef.props;
10
+ export type ExplorerNotSupportedEvents = typeof __propDef.events;
11
+ export type ExplorerNotSupportedSlots = typeof __propDef.slots;
12
+ export default class ExplorerNotSupported extends SvelteComponentTyped<ExplorerNotSupportedProps, ExplorerNotSupportedEvents, ExplorerNotSupportedSlots> {
13
+ }
14
+ export {};
@@ -0,0 +1,399 @@
1
+ <script lang="ts">
2
+ import type { ExtensionProps } from "@lobb-js/studio";
3
+ import type { Snippet } from "svelte";
4
+
5
+ import { fileOpen } from 'browser-fs-access';
6
+ import path from "path-browserify"
7
+ import FileManagerBreadCrumbs from "./fileManagerBreadCrumbs.svelte";
8
+ import FileIcon from "./fileIcon.svelte";
9
+
10
+ export interface Entry {
11
+ id: string;
12
+ name: string;
13
+ path: string;
14
+ type: "directory" | "file";
15
+ icon: string;
16
+ file_mime_type: string;
17
+ }
18
+
19
+ interface Props extends ExtensionProps {
20
+ location?: string;
21
+ onFileSelect?: (entry: Entry) => void;
22
+ foreignKeyObject?: Record<string, any>;
23
+ topLeftHeader?: Snippet<[]>;
24
+ class?: string;
25
+ }
26
+
27
+ let { location = $bindable('/'), onFileSelect, topLeftHeader, foreignKeyObject = {}, class: className, ...props }: Props = $props();
28
+
29
+ let loading = $state(true);
30
+ let entries: Entry[] = $state([]);
31
+
32
+ let contextType: "directory" | "file" | "background" = $state("background");
33
+ let selectedEntry: Entry | undefined = $state(undefined);
34
+ let contextMenuOpen: boolean = $state(false);
35
+
36
+ const utils = props.utils;
37
+ const icons = utils.components.Icons;
38
+ const { Tooltip, Button, ContextMenu } = utils.components;
39
+
40
+ $effect(() => {
41
+ renderFileExplorer(location);
42
+ });
43
+
44
+ async function renderFileExplorer(
45
+ location: string,
46
+ ) {
47
+ loading = true;
48
+
49
+ const localFilter = {
50
+ filter: {
51
+ path: location,
52
+ ...foreignKeyObject,
53
+ },
54
+ };
55
+ const response = await utils.lobb.findAll("storage_fs", localFilter);
56
+ entries = (await response.json()).data;
57
+
58
+ loading = false;
59
+ }
60
+
61
+ async function refreshFileExplorer() {
62
+ await renderFileExplorer(location);
63
+ }
64
+
65
+ async function handleCreateFolder() {
66
+ contextMenuOpen = false;
67
+ const newFolderName = prompt("Please enter a name for the new folder:");
68
+ if (newFolderName) {
69
+ const response = await utils.lobb.createOne("storage_fs", {
70
+ name: newFolderName,
71
+ path: location,
72
+ type: "directory",
73
+ ...foreignKeyObject,
74
+ });
75
+ const result = await response.json();
76
+ if (result.error) {
77
+ if (result.error.status === 409) {
78
+ utils.toast.error(
79
+ "Directories with the same name cannot exist in the same directory.",
80
+ );
81
+ } else {
82
+ utils.toast.error(result.error.message);
83
+ }
84
+ return;
85
+ }
86
+ await refreshFileExplorer();
87
+ }
88
+ }
89
+
90
+ async function handleFileUpload() {
91
+ contextMenuOpen = false;
92
+
93
+ async function uploadFile(file: File) {
94
+ if (file) {
95
+ const response = await utils.lobb.createOne(
96
+ "storage_fs",
97
+ {
98
+ path: location,
99
+ ...foreignKeyObject,
100
+ },
101
+ file,
102
+ );
103
+ const result = await response.json();
104
+ if (result.error) {
105
+ if (result.error.status === 409) {
106
+ utils.toast.error(
107
+ "Files with the same name cannot exist in the same directory.",
108
+ );
109
+ } else {
110
+ utils.toast.error(result.error.message);
111
+ }
112
+ contextMenuOpen = false;
113
+ return;
114
+ }
115
+ }
116
+ await refreshFileExplorer();
117
+ }
118
+
119
+ const file = await fileOpen();
120
+
121
+ utils.toast.promise(
122
+ uploadFile(file),
123
+ {
124
+ loading: 'Uploading File...',
125
+ success: 'Done Uploading!',
126
+ error: 'Something went wrong While Uploading!',
127
+ duration: 3000,
128
+ },
129
+ );
130
+ }
131
+
132
+ async function handleDeleteEntry() {
133
+ if (!selectedEntry) {
134
+ throw new Error(
135
+ "The selected Element is (undefined) for some reason",
136
+ );
137
+ }
138
+ const entryId = selectedEntry.id;
139
+ if (!entryId) {
140
+ throw new Error("The id of the entry is (null) for some reason");
141
+ }
142
+ const response = await utils.lobb.deleteOne(
143
+ "storage_fs",
144
+ entryId,
145
+ );
146
+ const result = await response.json();
147
+ if (result.status >= 400) {
148
+ return;
149
+ }
150
+ await refreshFileExplorer();
151
+ utils.toast.success("Deleted entry successfully");
152
+ contextMenuOpen = false;
153
+ }
154
+
155
+ async function oncontextmenucapture(e: Event) {
156
+ const element = e.target as HTMLElement;
157
+ const entryId = element.getAttribute("data-id");
158
+ if (entryId) {
159
+ selectedEntry = entries.find((el: any) => el.id == entryId);
160
+ if (selectedEntry?.type === "file") {
161
+ contextType = "file";
162
+ } else if (selectedEntry?.type === "directory") {
163
+ contextType = "directory";
164
+ } else {
165
+ contextType = "background";
166
+ }
167
+ } else {
168
+ selectedEntry = undefined;
169
+ contextType = "background";
170
+ }
171
+ }
172
+
173
+ async function handleSelect(e: Event) {
174
+ const element = e.target as HTMLElement;
175
+ const entryId = element.getAttribute("data-id");
176
+ if (entryId) {
177
+ selectedEntry = entries.find((el: any) => el.id == entryId);
178
+ } else {
179
+ selectedEntry = undefined;
180
+ }
181
+ }
182
+
183
+ async function handleEntryDoubleClick(entry: Entry) {
184
+ if (entry.type === "directory") {
185
+ const locationPath = path.join(entry.path, entry.name);
186
+ location = locationPath;
187
+ } else if (entry.type === 'file') {
188
+ if (onFileSelect) {
189
+ onFileSelect(entry);
190
+ }
191
+ }
192
+ }
193
+ </script>
194
+
195
+ <div class="flex h-full w-full flex-col bg-background {className}">
196
+ <!-- file explorer header -->
197
+ <div
198
+ class="flex h-10 items-center justify-between border-b px-2"
199
+ >
200
+ <div class="flex items-center justify-between gap-2">
201
+ {@render topLeftHeader?.()}
202
+ <FileManagerBreadCrumbs bind:location={location} {utils} />
203
+ </div>
204
+ <div class="flex gap-2">
205
+ <!-- entries view toggle -->
206
+ <Tooltip.Provider delayDuration={0}>
207
+ <Tooltip.Root>
208
+ <Tooltip.Trigger>
209
+ <Button
210
+ class="h-6 w-6 text-muted-foreground hover:bg-transparent"
211
+ variant="ghost"
212
+ size="icon"
213
+ >
214
+ <icons.List />
215
+ </Button>
216
+ </Tooltip.Trigger>
217
+ <Tooltip.Content
218
+ side="bottom"
219
+ sideOffset={7.5}
220
+ >
221
+ List View
222
+ </Tooltip.Content>
223
+ </Tooltip.Root>
224
+ </Tooltip.Provider>
225
+ <Button
226
+ variant="outline"
227
+ class="h-7 px-3 text-xs font-normal"
228
+ Icon={icons.FolderPlus}
229
+ onclick={handleCreateFolder}
230
+ >
231
+ New folder
232
+ </Button>
233
+ <Button
234
+ class="h-7 px-3 text-xs font-normal"
235
+ Icon={icons.FilePlus}
236
+ onclick={() => { handleFileUpload() }}
237
+ >
238
+ New file
239
+ </Button>
240
+ </div>
241
+ </div>
242
+ <!-- file explorer body -->
243
+ <div class="flex flex-1 overflow-auto h-[calc(100vh-7.5rem)]">
244
+ <ContextMenu.Root bind:open={contextMenuOpen}>
245
+ <ContextMenu.Trigger
246
+ class="z-10 flex flex-1 flex-wrap content-start items-start justify-start gap-4 p-4"
247
+ onclick={handleSelect}
248
+ {oncontextmenucapture}
249
+ >
250
+ {#if loading}
251
+ <div
252
+ class="flex h-full w-full items-center justify-center"
253
+ >
254
+ <div
255
+ class="flex h-full w-full flex-col items-center justify-center gap-4 text-muted-foreground"
256
+ >
257
+ <icons.LoaderCircle
258
+ class="opacity-50 animate-spin"
259
+ size="50"
260
+ />
261
+ <div
262
+ class="flex flex-col items-center justify-center"
263
+ >
264
+ <div>Loading files and directories, please wait...</div>
265
+ <div class="text-xs">
266
+ Fetching the contents of the current folder from the lobb server.
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ {:else}
272
+ {#if entries && entries.length}
273
+ {#each entries as entry}
274
+ {@const selectedCss =
275
+ selectedEntry?.id === entry.id
276
+ ? "bg-opacity-25 hover:bg-opacity-25"
277
+ : "hover:bg-opacity-10"}
278
+ <button
279
+ class="w-32 cursor-default rounded-md bg-muted-foreground bg-opacity-0 p-2 {selectedCss}"
280
+ data-id={entry.id}
281
+ ondblclick={() =>
282
+ handleEntryDoubleClick(entry)}
283
+ >
284
+ {#if entry.type === "directory"}
285
+ <div
286
+ class="pointer-events-none flex min-w-28 flex-col items-center"
287
+ >
288
+ <div
289
+ class="pointer-events-none relative"
290
+ >
291
+ <icons.Folder
292
+ size="50"
293
+ class="fill-muted-foreground stroke-none"
294
+ />
295
+ {#if entry.icon}
296
+ {@const key = entry.icon}
297
+ {/* @ts-ignore */ null}
298
+ {@const Icon = icons[key]}
299
+ <div
300
+ class="absolute left-0 top-0 flex h-full w-full items-center justify-center"
301
+ >
302
+ <Icon
303
+ class="text-primary-foreground"
304
+ style="transform: translateY(0.1rem);"
305
+ size="15"
306
+ />
307
+ </div>
308
+ {/if}
309
+ </div>
310
+ <div
311
+ class="pointer-events-none w-full break-words text-center text-sm font-medium"
312
+ >
313
+ {entry.name}
314
+ </div>
315
+ </div>
316
+ {:else}
317
+ <FileIcon {...props} {entry} />
318
+ {/if}
319
+ </button>
320
+ {/each}
321
+ {:else}
322
+ <div
323
+ class="flex h-full w-full items-center justify-center p-8"
324
+ >
325
+ <div
326
+ class="flex h-full w-full flex-col items-center justify-center gap-4 text-muted-foreground"
327
+ >
328
+ <icons.CircleSlash2
329
+ class="opacity-50"
330
+ size="50"
331
+ />
332
+ <div
333
+ class="flex flex-col items-center justify-center"
334
+ >
335
+ <div>Folder is empty</div>
336
+ <div class="text-xs">
337
+ Right-click to create a new folder
338
+ or upload a file.
339
+ </div>
340
+ </div>
341
+ </div>
342
+ </div>
343
+ {/if}
344
+ {/if}
345
+ </ContextMenu.Trigger>
346
+ <ContextMenu.Content
347
+ class="z-10 flex flex-col rounded-md border border-muted bg-background p-1 shadow-md outline-none w-40"
348
+ >
349
+ {#if contextType === "directory"}
350
+ <Button
351
+ variant="ghost"
352
+ onclick={handleDeleteEntry}
353
+ class="flex items-center gap-2 rounded-md px-4 h-8 hover:bg-muted focus-visible:ring-0 cursor-default font-normal"
354
+ >
355
+ Delete directory
356
+ </Button>
357
+ {:else if contextType === "file"}
358
+ <Button
359
+ variant="ghost"
360
+ onclick={handleDeleteEntry}
361
+ class="rounded-md px-4 h-8 hover:bg-muted focus-visible:ring-0 cursor-default font-normal"
362
+ >
363
+ Delete file
364
+ </Button>
365
+ {:else}
366
+ <Button
367
+ variant="ghost"
368
+ onclick={handleCreateFolder}
369
+ class="flex items-center gap-2 rounded-md px-4 h-8 hover:bg-muted focus-visible:ring-0 cursor-default font-normal"
370
+ >
371
+ New folder
372
+ </Button>
373
+ <Button
374
+ variant="ghost"
375
+ onclick={handleFileUpload}
376
+ class="flex items-center gap-2 rounded-md px-4 h-8 hover:bg-muted focus-visible:ring-0 cursor-default font-normal"
377
+ >
378
+ New file
379
+ </Button>
380
+ {/if}
381
+ </ContextMenu.Content>
382
+ </ContextMenu.Root>
383
+ </div>
384
+ <div
385
+ class="flex h-10 items-center justify-center border-t px-4 text-sm text-muted-foreground"
386
+ >
387
+ {#if loading}
388
+ <div class="flex gap-2 justify-center items-center">
389
+ <icons.LoaderCircle
390
+ class="animate-spin"
391
+ size="15"
392
+ />
393
+ <div>loading...</div>
394
+ </div>
395
+ {:else}
396
+ {entries ? entries.length : "0"} items available
397
+ {/if}
398
+ </div>
399
+ </div>
@@ -0,0 +1,22 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ export interface Entry {
3
+ id: string;
4
+ name: string;
5
+ path: string;
6
+ type: "directory" | "file";
7
+ icon: string;
8
+ file_mime_type: string;
9
+ }
10
+ declare const __propDef: {
11
+ props: Record<string, never>;
12
+ events: {
13
+ [evt: string]: CustomEvent<any>;
14
+ };
15
+ slots: {};
16
+ };
17
+ export type FileExplorerProps = typeof __propDef.props;
18
+ export type FileExplorerEvents = typeof __propDef.events;
19
+ export type FileExplorerSlots = typeof __propDef.slots;
20
+ export default class FileExplorer extends SvelteComponentTyped<FileExplorerProps, FileExplorerEvents, FileExplorerSlots> {
21
+ }
22
+ export {};
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import type { ExtensionProps } from "@lobb-js/studio";
3
+ import type { Entry } from "./fileExplorer.svelte";
4
+
5
+ interface Props extends ExtensionProps {
6
+ entry: Entry;
7
+ }
8
+
9
+ const { entry, utils }: Props = $props();
10
+
11
+ const MainIcon = utils.components.Icons.File;
12
+ const InnerIcon = utils.components.Icons.Text;
13
+ </script>
14
+
15
+ <div
16
+ class="pointer-events-none flex flex-col items-center gap-2 min-w-28"
17
+ >
18
+ {#if entry.file_mime_type.startsWith("image/")}
19
+ <div class="rounded-md w-24 h-24 bg-center bg-contain bg-no-repeat" style="background-image: url('{utils.lobb.lobbUrl}/api/collections/storage_fs/{entry.id}?action=view');"></div>
20
+ {:else}
21
+ <div
22
+ class="pointer-events-none relative"
23
+ >
24
+ <MainIcon
25
+ size="50"
26
+ class="fill-muted stroke-muted-foreground"
27
+ style="stroke-width: 0.03rem;"
28
+ />
29
+ <div
30
+ class="absolute left-0 top-0 flex h-full w-full items-center justify-center"
31
+ >
32
+ <InnerIcon
33
+ class="text-muted-foreground"
34
+ style="transform: translateY(0.1rem);"
35
+ size="15"
36
+ />
37
+ </div>
38
+ </div>
39
+ {/if}
40
+ <div
41
+ class="pointer-events-none w-full break-words text-center text-sm font-medium"
42
+ >
43
+ {entry.name}
44
+ </div>
45
+ </div>
@@ -0,0 +1,14 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: Record<string, never>;
4
+ events: {
5
+ [evt: string]: CustomEvent<any>;
6
+ };
7
+ slots: {};
8
+ };
9
+ export type FileIconProps = typeof __propDef.props;
10
+ export type FileIconEvents = typeof __propDef.events;
11
+ export type FileIconSlots = typeof __propDef.slots;
12
+ export default class FileIcon extends SvelteComponentTyped<FileIconProps, FileIconEvents, FileIconSlots> {
13
+ }
14
+ export {};
@@ -0,0 +1,50 @@
1
+ <script lang="ts">
2
+ import type { ExtensionProps } from "@lobb-js/studio";
3
+ import path from "path-browserify"
4
+
5
+ interface Props extends ExtensionProps {
6
+ location: string;
7
+ }
8
+
9
+ let { location = $bindable(), utils }: Props = $props();
10
+
11
+ let pathNames: string[] | undefined = $state(undefined);
12
+
13
+ $effect(() => {
14
+ pathNames = ["/", ...location.split("/").filter(Boolean)];
15
+ });
16
+ </script>
17
+
18
+ <utils.components.Breadcrumb.Root class="pl-2">
19
+ <utils.components.Breadcrumb.List>
20
+ {#if pathNames}
21
+ {#each pathNames as localPath, index}
22
+ {@const isLastElement = pathNames.length - 1 === index}
23
+ {@const currentFullPaths = pathNames
24
+ .slice(1, index + 1)
25
+ .join("/")}
26
+ {@const pathLabel = localPath === "/" ? "Home" : localPath}
27
+ <utils.components.Breadcrumb.Item>
28
+ {#if isLastElement}
29
+ <utils.components.Breadcrumb.Page>
30
+ {pathLabel}
31
+ </utils.components.Breadcrumb.Page>
32
+ {:else}
33
+ <utils.components.Breadcrumb.Link
34
+ class="cursor-pointer"
35
+ onclick={() => {
36
+ const invertedIndex = pathNames && pathNames.length - 1 - index;
37
+ location = path.join(location, Array(invertedIndex).fill("..").join('/'))
38
+ }}
39
+ >
40
+ {pathLabel}
41
+ </utils.components.Breadcrumb.Link>
42
+ {/if}
43
+ </utils.components.Breadcrumb.Item>
44
+ {#if !isLastElement}
45
+ <utils.components.Breadcrumb.Separator />
46
+ {/if}
47
+ {/each}
48
+ {/if}
49
+ </utils.components.Breadcrumb.List>
50
+ </utils.components.Breadcrumb.Root>
@@ -0,0 +1,14 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: Record<string, never>;
4
+ events: {
5
+ [evt: string]: CustomEvent<any>;
6
+ };
7
+ slots: {};
8
+ };
9
+ export type FileManagerBreadCrumbsProps = typeof __propDef.props;
10
+ export type FileManagerBreadCrumbsEvents = typeof __propDef.events;
11
+ export type FileManagerBreadCrumbsSlots = typeof __propDef.slots;
12
+ export default class FileManagerBreadCrumbs extends SvelteComponentTyped<FileManagerBreadCrumbsProps, FileManagerBreadCrumbsEvents, FileManagerBreadCrumbsSlots> {
13
+ }
14
+ export {};