@securancy/file-explorer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +7 -0
  2. package/default-styling.pcss +39 -0
  3. package/dist/components/FileExplorer.svelte +443 -0
  4. package/dist/components/FileExplorer.svelte.d.ts +63 -0
  5. package/dist/components/FileExplorerBreadcrumb.svelte +263 -0
  6. package/dist/components/FileExplorerBreadcrumb.svelte.d.ts +18 -0
  7. package/dist/components/FileExplorerCreateDirectory.svelte +77 -0
  8. package/dist/components/FileExplorerCreateDirectory.svelte.d.ts +24 -0
  9. package/dist/components/FileExplorerDirectoryColumn.svelte +456 -0
  10. package/dist/components/FileExplorerDirectoryColumn.svelte.d.ts +39 -0
  11. package/dist/components/FileExplorerDirectoryIndex.svelte +367 -0
  12. package/dist/components/FileExplorerDirectoryIndex.svelte.d.ts +33 -0
  13. package/dist/components/FileExplorerDisplayModeSwitcher.svelte +300 -0
  14. package/dist/components/FileExplorerDisplayModeSwitcher.svelte.d.ts +19 -0
  15. package/dist/components/FileExplorerFileDetailColumn.svelte +340 -0
  16. package/dist/components/FileExplorerFileDetailColumn.svelte.d.ts +24 -0
  17. package/dist/components/FileIcon.svelte +310 -0
  18. package/dist/components/FileIcon.svelte.d.ts +19 -0
  19. package/dist/file-explorer.pcss +304 -0
  20. package/dist/index.d.ts +3 -0
  21. package/dist/index.js +2 -0
  22. package/dist/models/file-explorer-display-mode.d.ts +4 -0
  23. package/dist/models/file-explorer-display-mode.js +5 -0
  24. package/dist/models/file-item.d.ts +23 -0
  25. package/dist/models/file-item.js +1 -0
  26. package/dist/models/index.d.ts +3 -0
  27. package/dist/models/index.js +3 -0
  28. package/dist/models/translations.d.ts +19 -0
  29. package/dist/models/translations.js +17 -0
  30. package/dist/utilities/file-utilities.d.ts +8 -0
  31. package/dist/utilities/file-utilities.js +49 -0
  32. package/dist/utilities/index.d.ts +1 -0
  33. package/dist/utilities/index.js +1 -0
  34. package/dist/validation/file-upload-schema.d.ts +2 -0
  35. package/dist/validation/file-upload-schema.js +2 -0
  36. package/package.json +72 -0
@@ -0,0 +1,456 @@
1
+ <script>import { createEventDispatcher } from "svelte";
2
+ import { fade } from "svelte/transition";
3
+ import { FileExplorerDisplayMode } from "..";
4
+ import { resize, ResizeDirections } from "@securancy/svelte-utilities";
5
+ import { Icon, Popover, PopoverItem } from "@securancy/svelte-components";
6
+ import { getExtension, openItem } from "../utilities/index.js";
7
+ import FileIcon from "./FileIcon.svelte";
8
+ import FileExplorerFileDetailColumn from "./FileExplorerFileDetailColumn.svelte";
9
+ import FileExplorerDirectoryIndex from "./FileExplorerDirectoryIndex.svelte";
10
+ 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;
17
+ export let sticky = false;
18
+ export let showScrollBackButton = false;
19
+ export let loading;
20
+ export let routePrefix;
21
+ export let canEdit;
22
+ export let translations;
23
+ export let getFilePreview;
24
+ let currentFileItem;
25
+ let activeFileItem;
26
+ let filePathSplit;
27
+ let dragoverIndex = 0;
28
+ 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
+ function addItem() {
36
+ dispatch("upload-file", { path: currentPath });
37
+ }
38
+ function createDirectory() {
39
+ dispatch("create-directory", { path: currentPath });
40
+ }
41
+ function handleDragEnter(event) {
42
+ event.preventDefault();
43
+ event.stopPropagation();
44
+ dragoverIndex++;
45
+ }
46
+ function handleDrop(event) {
47
+ event.preventDefault();
48
+ event.stopPropagation();
49
+ dragoverIndex = 0;
50
+ const eventData = { path: currentPath };
51
+ if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
52
+ eventData.files = event.dataTransfer.files;
53
+ }
54
+ dispatch("upload-file", eventData);
55
+ }
56
+ function handleDragLeave(event) {
57
+ event.preventDefault();
58
+ event.stopPropagation();
59
+ dragoverIndex--;
60
+ }
61
+ function handleDragEnd(event) {
62
+ event.preventDefault();
63
+ event.stopPropagation();
64
+ dragoverIndex = 0;
65
+ }
66
+ function handleDragOver(event) {
67
+ event.preventDefault();
68
+ event.stopPropagation();
69
+ }
70
+ </script>
71
+
72
+ <svelte:window bind:innerWidth />
73
+
74
+ <!-- ignored because users with assistive tech have an option to upload using the button -->
75
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
76
+ <div
77
+ class="file-explorer__column"
78
+ class:file-explorer__column--dragover={dragoverIndex > 0}
79
+ class:file-explorer__column--sticky={sticky}
80
+ on:dragend={handleDragEnd}
81
+ on:dragenter={handleDragEnter}
82
+ on:dragleave={handleDragLeave}
83
+ on:dragover={handleDragOver}
84
+ on:drop={handleDrop}
85
+ use:resize={{
86
+ disabled: innerWidth < 1024,
87
+ directions: [ResizeDirections.East],
88
+ parentPosition: sticky ? 'sticky' : 'relative',
89
+ parentPadding: true,
90
+ }}
91
+ >
92
+ <slot name="top" />
93
+ <div class="file-explorer__column-scroll-container">
94
+ <div class="file-explorer__list">
95
+ <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}
109
+ {/if}
110
+ </div>
111
+ {#if fileItems.length > 0}
112
+ {#each fileItems as item}
113
+ <button
114
+ 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)}
119
+ >
120
+ {#if item.type === 'directory'}
121
+ <Icon class="fa-light fa-folder{activeFileItem === item ? '-open' : ''}" />
122
+ {:else}
123
+ <FileIcon extension={getExtension(item.name)} />
124
+ {/if}
125
+ <span>{item.name}</span>
126
+ {#if item.type === 'directory'}
127
+ <Icon class="fa-light fa-sm fa-chevron-right" size="custom" />
128
+ {/if}
129
+ </button>
130
+ {/each}
131
+ {:else if loading}
132
+ <div class="file-explorer__loader">
133
+ <Icon class="fa-light fa-spinner-third fa-spin" />
134
+ </div>
135
+ {:else}
136
+ <p class="file-explorer__message">
137
+ {translations.emptyDirectory}
138
+ </p>
139
+ {/if}
140
+ </div>
141
+ </div>
142
+ </div>
143
+
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
+ <style>/* eslint-disable */
203
+ /**
204
+ * Media queries and devices
205
+ */
206
+ /**
207
+ * Some mixins that can be used dynamically inside @each loops
208
+ */
209
+ .file-explorer {
210
+ position: relative;
211
+ display: flex;
212
+ flex-direction: column;
213
+ height: 100%;
214
+ width: 100%;
215
+ background: var(--file-explorer-background);
216
+ overflow: hidden;
217
+ }
218
+ .file-explorer__controls {
219
+ flex-shrink: 0;
220
+ display: flex;
221
+ border-bottom: var(--file-explorer-border-style);
222
+ padding: var(--file-explorer-default-spacing);
223
+ background: var(--file-explorer-background-shade);
224
+ }
225
+ .file-explorer__content {
226
+ flex-grow: 1;
227
+ display: flex;
228
+ overflow-x: auto;
229
+ scroll-behavior: smooth;
230
+ }
231
+ /* Keep the index within screen */
232
+ @media (width >= 769px) {
233
+ /* @formatter:off */
234
+ .file-explorer--display-mode-index .file-explorer__content {
235
+ /* @formatter:on */
236
+ overflow-x: inherit;
237
+ }
238
+ }
239
+ .file-explorer__column, .file-explorer__index {
240
+ flex-shrink: 0;
241
+ border-right: var(--file-explorer-border-style);
242
+ background: var(--file-explorer-background);
243
+ }
244
+ .file-explorer__column {
245
+ display: flex;
246
+ flex-direction: column;
247
+ width: var(--file-explorer-column-default-width);
248
+ min-width: var(--file-explorer-column-min-width);
249
+ overflow: hidden;
250
+ }
251
+ .file-explorer__column--file-detail {
252
+ flex-grow: 1;
253
+ width: var(--file-explorer-preview-default-width);
254
+ display: flex;
255
+ flex-direction: column;
256
+ }
257
+ .file-explorer__column--sticky {
258
+ position: sticky;
259
+ left: 0;
260
+ z-index: 1;
261
+ }
262
+ .file-explorer__column::after {
263
+ content: '';
264
+ position: absolute;
265
+ inset: 0;
266
+ pointer-events: none;
267
+ transition: background-color 0.2s ease-in-out;
268
+ }
269
+ .file-explorer__column--dragover {
270
+ cursor: copy;
271
+ }
272
+ .file-explorer__column--dragover::after {
273
+ pointer-events: all;
274
+ background-color: var(--file-explorer-background-dragover);
275
+ }
276
+ .file-explorer__index {
277
+ display: flex;
278
+ flex-direction: column;
279
+ width: var(--file-explorer-index-default-width);
280
+ overflow: hidden;
281
+ }
282
+ @media (width >= 769px) {
283
+ .file-explorer__index {
284
+ flex-shrink: 1
285
+ }
286
+ }
287
+ .file-explorer__index--hidden {
288
+ display: none;
289
+ }
290
+ .file-explorer__column-scroll-container, .file-explorer__index-scroll-container {
291
+ flex-grow: 1;
292
+ overflow-y: auto;
293
+ padding: calc(var(--file-explorer-default-spacing) * 1.5) var(--file-explorer-default-spacing) 0 var(--file-explorer-default-spacing);
294
+ }
295
+ .file-explorer__grid {
296
+ display: flex;
297
+ align-items: flex-start;
298
+ flex-wrap: wrap;
299
+ gap: var(--file-explorer-default-spacing);
300
+ }
301
+ .file-explorer__breadcrumb {
302
+ /* @todo */
303
+ }
304
+ .file-explorer__display-mode-switcher {
305
+ display: flex;
306
+ gap: calc(0.5 * var(--file-explorer-default-spacing));
307
+ margin-left: auto;
308
+ }
309
+ .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);
312
+ }
313
+ .file-explorer__list {
314
+ margin-bottom: var(--file-explorer-default-spacing);
315
+ }
316
+ .file-explorer__list-header {
317
+ display: flex;
318
+ justify-content: space-between;
319
+ gap: calc(0.5 * var(--file-explorer-default-spacing));
320
+ width: 100%;
321
+ margin-bottom: calc(0.5 * var(--file-explorer-default-spacing));
322
+ color: var(--file-explorer-list-header-color);
323
+ }
324
+ .file-explorer__list-header span {
325
+ overflow: hidden;
326
+ white-space: nowrap;
327
+ text-overflow: ellipsis;
328
+ }
329
+ .file-explorer__list-header button {
330
+ flex-shrink: 0;
331
+ color: var(--file-explorer-action-primary-color);
332
+ cursor: pointer;
333
+ }
334
+ .file-explorer__grid-header {
335
+ width: 100%;
336
+ }
337
+ .file-explorer__grid-header button {
338
+ color: var(--file-explorer-action-primary-color);
339
+ cursor: pointer;
340
+ }
341
+ .file-explorer__list-item, .file-explorer__grid-item {
342
+ border-radius: var(--file-explorer-list-item-border-radius);
343
+ background: none;
344
+ color: var(--file-explorer-list-item-color);
345
+ }
346
+ .file-explorer__list-item > :global(i), .file-explorer__grid-item > :global(i) {
347
+ flex-shrink: 0;
348
+ }
349
+ .file-explorer__list-item:not(.file-explorer__list-item--active):hover, .file-explorer__grid-item:not(.file-explorer__grid-item--active):hover {
350
+ background: var(--file-explorer-list-item-background-hover);
351
+ }
352
+ .file-explorer__list-item--active, .file-explorer__grid-item--active {
353
+ color: var(--file-explorer-list-item-color-active);
354
+ background: var(--file-explorer-list-item-background-active);
355
+ }
356
+ .file-explorer__grid-item {
357
+ display: flex;
358
+ flex-direction: column;
359
+ align-items: center;
360
+
361
+ /* 3 items in a grid, spaced out with default spacing means the width of 2 gaps must be subtracted from width */
362
+ width: calc(33.33% - (0.6666 * var(--file-explorer-default-spacing)));
363
+ padding: 5px 2px;
364
+ }
365
+ @media (width >= 769px) {
366
+ .file-explorer__grid-item {
367
+ width: 100px
368
+ }
369
+ }
370
+ .file-explorer__grid-item--active {
371
+ /* @todo */
372
+ }
373
+ .file-explorer__grid-item > span {
374
+ word-break: break-word;
375
+ margin-top: calc(0.25 * var(--file-explorer-default-spacing));
376
+ }
377
+ .file-explorer__list-item {
378
+ display: flex;
379
+ align-items: baseline;
380
+ width: 100%;
381
+ text-align: left;
382
+ padding: var(--file-explorer-list-item-spacing);
383
+ white-space: nowrap;
384
+ overflow: hidden;
385
+ text-overflow: ellipsis;
386
+ }
387
+ .file-explorer__list-item > span {
388
+ flex-grow: 1;
389
+ min-width: 0;
390
+ overflow: hidden;
391
+ text-overflow: ellipsis;
392
+ }
393
+ .file-explorer__list-item > :global(i:first-child) {
394
+ margin-right: var(--file-explorer-list-item-spacing);
395
+ }
396
+ .file-explorer__list-item > :global(i:last-child) {
397
+ margin-left: var(--file-explorer-list-item-spacing);
398
+ }
399
+ .file-explorer__message {
400
+ display: block;
401
+ padding: var(--file-explorer-list-item-spacing) 0;
402
+ color: var(--file-explorer-message-color);
403
+ }
404
+ .file-explorer__image-preview {
405
+ max-width: 100%;
406
+ margin: 0 auto;
407
+ }
408
+ .file-explorer-preview__title {
409
+ font-size: var(--font-size--h1);
410
+ font-weight: var(--font-weight--semi-bold);
411
+ overflow: hidden;
412
+ text-overflow: ellipsis;
413
+ white-space: nowrap;
414
+ }
415
+ .file-explorer-preview__download {
416
+ color: var(--file-explorer-action-primary-color);
417
+ font-weight: var(--font-weight--semi-bold);
418
+ }
419
+ .file-explorer-preview__download:hover {
420
+ text-decoration-line: underline;
421
+ }
422
+ .file-explorer-preview__info {
423
+ margin-top: 14px;
424
+ }
425
+ .file-explorer-preview__info span {
426
+ color: var(--file-explorer-message-color);
427
+ }
428
+ .file-explorer-preview__info button {
429
+ cursor: pointer;
430
+ }
431
+ .file-explorer__scroll-back-button {
432
+ position: fixed;
433
+ bottom: var(--file-explorer-default-spacing);
434
+ right: var(--file-explorer-default-spacing);
435
+ z-index: 2;
436
+ background: var(--file-explorer-action-primary-color);
437
+ color: var(--file-explorer-action-primary-color-contrast);
438
+ padding: var(--file-explorer-list-item-spacing);
439
+ border-radius: var(--file-explorer-list-item-border-radius);
440
+ }
441
+ .file-explorer__file-icon--large {
442
+ width: 100%;
443
+ text-align: center;
444
+ padding: calc(2 * var(--file-explorer-default-spacing));
445
+ }
446
+ .file-explorer__file-icon--large :global(i) {
447
+ font-size: var(--file-explorer-preview-icon-size);
448
+ }
449
+ .file-explorer__text--semi-bold {
450
+ font-weight: var(--file-explorer-text-semi-bold);
451
+ }
452
+ .file-explorer__loader {
453
+ display: flex;
454
+ justify-content: center;
455
+ }
456
+ </style>
@@ -0,0 +1,39 @@
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';
4
+ declare const __propDef: {
5
+ props: {
6
+ directoryName: string;
7
+ filePath: string;
8
+ currentPath: string;
9
+ fileItems: FileSystemItem[];
10
+ documents: Refreshable<FileSystemItem[]>;
11
+ displayMode: FileExplorerDisplayMode;
12
+ sticky?: boolean;
13
+ showScrollBackButton?: boolean;
14
+ loading: boolean;
15
+ routePrefix: string;
16
+ canEdit: boolean;
17
+ translations: DocumentTranslations;
18
+ getFilePreview: (fileItem: FileItem) => Promise<FilePreview>;
19
+ };
20
+ events: {
21
+ 'upload-file': CustomEvent<any>;
22
+ 'create-directory': CustomEvent<any>;
23
+ 'delete-file': CustomEvent<import("..").DeleteFileEvent>;
24
+ 'scroll-back': CustomEvent<void>;
25
+ } & {
26
+ [evt: string]: CustomEvent<any>;
27
+ };
28
+ slots: {
29
+ top: {};
30
+ };
31
+ exports?: {} | undefined;
32
+ bindings?: string | undefined;
33
+ };
34
+ export type FileExplorerDirectoryColumnProps = typeof __propDef.props;
35
+ export type FileExplorerDirectoryColumnEvents = typeof __propDef.events;
36
+ export type FileExplorerDirectoryColumnSlots = typeof __propDef.slots;
37
+ export default class FileExplorerDirectoryColumn extends SvelteComponent<FileExplorerDirectoryColumnProps, FileExplorerDirectoryColumnEvents, FileExplorerDirectoryColumnSlots> {
38
+ }
39
+ export {};