@securancy/file-explorer 1.0.2 → 1.0.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.
@@ -4,9 +4,11 @@
4
4
  import FileExplorerBreadcrumb from "./FileExplorerBreadcrumb.svelte";
5
5
  import FileExplorerDisplayModeSwitcher from "./FileExplorerDisplayModeSwitcher.svelte";
6
6
  import { defaultTranslations, FileExplorerDisplayMode } from "../index.js";
7
- import FileExplorerDirectoryColumn from "./FileExplorerDirectoryColumn.svelte";
8
7
  import FileExplorerCreateDirectory from "./FileExplorerCreateDirectory.svelte";
9
8
  import { Button, Modal } from "@securancy/svelte-components";
9
+ import FileExplorerDirectory from "./FileExplorerDirectory.svelte";
10
+ import { Icon } from "@securancy/svelte-components";
11
+ import { fade } from "svelte/transition";
10
12
  export let displayMode = FileExplorerDisplayMode.Columns;
11
13
  export let filePath;
12
14
  export let documents;
@@ -106,44 +108,57 @@ function deleteFileInternal() {
106
108
  }
107
109
  </script>
108
110
 
109
- <div class="file-explorer file-explorer--display-mode-{displayMode}"
110
- >
111
+ <div class="file-explorer file-explorer--display-mode-{displayMode}">
111
112
  <div class="file-explorer__controls">
112
- <FileExplorerBreadcrumb {filePath} />
113
+ <FileExplorerBreadcrumb {filePath} {routePrefix} />
113
114
  <FileExplorerDisplayModeSwitcher bind:displayMode />
114
115
  </div>
115
- <div
116
- bind:this={fileExplorerContentContainer}
117
- class="file-explorer__content"
118
- on:scroll={handleScroll}
119
- >
120
- <FileExplorerDirectoryColumn
121
- {canEdit}
122
- currentPath=""
123
- directoryName={translations.items}
124
- {displayMode}
125
- {documents}
126
- fileItems={$documents}
127
- {filePath}
128
- {getFilePreview}
129
- {loading}
130
- {routePrefix}
131
- {showScrollBackButton}
132
- {translations}
133
- on:create-directory={handleCreateDirectory}
134
- on:delete-file={handleDeleteFile}
135
- on:scroll-back={handleScrollBack}
136
- on:upload-file={handleUploadFile}
116
+ {#if $$slots.search}
117
+ <div class="file-explorer__search">
118
+ <slot name="search" />
119
+ </div>
120
+ {/if}
121
+ {#if loading}
122
+ <div class="file-explorer__loader">
123
+ <Icon class="fa-light fa-spinner-third fa-spin" />
124
+ </div>
125
+ {:else}
126
+ <div
127
+ bind:this={fileExplorerContentContainer}
128
+ class="file-explorer__content"
129
+ on:scroll={handleScroll}
137
130
  >
138
- <svelte:fragment slot="top">
139
- {#if $$slots.search}
140
- <div class="file-explorer__search">
141
- <slot name="search" />
142
- </div>
143
- {/if}
144
- </svelte:fragment>
145
- </FileExplorerDirectoryColumn>
146
- </div>
131
+ <FileExplorerDirectory
132
+ {canEdit}
133
+ currentPath={filePath}
134
+ {displayMode}
135
+ {getFilePreview}
136
+ item={{
137
+ type: 'directory',
138
+ name: '',
139
+ path: '',
140
+ children: $documents,
141
+ }}
142
+ {routePrefix}
143
+ {translations}
144
+ on:create-directory={handleCreateDirectory}
145
+ on:delete-file={handleDeleteFile}
146
+ on:upload-file={handleUploadFile}
147
+ />
148
+ </div>
149
+ {/if}
150
+
151
+ <!-- Button to scroll back to the left -->
152
+ {#if showScrollBackButton}
153
+ <button
154
+ class="file-explorer__scroll-back-button"
155
+ type="button"
156
+ on:click={handleScrollBack}
157
+ transition:fade|local
158
+ >
159
+ <Icon class="fa-light fa-chevron-left" size="large" />
160
+ </button>
161
+ {/if}
147
162
  </div>
148
163
 
149
164
  {#if createDirectory}
@@ -294,8 +309,7 @@ function deleteFileInternal() {
294
309
  margin-left: auto;
295
310
  }
296
311
  .file-explorer__search {
297
- margin-bottom: var(--file-explorer-default-spacing);
298
- padding: calc(var(--file-explorer-default-spacing) * 1.5) var(--file-explorer-default-spacing) 0 var(--file-explorer-default-spacing);
312
+ padding: calc(var(--file-explorer-default-spacing) * 0.5) calc(var(--file-explorer-default-spacing) * 0.5) 0 calc(var(--file-explorer-default-spacing) * 0.5);
299
313
  }
300
314
  .file-explorer__list {
301
315
  margin-bottom: var(--file-explorer-default-spacing);
@@ -439,5 +453,9 @@ function deleteFileInternal() {
439
453
  .file-explorer__loader {
440
454
  display: flex;
441
455
  justify-content: center;
456
+ align-items: center;
457
+ min-height: 50px;
458
+ height: 100%;
459
+ font-size: 25px;
442
460
  }
443
461
  </style>
@@ -19,7 +19,7 @@ declare const __propDef: {
19
19
  documents: Refreshable<FileSystemItem[]>;
20
20
  loading: boolean;
21
21
  routePrefix: string;
22
- canEdit: boolean;
22
+ canEdit: boolean | ((fileItem: FileSystemItem) => boolean);
23
23
  translations?: {
24
24
  createDirectoryModalTitle: string;
25
25
  cancel: string;
@@ -38,6 +38,7 @@ declare const __propDef: {
38
38
  uploadFile: string;
39
39
  size: string;
40
40
  items: string;
41
+ directoryAlreadyExists: string;
41
42
  };
42
43
  getFilePreview: (fileItem: FileItem) => Promise<FilePreview>;
43
44
  createDirectory?: ((request: CreateDirectoryRequest) => Promise<void>) | undefined;
@@ -1,9 +1,26 @@
1
- <script>export let filePath;
1
+ <script>import { Icon } from "@securancy/svelte-components";
2
+ export let filePath;
3
+ export let routePrefix;
4
+ $: parts = filePath.split("/");
5
+ function getHref(parts2, index, routePrefix2) {
6
+ const href = [
7
+ ...routePrefix2.split("/"),
8
+ ...parts2.slice(0, index + 1)
9
+ ].filter((node) => node !== "").map(encodeURIComponent).join("/");
10
+ return "/" + href;
11
+ }
2
12
  </script>
3
13
 
4
- <!-- @todo make it clickable -->
5
14
  <div class="file-explorer__breadcrumb">
6
- {filePath}
15
+ <a href={routePrefix.startsWith('/') ? routePrefix : `/${routePrefix}`} title="Home">
16
+ <Icon class="fa-light fa-house" />
17
+ </a>
18
+ {#each parts as part, index}
19
+ <Icon class="fa-light fa-chevron-right" />
20
+ <a href={getHref(parts, index, routePrefix)} title={parts.slice(0, index + 1).join('/')}>
21
+ {part}
22
+ </a>
23
+ {/each}
7
24
  </div>
8
25
 
9
26
  <style>/* eslint-disable */
@@ -114,8 +131,7 @@
114
131
  margin-left: auto;
115
132
  }
116
133
  .file-explorer__search {
117
- margin-bottom: var(--file-explorer-default-spacing);
118
- padding: calc(var(--file-explorer-default-spacing) * 1.5) var(--file-explorer-default-spacing) 0 var(--file-explorer-default-spacing);
134
+ padding: calc(var(--file-explorer-default-spacing) * 0.5) calc(var(--file-explorer-default-spacing) * 0.5) 0 calc(var(--file-explorer-default-spacing) * 0.5);
119
135
  }
120
136
  .file-explorer__list {
121
137
  margin-bottom: var(--file-explorer-default-spacing);
@@ -259,5 +275,9 @@
259
275
  .file-explorer__loader {
260
276
  display: flex;
261
277
  justify-content: center;
278
+ align-items: center;
279
+ min-height: 50px;
280
+ height: 100%;
281
+ font-size: 25px;
262
282
  }
263
283
  </style>
@@ -2,6 +2,7 @@ import { SvelteComponent } from "svelte";
2
2
  declare const __propDef: {
3
3
  props: {
4
4
  filePath: string;
5
+ routePrefix: string;
5
6
  };
6
7
  events: {
7
8
  [evt: string]: CustomEvent<any>;
@@ -4,6 +4,8 @@ export let createDirectoryModalOpen = false;
4
4
  export let documents;
5
5
  export let createDirectory;
6
6
  export let translations;
7
+ $: existingFoldersInPath = getExistingFoldersInPath(createDirectoryPath, $documents);
8
+ $: directoryAlreadyExists = existingFoldersInPath.some((x) => x.name === request.name);
7
9
  let request;
8
10
  let submitting = false;
9
11
  let input = void 0;
@@ -25,6 +27,16 @@ async function submitFiles() {
25
27
  });
26
28
  documents.refresh();
27
29
  }
30
+ function getExistingFoldersInPath(path, documents2) {
31
+ if (!path || !documents2) return [];
32
+ const pathParts = path.split("/");
33
+ let existingFoldersInPath2 = documents2.filter((x) => x.type === "directory");
34
+ for (const pathPart of pathParts) {
35
+ const currentFolder = existingFoldersInPath2.find((document) => document.name === pathPart);
36
+ existingFoldersInPath2 = currentFolder ? currentFolder.children.filter((x) => x.type === "directory") : [];
37
+ }
38
+ return existingFoldersInPath2;
39
+ }
28
40
  </script>
29
41
 
30
42
  <Modal
@@ -33,8 +45,12 @@ async function submitFiles() {
33
45
  >
34
46
  <div class="create-directory">
35
47
  <span class="create-directory__path">{createDirectoryPath}/</span>
36
- <FormElement clearable>
37
- <input bind:this={input} bind:value={request.name}>
48
+ <FormElement
49
+ clearable
50
+ error={directoryAlreadyExists ? translations.directoryAlreadyExists : undefined}
51
+ floatError
52
+ >
53
+ <input bind:this={input} type="text" bind:value={request.name}>
38
54
  </FormElement>
39
55
  </div>
40
56
  <svelte:fragment slot="footer">
@@ -46,7 +62,7 @@ async function submitFiles() {
46
62
  {translations.cancel}
47
63
  </Button>
48
64
  <Button
49
- disabled={(!request.name || request.name.length === 0) || submitting}
65
+ disabled={(!request.name || request.name.length === 0) || directoryAlreadyExists || submitting}
50
66
  on:click={submitFiles}
51
67
  >
52
68
  {#if submitting}
@@ -0,0 +1,329 @@
1
+ <script>import {} from "..";
2
+ import FileExplorerDirectoryColumn from "./FileExplorerDirectoryColumn.svelte";
3
+ import FileExplorerFileDetailColumn from "./FileExplorerFileDetailColumn.svelte";
4
+ import FileExplorerDirectoryIndex from "./FileExplorerDirectoryIndex.svelte";
5
+ export let item;
6
+ export let translations;
7
+ export let routePrefix;
8
+ export let currentPath;
9
+ export let canEdit;
10
+ export let displayMode;
11
+ export let getFilePreview;
12
+ $: currentPathParts = currentPath.split("/");
13
+ $: activeFileItem = item.children.find((fileItem) => {
14
+ const parts = [
15
+ ...fileItem.path.split("/").filter((part) => part !== ""),
16
+ fileItem.name
17
+ ];
18
+ return parts.every((part, index) => currentPathParts[index] === part);
19
+ });
20
+ function canEditItem(item2, canEdit2) {
21
+ return typeof canEdit2 === "function" ? canEdit2(item2) : canEdit2 === true;
22
+ }
23
+ </script>
24
+
25
+ {#if displayMode === 'columns'}
26
+ <FileExplorerDirectoryColumn
27
+ {activeFileItem}
28
+ canEdit={canEditItem(item, canEdit)}
29
+ {item}
30
+ {routePrefix}
31
+ {translations}
32
+ on:upload-file
33
+ on:create-directory
34
+ />
35
+ {:else}
36
+ <FileExplorerDirectoryIndex
37
+ {activeFileItem}
38
+ canEdit={canEditItem(item, canEdit)}
39
+ {item}
40
+ {routePrefix}
41
+ {translations}
42
+ on:upload-file
43
+ on:create-directory
44
+ />
45
+ {/if}
46
+
47
+ <!-- The component repeats itself until there is no selected item or it reaches a fileItem -->
48
+ {#if activeFileItem}
49
+ {#if activeFileItem.type === 'directory'}
50
+ <svelte:self
51
+ {canEdit}
52
+ {currentPath}
53
+ {displayMode}
54
+ {getFilePreview}
55
+ item={activeFileItem}
56
+ {routePrefix}
57
+ {translations}
58
+ on:upload-file
59
+ on:create-directory
60
+ on:delete-file
61
+ />
62
+ {:else}
63
+ <FileExplorerFileDetailColumn
64
+ canEdit={canEditItem(activeFileItem, canEdit)}
65
+ fileItem={activeFileItem}
66
+ {getFilePreview}
67
+ {translations}
68
+ on:delete-file
69
+ />
70
+ {/if}
71
+ {/if}
72
+ <style>/* eslint-disable */
73
+ /**
74
+ * Media queries and devices
75
+ */
76
+ /**
77
+ * Some mixins that can be used dynamically inside @each loops
78
+ */
79
+ .file-explorer {
80
+ position: relative;
81
+ display: flex;
82
+ flex-direction: column;
83
+ height: 100%;
84
+ width: 100%;
85
+ background: var(--file-explorer-background);
86
+ overflow: hidden;
87
+ }
88
+ .file-explorer__controls {
89
+ flex-shrink: 0;
90
+ display: flex;
91
+ border-bottom: var(--file-explorer-border-style);
92
+ padding: var(--file-explorer-default-spacing);
93
+ background: var(--file-explorer-background-shade);
94
+ }
95
+ .file-explorer__content {
96
+ flex-grow: 1;
97
+ display: flex;
98
+ overflow-x: auto;
99
+ scroll-behavior: smooth;
100
+ }
101
+ /* Keep the index within screen */
102
+ @media (width >= 769px) {
103
+ /* @formatter:off */
104
+ .file-explorer--display-mode-index .file-explorer__content {
105
+ /* @formatter:on */
106
+ overflow-x: inherit;
107
+ }
108
+ }
109
+ .file-explorer__column, .file-explorer__index {
110
+ flex-shrink: 0;
111
+ border-right: var(--file-explorer-border-style);
112
+ background: var(--file-explorer-background);
113
+ }
114
+ .file-explorer__column {
115
+ display: flex;
116
+ flex-direction: column;
117
+ width: var(--file-explorer-column-default-width);
118
+ min-width: var(--file-explorer-column-min-width);
119
+ overflow: hidden;
120
+ }
121
+ .file-explorer__column--file-detail {
122
+ flex-grow: 1;
123
+ width: var(--file-explorer-preview-default-width);
124
+ display: flex;
125
+ flex-direction: column;
126
+ }
127
+ .file-explorer__column--sticky {
128
+ position: sticky;
129
+ left: 0;
130
+ z-index: 1;
131
+ }
132
+ .file-explorer__column::after {
133
+ content: '';
134
+ position: absolute;
135
+ inset: 0;
136
+ pointer-events: none;
137
+ transition: background-color 0.2s ease-in-out;
138
+ }
139
+ .file-explorer__column--dragover {
140
+ cursor: copy;
141
+ }
142
+ .file-explorer__column--dragover::after {
143
+ pointer-events: all;
144
+ background-color: var(--file-explorer-background-dragover);
145
+ }
146
+ .file-explorer__index {
147
+ display: flex;
148
+ flex-direction: column;
149
+ width: var(--file-explorer-index-default-width);
150
+ overflow: hidden;
151
+ }
152
+ @media (width >= 769px) {
153
+ .file-explorer__index {
154
+ flex-shrink: 1
155
+ }
156
+ }
157
+ .file-explorer__index--hidden {
158
+ display: none;
159
+ }
160
+ .file-explorer__column-scroll-container, .file-explorer__index-scroll-container {
161
+ flex-grow: 1;
162
+ overflow-y: auto;
163
+ padding: calc(var(--file-explorer-default-spacing) * 1.5) var(--file-explorer-default-spacing) 0 var(--file-explorer-default-spacing);
164
+ }
165
+ .file-explorer__grid {
166
+ display: flex;
167
+ align-items: flex-start;
168
+ flex-wrap: wrap;
169
+ gap: var(--file-explorer-default-spacing);
170
+ }
171
+ .file-explorer__breadcrumb {
172
+ /* @todo */
173
+ }
174
+ .file-explorer__display-mode-switcher {
175
+ display: flex;
176
+ gap: calc(0.5 * var(--file-explorer-default-spacing));
177
+ margin-left: auto;
178
+ }
179
+ .file-explorer__search {
180
+ padding: calc(var(--file-explorer-default-spacing) * 0.5) calc(var(--file-explorer-default-spacing) * 0.5) 0 calc(var(--file-explorer-default-spacing) * 0.5);
181
+ }
182
+ .file-explorer__list {
183
+ margin-bottom: var(--file-explorer-default-spacing);
184
+ }
185
+ .file-explorer__list-header {
186
+ display: flex;
187
+ justify-content: space-between;
188
+ gap: calc(0.5 * var(--file-explorer-default-spacing));
189
+ width: 100%;
190
+ margin-bottom: calc(0.5 * var(--file-explorer-default-spacing));
191
+ color: var(--file-explorer-list-header-color);
192
+ }
193
+ .file-explorer__list-header span {
194
+ overflow: hidden;
195
+ white-space: nowrap;
196
+ text-overflow: ellipsis;
197
+ }
198
+ .file-explorer__list-header button {
199
+ flex-shrink: 0;
200
+ color: var(--file-explorer-action-primary-color);
201
+ cursor: pointer;
202
+ }
203
+ .file-explorer__grid-header {
204
+ width: 100%;
205
+ }
206
+ .file-explorer__grid-header button {
207
+ color: var(--file-explorer-action-primary-color);
208
+ cursor: pointer;
209
+ }
210
+ .file-explorer__list-item, .file-explorer__grid-item {
211
+ border-radius: var(--file-explorer-list-item-border-radius);
212
+ background: none;
213
+ color: var(--file-explorer-list-item-color);
214
+ }
215
+ .file-explorer__list-item > :global(i), .file-explorer__grid-item > :global(i) {
216
+ flex-shrink: 0;
217
+ }
218
+ .file-explorer__list-item:not(.file-explorer__list-item--active):hover, .file-explorer__grid-item:not(.file-explorer__grid-item--active):hover {
219
+ background: var(--file-explorer-list-item-background-hover);
220
+ }
221
+ .file-explorer__list-item--active, .file-explorer__grid-item--active {
222
+ color: var(--file-explorer-list-item-color-active);
223
+ background: var(--file-explorer-list-item-background-active);
224
+ }
225
+ .file-explorer__grid-item {
226
+ display: flex;
227
+ flex-direction: column;
228
+ align-items: center;
229
+
230
+ /* 3 items in a grid, spaced out with default spacing means the width of 2 gaps must be subtracted from width */
231
+ width: calc(33.33% - (0.6666 * var(--file-explorer-default-spacing)));
232
+ padding: 5px 2px;
233
+ }
234
+ @media (width >= 769px) {
235
+ .file-explorer__grid-item {
236
+ width: 100px
237
+ }
238
+ }
239
+ .file-explorer__grid-item--active {
240
+ /* @todo */
241
+ }
242
+ .file-explorer__grid-item > span {
243
+ word-break: break-word;
244
+ margin-top: calc(0.25 * var(--file-explorer-default-spacing));
245
+ }
246
+ .file-explorer__list-item {
247
+ display: flex;
248
+ align-items: baseline;
249
+ width: 100%;
250
+ text-align: left;
251
+ padding: var(--file-explorer-list-item-spacing);
252
+ white-space: nowrap;
253
+ overflow: hidden;
254
+ text-overflow: ellipsis;
255
+ }
256
+ .file-explorer__list-item > span {
257
+ flex-grow: 1;
258
+ min-width: 0;
259
+ overflow: hidden;
260
+ text-overflow: ellipsis;
261
+ }
262
+ .file-explorer__list-item > :global(i:first-child) {
263
+ margin-right: var(--file-explorer-list-item-spacing);
264
+ }
265
+ .file-explorer__list-item > :global(i:last-child) {
266
+ margin-left: var(--file-explorer-list-item-spacing);
267
+ }
268
+ .file-explorer__message {
269
+ display: block;
270
+ padding: var(--file-explorer-list-item-spacing) 0;
271
+ color: var(--file-explorer-message-color);
272
+ }
273
+ .file-explorer__image-preview {
274
+ max-width: 100%;
275
+ margin: 0 auto;
276
+ }
277
+ .file-explorer-preview__title {
278
+ font-size: var(--font-size--h1);
279
+ font-weight: var(--font-weight--semi-bold);
280
+ overflow: hidden;
281
+ text-overflow: ellipsis;
282
+ white-space: nowrap;
283
+ }
284
+ .file-explorer-preview__download {
285
+ color: var(--file-explorer-action-primary-color);
286
+ font-weight: var(--font-weight--semi-bold);
287
+ }
288
+ .file-explorer-preview__download:hover {
289
+ text-decoration-line: underline;
290
+ }
291
+ .file-explorer-preview__info {
292
+ margin-top: 14px;
293
+ }
294
+ .file-explorer-preview__info span {
295
+ color: var(--file-explorer-message-color);
296
+ }
297
+ .file-explorer-preview__info button {
298
+ cursor: pointer;
299
+ }
300
+ .file-explorer__scroll-back-button {
301
+ position: fixed;
302
+ bottom: var(--file-explorer-default-spacing);
303
+ right: var(--file-explorer-default-spacing);
304
+ z-index: 2;
305
+ background: var(--file-explorer-action-primary-color);
306
+ color: var(--file-explorer-action-primary-color-contrast);
307
+ padding: var(--file-explorer-list-item-spacing);
308
+ border-radius: var(--file-explorer-list-item-border-radius);
309
+ }
310
+ .file-explorer__file-icon--large {
311
+ width: 100%;
312
+ text-align: center;
313
+ padding: calc(2 * var(--file-explorer-default-spacing));
314
+ }
315
+ .file-explorer__file-icon--large :global(i) {
316
+ font-size: var(--file-explorer-preview-icon-size);
317
+ }
318
+ .file-explorer__text--semi-bold {
319
+ font-weight: var(--file-explorer-text-semi-bold);
320
+ }
321
+ .file-explorer__loader {
322
+ display: flex;
323
+ justify-content: center;
324
+ align-items: center;
325
+ min-height: 50px;
326
+ height: 100%;
327
+ font-size: 25px;
328
+ }
329
+ </style>
@@ -0,0 +1,30 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { FileExplorerDisplayMode, FileSystemItem } from '..';
3
+ import { type DirectoryItem, type DocumentTranslations, type FileItem, type FilePreview } from '..';
4
+ declare const __propDef: {
5
+ props: {
6
+ item: DirectoryItem;
7
+ translations: DocumentTranslations;
8
+ routePrefix: string;
9
+ currentPath: string;
10
+ canEdit: boolean | ((fileItem: FileSystemItem) => boolean);
11
+ displayMode: FileExplorerDisplayMode;
12
+ getFilePreview: (fileItem: FileItem) => Promise<FilePreview>;
13
+ };
14
+ events: {
15
+ 'upload-file': CustomEvent<import("..").UploadFileEvent>;
16
+ 'create-directory': CustomEvent<import("..").CreateDirectoryEvent>;
17
+ 'delete-file': CustomEvent<import("..").DeleteFileEvent>;
18
+ } & {
19
+ [evt: string]: CustomEvent<any>;
20
+ };
21
+ slots: {};
22
+ exports?: {} | undefined;
23
+ bindings?: string | undefined;
24
+ };
25
+ export type FileExplorerDirectoryProps = typeof __propDef.props;
26
+ export type FileExplorerDirectoryEvents = typeof __propDef.events;
27
+ export type FileExplorerDirectorySlots = typeof __propDef.slots;
28
+ export default class FileExplorerDirectory extends SvelteComponent<FileExplorerDirectoryProps, FileExplorerDirectoryEvents, FileExplorerDirectorySlots> {
29
+ }
30
+ export {};
@@ -1,42 +1,24 @@
1
1
  <script>import { createEventDispatcher } from "svelte";
2
- import { fade } from "svelte/transition";
3
- import { FileExplorerDisplayMode } from "..";
2
+ import {} from "..";
4
3
  import { resize, ResizeDirections } from "@securancy/svelte-utilities";
5
4
  import { Icon, Popover, PopoverItem } from "@securancy/svelte-components";
6
- import { getExtension, openItem } from "../utilities/index.js";
5
+ import { getExtension, getItemHref } from "../utilities/index.js";
7
6
  import FileIcon from "./FileIcon.svelte";
8
- import FileExplorerFileDetailColumn from "./FileExplorerFileDetailColumn.svelte";
9
- import FileExplorerDirectoryIndex from "./FileExplorerDirectoryIndex.svelte";
7
+ import { joinPaths } from "../utilities/index.js";
10
8
  const dispatch = createEventDispatcher();
11
- export let directoryName;
12
- export let filePath;
13
- export let currentPath;
14
- export let fileItems;
15
- export let documents;
16
- export let displayMode;
9
+ export let item;
17
10
  export let sticky = false;
18
- export let showScrollBackButton = false;
19
- export let loading;
20
11
  export let routePrefix;
21
12
  export let canEdit;
22
13
  export let translations;
23
- export let getFilePreview;
24
- let currentFileItem;
25
- let activeFileItem;
26
- let filePathSplit;
14
+ export let activeFileItem;
27
15
  let dragoverIndex = 0;
28
16
  let innerWidth = 0;
29
- $: filePathSplit = filePath.split("/");
30
- $: currentFileItem = filePathSplit.shift();
31
- $: activeFileItem = fileItems.find((fi) => fi.name === currentFileItem);
32
- function handleScrollBack() {
33
- dispatch("scroll-back");
34
- }
35
17
  function addItem() {
36
- dispatch("upload-file", { path: currentPath });
18
+ dispatch("upload-file", { path: joinPaths(item.path, item.name) });
37
19
  }
38
20
  function createDirectory() {
39
- dispatch("create-directory", { path: currentPath });
21
+ dispatch("create-directory", { path: joinPaths(item.path, item.name) });
40
22
  }
41
23
  function handleDragEnter(event) {
42
24
  event.preventDefault();
@@ -47,7 +29,7 @@ function handleDrop(event) {
47
29
  event.preventDefault();
48
30
  event.stopPropagation();
49
31
  dragoverIndex = 0;
50
- const eventData = { path: currentPath };
32
+ const eventData = { path: joinPaths(item.path, item.name) };
51
33
  if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
52
34
  eventData.files = event.dataTransfer.files;
53
35
  }
@@ -89,49 +71,41 @@ function handleDragOver(event) {
89
71
  parentPadding: true,
90
72
  }}
91
73
  >
92
- <slot name="top" />
93
74
  <div class="file-explorer__column-scroll-container">
94
75
  <div class="file-explorer__list">
95
76
  <div class="file-explorer__list-header">
96
- {#if !loading}
97
- <span>
98
- {directoryName} ({fileItems?.length ?? 0})
99
- </span>
100
- {#if canEdit}
101
- <Popover placement="bottom-end" trigger="click">
102
- <button slot="trigger" type="button">
103
- <Icon class="fa-solid fa-add" />
104
- </button>
105
- <PopoverItem closeOnClick on:click={createDirectory}>{translations.createDirectory}</PopoverItem>
106
- <PopoverItem closeOnClick on:click={addItem}>{translations.uploadFile}</PopoverItem>
107
- </Popover>
108
- {/if}
77
+ <span>
78
+ {item.name === '' ? translations.items : item.name} ({item.children.length})
79
+ </span>
80
+ {#if canEdit}
81
+ <Popover placement="bottom-end" trigger="click">
82
+ <button slot="trigger" type="button">
83
+ <Icon class="fa-solid fa-add" />
84
+ </button>
85
+ <PopoverItem closeOnClick on:click={createDirectory}>{translations.createDirectory}</PopoverItem>
86
+ <PopoverItem closeOnClick on:click={addItem}>{translations.uploadFile}</PopoverItem>
87
+ </Popover>
109
88
  {/if}
110
89
  </div>
111
- {#if fileItems.length > 0}
112
- {#each fileItems as item}
113
- <button
90
+ {#if item.children.length > 0}
91
+ {#each item.children as child}
92
+ <a
114
93
  class="file-explorer__list-item"
115
- class:file-explorer__list-item--active={activeFileItem === item}
116
- title={translations.itemTitle(item)}
117
- type="button"
118
- on:click={() => openItem(item, routePrefix)}
94
+ class:file-explorer__list-item--active={activeFileItem === child}
95
+ href={getItemHref(child, routePrefix)}
96
+ title={translations.itemTitle(child)}
119
97
  >
120
- {#if item.type === 'directory'}
121
- <Icon class="fa-light fa-folder{activeFileItem === item ? '-open' : ''}" />
98
+ {#if child.type === 'directory'}
99
+ <Icon class="fa-light fa-folder{activeFileItem === child ? '-open' : ''}" />
122
100
  {:else}
123
- <FileIcon extension={getExtension(item.name)} />
101
+ <FileIcon extension={getExtension(child.name)} />
124
102
  {/if}
125
- <span>{item.name}</span>
126
- {#if item.type === 'directory'}
103
+ <span>{child.name}</span>
104
+ {#if child.type === 'directory'}
127
105
  <Icon class="fa-light fa-sm fa-chevron-right" size="custom" />
128
106
  {/if}
129
- </button>
107
+ </a>
130
108
  {/each}
131
- {:else if loading}
132
- <div class="file-explorer__loader">
133
- <Icon class="fa-light fa-spinner-third fa-spin" />
134
- </div>
135
109
  {:else}
136
110
  <p class="file-explorer__message">
137
111
  {translations.emptyDirectory}
@@ -141,64 +115,6 @@ function handleDragOver(event) {
141
115
  </div>
142
116
  </div>
143
117
 
144
- <!-- The component repeats itself until the end of the filePath is reached or shows a FileDetail -->
145
- {#if activeFileItem}
146
- {#if activeFileItem.type === 'directory'}
147
- {#if displayMode === FileExplorerDisplayMode.Columns}
148
- <svelte:self
149
- {canEdit}
150
- currentPath={[activeFileItem.path, activeFileItem.name].join('/')}
151
- directoryName={currentFileItem || ''}
152
- {displayMode}
153
- {documents}
154
- fileItems={activeFileItem.children || []}
155
- filePath={filePathSplit.join('/')}
156
- {getFilePreview}
157
- {loading}
158
- {routePrefix}
159
- {translations}
160
- on:upload-file
161
- on:create-directory
162
- on:delete-file
163
- />
164
- {:else}
165
- <FileExplorerDirectoryIndex
166
- {canEdit}
167
- currentPath={[activeFileItem.path, activeFileItem.name].join('/')}
168
- directoryName={currentFileItem || ''}
169
- {documents}
170
- fileItems={activeFileItem.children || []}
171
- filePath={filePathSplit.join('/')}
172
- {getFilePreview}
173
- {routePrefix}
174
- {translations}
175
- on:upload-file
176
- on:create-directory
177
- on:delete-file
178
- />
179
- {/if}
180
- {:else}
181
- <FileExplorerFileDetailColumn
182
- fileItem={activeFileItem}
183
- {getFilePreview}
184
- {translations}
185
- on:delete-file
186
- />
187
- {/if}
188
- {/if}
189
-
190
- <!-- Button to scroll back to the left -->
191
- {#if showScrollBackButton}
192
- <button
193
- class="file-explorer__scroll-back-button"
194
- type="button"
195
- on:click={handleScrollBack}
196
- transition:fade|local
197
- >
198
- <Icon class="fa-light fa-chevron-left" />
199
- </button>
200
- {/if}
201
-
202
118
  <style>/* eslint-disable */
203
119
  /**
204
120
  * Media queries and devices
@@ -307,8 +223,7 @@ function handleDragOver(event) {
307
223
  margin-left: auto;
308
224
  }
309
225
  .file-explorer__search {
310
- margin-bottom: var(--file-explorer-default-spacing);
311
- padding: calc(var(--file-explorer-default-spacing) * 1.5) var(--file-explorer-default-spacing) 0 var(--file-explorer-default-spacing);
226
+ padding: calc(var(--file-explorer-default-spacing) * 0.5) calc(var(--file-explorer-default-spacing) * 0.5) 0 calc(var(--file-explorer-default-spacing) * 0.5);
312
227
  }
313
228
  .file-explorer__list {
314
229
  margin-bottom: var(--file-explorer-default-spacing);
@@ -452,5 +367,9 @@ function handleDragOver(event) {
452
367
  .file-explorer__loader {
453
368
  display: flex;
454
369
  justify-content: center;
370
+ align-items: center;
371
+ min-height: 50px;
372
+ height: 100%;
373
+ font-size: 25px;
455
374
  }
456
375
  </style>
@@ -1,33 +1,24 @@
1
1
  import { SvelteComponent } from "svelte";
2
- import { type DocumentTranslations, FileExplorerDisplayMode, type FileItem, type FilePreview, type FileSystemItem } from '..';
3
- import type { Refreshable } from '@securancy/svelte-utilities';
2
+ import type { UploadFileEvent } from './FileExplorer.svelte';
3
+ import { type DirectoryItem, type DocumentTranslations, type FileSystemItem } from '..';
4
+ import type { CreateDirectoryEvent } from './FileExplorer.svelte';
4
5
  declare const __propDef: {
5
6
  props: {
6
- directoryName: string;
7
- filePath: string;
8
- currentPath: string;
9
- fileItems: FileSystemItem[];
10
- documents: Refreshable<FileSystemItem[]>;
11
- displayMode: FileExplorerDisplayMode;
7
+ item: DirectoryItem;
12
8
  sticky?: boolean;
13
- showScrollBackButton?: boolean;
14
- loading: boolean;
15
9
  routePrefix: string;
16
10
  canEdit: boolean;
17
11
  translations: DocumentTranslations;
18
- getFilePreview: (fileItem: FileItem) => Promise<FilePreview>;
12
+ activeFileItem: FileSystemItem | undefined;
19
13
  };
20
14
  events: {
21
- 'upload-file': CustomEvent<any>;
22
- 'create-directory': CustomEvent<any>;
23
- 'delete-file': CustomEvent<import("..").DeleteFileEvent>;
24
15
  'scroll-back': CustomEvent<void>;
16
+ 'upload-file': CustomEvent<UploadFileEvent>;
17
+ 'create-directory': CustomEvent<CreateDirectoryEvent>;
25
18
  } & {
26
19
  [evt: string]: CustomEvent<any>;
27
20
  };
28
- slots: {
29
- top: {};
30
- };
21
+ slots: {};
31
22
  exports?: {} | undefined;
32
23
  bindings?: string | undefined;
33
24
  };
@@ -1,31 +1,20 @@
1
1
  <script>import FileIcon from "./FileIcon.svelte";
2
- import FileExplorerFileDetailColumn from "./FileExplorerFileDetailColumn.svelte";
3
2
  import { createEventDispatcher } from "svelte";
4
3
  import { resize, ResizeDirections } from "@securancy/svelte-utilities";
5
4
  import { Icon, Popover, PopoverItem } from "@securancy/svelte-components";
6
- import { getExtension, openItem } from "../utilities/index.js";
5
+ import { getExtension, getItemHref, joinPaths } from "../utilities/index.js";
7
6
  const dispatch = createEventDispatcher();
8
- export let directoryName;
9
- export let filePath;
10
- export let fileItems;
11
- export let currentPath;
7
+ export let item;
12
8
  export let routePrefix;
13
9
  export let translations;
14
10
  export let canEdit;
15
- export let documents;
16
- export let getFilePreview;
17
- let currentFileItem;
18
- let activeFileItem;
19
- let filePathSplit;
11
+ export let activeFileItem;
20
12
  let innerWidth = 0;
21
- $: filePathSplit = filePath.split("/");
22
- $: currentFileItem = filePathSplit.shift();
23
- $: activeFileItem = fileItems.find((fi) => fi.name === currentFileItem);
24
13
  function addItem() {
25
- dispatch("upload-file", { path: currentPath });
14
+ dispatch("upload-file", { path: joinPaths(item.path, item.name) });
26
15
  }
27
16
  function createDirectory() {
28
- dispatch("create-directory", { path: currentPath });
17
+ dispatch("create-directory", { path: joinPaths(item.path, item.name) });
29
18
  }
30
19
  </script>
31
20
 
@@ -44,7 +33,7 @@ function createDirectory() {
44
33
  <div class="file-explorer__grid">
45
34
  <div class="file-explorer__grid-header">
46
35
  <span>
47
- {directoryName} ({fileItems?.length ?? 0})
36
+ {item.name === '' ? translations.items : item.name} ({item.children.length})
48
37
  </span>
49
38
  {#if canEdit}
50
39
  <div class="inline-block">
@@ -58,24 +47,23 @@ function createDirectory() {
58
47
  </div>
59
48
  {/if}
60
49
  </div>
61
- {#each fileItems as item}
62
- <button
50
+ {#each item.children as child}
51
+ <a
63
52
  class="file-explorer__grid-item"
64
- class:file-explorer__grid-item--active={activeFileItem === item}
65
- title={translations.itemTitle(item)}
66
- type="button"
67
- on:click={() => openItem(item, routePrefix)}
53
+ class:file-explorer__grid-item--active={activeFileItem === child}
54
+ href={getItemHref(child, routePrefix)}
55
+ title={translations.itemTitle(child)}
68
56
  >
69
- {#if item.type === 'directory'}
57
+ {#if child.type === 'directory'}
70
58
  <Icon
71
- class="fa-light fa-2xl fa-folder{activeFileItem?.name === item.name ? '-open' : ''}"
59
+ class="fa-light fa-2xl fa-folder{activeFileItem?.name === child.name ? '-open' : ''}"
72
60
  size="custom"
73
61
  />
74
62
  {:else}
75
- <FileIcon extension={getExtension(item.name)} size="grid" />
63
+ <FileIcon extension={getExtension(child.name)} size="grid" />
76
64
  {/if}
77
- <span>{item.name}</span>
78
- </button>
65
+ <span>{child.name}</span>
66
+ </a>
79
67
  {:else}
80
68
  <p class="file-explorer__message">
81
69
  {translations.emptyDirectory}
@@ -86,30 +74,6 @@ function createDirectory() {
86
74
  </div>
87
75
  {/if}
88
76
 
89
- <!-- The component repeats itself until the end of the filePath is reached or shows a FileDetail -->
90
- {#if activeFileItem}
91
- {#if activeFileItem.type === 'directory'}
92
- <svelte:self
93
- {canEdit}
94
- currentPath={[activeFileItem.path, activeFileItem.name].join('/')}
95
- directoryName={currentFileItem || ''}
96
- {documents}
97
- fileItems={activeFileItem.children || []}
98
- filePath={filePathSplit.join('/')}
99
- {getFilePreview}
100
- {routePrefix}
101
- {translations}
102
- />
103
- {:else}
104
- <FileExplorerFileDetailColumn
105
- fileItem={activeFileItem}
106
- {getFilePreview}
107
- {translations}
108
- on:delete-file
109
- />
110
- {/if}
111
- {/if}
112
-
113
77
  <style>/* eslint-disable */
114
78
  /**
115
79
  * Media queries and devices
@@ -218,8 +182,7 @@ function createDirectory() {
218
182
  margin-left: auto;
219
183
  }
220
184
  .file-explorer__search {
221
- margin-bottom: var(--file-explorer-default-spacing);
222
- padding: calc(var(--file-explorer-default-spacing) * 1.5) var(--file-explorer-default-spacing) 0 var(--file-explorer-default-spacing);
185
+ padding: calc(var(--file-explorer-default-spacing) * 0.5) calc(var(--file-explorer-default-spacing) * 0.5) 0 calc(var(--file-explorer-default-spacing) * 0.5);
223
186
  }
224
187
  .file-explorer__list {
225
188
  margin-bottom: var(--file-explorer-default-spacing);
@@ -363,5 +326,9 @@ function createDirectory() {
363
326
  .file-explorer__loader {
364
327
  display: flex;
365
328
  justify-content: center;
329
+ align-items: center;
330
+ min-height: 50px;
331
+ height: 100%;
332
+ font-size: 25px;
366
333
  }
367
334
  </style>
@@ -1,21 +1,15 @@
1
1
  import { SvelteComponent } from "svelte";
2
- import type { DocumentTranslations, FileItem, FilePreview, FileSystemItem } from '../index.js';
3
- import type { Refreshable } from '@securancy/svelte-utilities';
2
+ import type { DirectoryItem, DocumentTranslations, FileSystemItem } from '../index.js';
4
3
  import type { CreateDirectoryEvent, UploadFileEvent } from './FileExplorer.svelte';
5
4
  declare const __propDef: {
6
5
  props: {
7
- directoryName: string;
8
- filePath: string;
9
- fileItems: FileSystemItem[];
10
- currentPath: string;
6
+ item: DirectoryItem;
11
7
  routePrefix: string;
12
8
  translations: DocumentTranslations;
13
9
  canEdit: boolean;
14
- documents: Refreshable<FileSystemItem[]>;
15
- getFilePreview: (fileItem: FileItem) => Promise<FilePreview>;
10
+ activeFileItem: FileSystemItem | undefined;
16
11
  };
17
12
  events: {
18
- 'delete-file': CustomEvent<import("./FileExplorer.svelte").DeleteFileEvent>;
19
13
  'upload-file': CustomEvent<UploadFileEvent>;
20
14
  'create-directory': CustomEvent<CreateDirectoryEvent>;
21
15
  } & {
@@ -136,8 +136,7 @@ const modeIcons = {
136
136
  margin-left: auto;
137
137
  }
138
138
  .file-explorer__search {
139
- margin-bottom: var(--file-explorer-default-spacing);
140
- padding: calc(var(--file-explorer-default-spacing) * 1.5) var(--file-explorer-default-spacing) 0 var(--file-explorer-default-spacing);
139
+ padding: calc(var(--file-explorer-default-spacing) * 0.5) calc(var(--file-explorer-default-spacing) * 0.5) 0 calc(var(--file-explorer-default-spacing) * 0.5);
141
140
  }
142
141
  .file-explorer__list {
143
142
  margin-bottom: var(--file-explorer-default-spacing);
@@ -281,6 +280,10 @@ const modeIcons = {
281
280
  .file-explorer__loader {
282
281
  display: flex;
283
282
  justify-content: center;
283
+ align-items: center;
284
+ min-height: 50px;
285
+ height: 100%;
286
+ font-size: 25px;
284
287
  }
285
288
  label {
286
289
  position: relative;
@@ -7,6 +7,7 @@ import { createEventDispatcher } from "svelte";
7
7
  export let fileItem;
8
8
  export let getFilePreview;
9
9
  export let translations;
10
+ export let canEdit;
10
11
  const dispatch = createEventDispatcher();
11
12
  const fileDateFormat = "cccc FF";
12
13
  let filePreview;
@@ -71,11 +72,13 @@ async function refresh(_fileItem) {
71
72
  <div>{DateTime.fromJSDate(filePreview.modifiedAt).toFormat(fileDateFormat)}</div>
72
73
  </div>
73
74
  {/if}
74
- <div class="file-explorer-preview__info">
75
- <button type="button" on:click={() => dispatch('delete-file', fileItem)}>
76
- <Icon class="fa-solid fa-trash" />
77
- </button>
78
- </div>
75
+ {#if canEdit}
76
+ <div class="file-explorer-preview__info">
77
+ <button type="button" on:click={() => dispatch('delete-file', fileItem)}>
78
+ <Icon class="fa-solid fa-trash" />
79
+ </button>
80
+ </div>
81
+ {/if}
79
82
  </Card>
80
83
  {:else}
81
84
  <!-- @todo add loader if needed (test performance) -->
@@ -191,8 +194,7 @@ async function refresh(_fileItem) {
191
194
  margin-left: auto;
192
195
  }
193
196
  .file-explorer__search {
194
- margin-bottom: var(--file-explorer-default-spacing);
195
- padding: calc(var(--file-explorer-default-spacing) * 1.5) var(--file-explorer-default-spacing) 0 var(--file-explorer-default-spacing);
197
+ padding: calc(var(--file-explorer-default-spacing) * 0.5) calc(var(--file-explorer-default-spacing) * 0.5) 0 calc(var(--file-explorer-default-spacing) * 0.5);
196
198
  }
197
199
  .file-explorer__list {
198
200
  margin-bottom: var(--file-explorer-default-spacing);
@@ -336,5 +338,9 @@ async function refresh(_fileItem) {
336
338
  .file-explorer__loader {
337
339
  display: flex;
338
340
  justify-content: center;
341
+ align-items: center;
342
+ min-height: 50px;
343
+ height: 100%;
344
+ font-size: 25px;
339
345
  }
340
346
  </style>
@@ -6,6 +6,7 @@ declare const __propDef: {
6
6
  fileItem: FileItem;
7
7
  getFilePreview: (fileItem: FileItem) => Promise<FilePreview>;
8
8
  translations: DocumentTranslations;
9
+ canEdit: boolean;
9
10
  };
10
11
  events: {
11
12
  'delete-file': CustomEvent<DeleteFileEvent>;
@@ -161,8 +161,7 @@ function determineIconString(_extension) {
161
161
  margin-left: auto;
162
162
  }
163
163
  .file-explorer__search {
164
- margin-bottom: var(--file-explorer-default-spacing);
165
- padding: calc(var(--file-explorer-default-spacing) * 1.5) var(--file-explorer-default-spacing) 0 var(--file-explorer-default-spacing);
164
+ padding: calc(var(--file-explorer-default-spacing) * 0.5) calc(var(--file-explorer-default-spacing) * 0.5) 0 calc(var(--file-explorer-default-spacing) * 0.5);
166
165
  }
167
166
  .file-explorer__list {
168
167
  margin-bottom: var(--file-explorer-default-spacing);
@@ -306,5 +305,9 @@ function determineIconString(_extension) {
306
305
  .file-explorer__loader {
307
306
  display: flex;
308
307
  justify-content: center;
308
+ align-items: center;
309
+ min-height: 50px;
310
+ height: 100%;
311
+ font-size: 25px;
309
312
  }
310
313
  </style>
@@ -121,8 +121,7 @@
121
121
  }
122
122
 
123
123
  &__search {
124
- margin-bottom: var(--file-explorer-default-spacing);
125
- padding: calc(var(--file-explorer-default-spacing) * 1.5) var(--file-explorer-default-spacing) 0 var(--file-explorer-default-spacing);
124
+ padding: calc(var(--file-explorer-default-spacing) * 0.5) calc(var(--file-explorer-default-spacing) * 0.5) 0 calc(var(--file-explorer-default-spacing) * 0.5);
126
125
  }
127
126
 
128
127
  &__list {
@@ -300,5 +299,9 @@
300
299
  &__loader {
301
300
  display: flex;
302
301
  justify-content: center;
302
+ align-items: center;
303
+ min-height: 50px;
304
+ height: 100%;
305
+ font-size: 25px;
303
306
  }
304
307
  }
@@ -17,5 +17,6 @@ export declare const defaultTranslations: {
17
17
  uploadFile: string;
18
18
  size: string;
19
19
  items: string;
20
+ directoryAlreadyExists: string;
20
21
  };
21
22
  export type DocumentTranslations = typeof defaultTranslations;
@@ -16,4 +16,5 @@ export const defaultTranslations = {
16
16
  uploadFile: 'Upload file',
17
17
  size: 'Size',
18
18
  items: 'Items',
19
+ directoryAlreadyExists: 'Directory already exists',
19
20
  };
@@ -1,8 +1,9 @@
1
1
  import type { FileSystemItem } from '../models/file-item.js';
2
- export declare function openItem(item: FileSystemItem, routePrefix: string): Promise<void>;
2
+ export declare function getItemHref(item: FileSystemItem, routePrefix: string): string;
3
3
  export declare function getExtension(path: string): string;
4
4
  export declare const imageExtensions: string[];
5
5
  export declare const videoExtensions: string[];
6
6
  export declare function isImage(path: string): boolean;
7
7
  export declare function formatBytes(bytes: number, decimals?: number): string;
8
8
  export declare function formatBytesParts(bytes: number, decimals?: number): [number, string];
9
+ export declare function joinPaths(...paths: string[]): string;
@@ -1,5 +1,4 @@
1
- import { goto } from '$app/navigation';
2
- export async function openItem(item, routePrefix) {
1
+ export function getItemHref(item, routePrefix) {
3
2
  const itemPath = [
4
3
  ...routePrefix.split('/'),
5
4
  ...item.path.split('/'),
@@ -8,7 +7,7 @@ export async function openItem(item, routePrefix) {
8
7
  .filter((node) => node !== '')
9
8
  .map(encodeURIComponent)
10
9
  .join('/');
11
- await goto(itemPath.startsWith('/') ? itemPath : `/${itemPath}`);
10
+ return itemPath.startsWith('/') ? itemPath : `/${itemPath}`;
12
11
  }
13
12
  export function getExtension(path) {
14
13
  // extract file name from full path (supports `\\` and `/` separators)
@@ -47,3 +46,8 @@ export function formatBytesParts(bytes, decimals = 2) {
47
46
  const index = Math.floor(Math.log(bytes) / Math.log(k));
48
47
  return [Number.parseFloat((bytes / Math.pow(k, index)).toFixed(dm)), sizes[index]];
49
48
  }
49
+ export function joinPaths(...paths) {
50
+ return paths
51
+ .filter((path) => path !== '')
52
+ .join('/');
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@securancy/file-explorer",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "svelte": "./dist/index.js",
@@ -48,7 +48,7 @@
48
48
  "zod": "^3.23.8",
49
49
  "@securancy/eslint-config": "0.2.1",
50
50
  "@securancy/stylelint-config": "0.1.2",
51
- "@securancy/svelte-components": "4.2.4",
51
+ "@securancy/svelte-components": "4.3.0",
52
52
  "@securancy/svelte-utilities": "2.0.1"
53
53
  },
54
54
  "peerDependencies": {