@securancy/file-explorer 1.0.4 → 1.0.6
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.
- package/dist/components/FileExplorer.svelte +68 -7
- package/dist/components/FileExplorer.svelte.d.ts +21 -11
- package/dist/components/FileExplorerCreateDirectory.svelte +12 -14
- package/dist/components/FileExplorerCreateDirectory.svelte.d.ts +1 -1
- package/dist/components/FileExplorerDirectoryColumn.svelte +1 -1
- package/dist/components/FileExplorerDirectoryIndex.svelte +1 -1
- package/dist/components/FileExplorerDisplayModeSwitcher.svelte +3 -6
- package/dist/components/FileExplorerDisplayModeSwitcher.svelte.d.ts +2 -1
- package/dist/components/FileExplorerFileDetailColumn.svelte +1 -1
- package/dist/models/translations.d.ts +21 -11
- package/dist/models/translations.js +21 -11
- package/dist/utilities/file-utilities.d.ts +1 -0
- package/dist/utilities/file-utilities.js +6 -0
- package/package.json +3 -3
|
@@ -9,6 +9,7 @@ import { Button, Modal } from "@securancy/svelte-components";
|
|
|
9
9
|
import FileExplorerDirectory from "./FileExplorerDirectory.svelte";
|
|
10
10
|
import { Icon } from "@securancy/svelte-components";
|
|
11
11
|
import { fade } from "svelte/transition";
|
|
12
|
+
import { pathsEqual } from "../utilities";
|
|
12
13
|
export let displayMode = FileExplorerDisplayMode.Columns;
|
|
13
14
|
export let filePath;
|
|
14
15
|
export let documents;
|
|
@@ -29,6 +30,9 @@ let createDirectoryModalOpen = false;
|
|
|
29
30
|
let deleteFileModalOpen = false;
|
|
30
31
|
let deleteFileEvent;
|
|
31
32
|
let deleting = false;
|
|
33
|
+
let overrideFilesModalOpen = false;
|
|
34
|
+
let overrideFiles = [];
|
|
35
|
+
let uploadFilesRequest;
|
|
32
36
|
$: if (filePath) tick().then(() => scrollToRight());
|
|
33
37
|
async function scrollToRight() {
|
|
34
38
|
if (!fileExplorerContentContainer) return;
|
|
@@ -49,23 +53,21 @@ function handleScrollBack() {
|
|
|
49
53
|
async function handleUploadFile(event) {
|
|
50
54
|
if (uploadFiles) {
|
|
51
55
|
if (event.detail.files) {
|
|
52
|
-
await
|
|
56
|
+
await uploadFilesInternal({
|
|
53
57
|
path: event.detail.path,
|
|
54
58
|
files: event.detail.files
|
|
55
59
|
});
|
|
56
|
-
documents.refresh();
|
|
57
60
|
} else {
|
|
58
61
|
const input = document.createElement("input");
|
|
59
62
|
input.type = "file";
|
|
60
63
|
input.multiple = true;
|
|
61
64
|
input.click();
|
|
62
|
-
input.addEventListener("change", async (
|
|
63
|
-
if (input.files
|
|
64
|
-
await
|
|
65
|
+
input.addEventListener("change", async () => {
|
|
66
|
+
if (input.files) {
|
|
67
|
+
await uploadFilesInternal({
|
|
65
68
|
path: event.detail.path,
|
|
66
69
|
files: input.files
|
|
67
70
|
});
|
|
68
|
-
documents.refresh();
|
|
69
71
|
}
|
|
70
72
|
});
|
|
71
73
|
}
|
|
@@ -73,6 +75,30 @@ async function handleUploadFile(event) {
|
|
|
73
75
|
dispatch("upload-file", event.detail);
|
|
74
76
|
}
|
|
75
77
|
}
|
|
78
|
+
async function uploadFilesInternal(request) {
|
|
79
|
+
const filesInDirectory = Array.from(flattenedFiles($documents)).filter((x) => pathsEqual(x.path, request.path));
|
|
80
|
+
const existingFiles = Array.from(request.files).filter((file) => filesInDirectory.some((document2) => document2.name === file.name));
|
|
81
|
+
if (existingFiles.length > 0) {
|
|
82
|
+
overrideFiles = existingFiles;
|
|
83
|
+
overrideFilesModalOpen = true;
|
|
84
|
+
uploadFilesRequest = request;
|
|
85
|
+
} else {
|
|
86
|
+
await uploadFiles(request);
|
|
87
|
+
documents.refresh();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function confirmOverrideFiles() {
|
|
91
|
+
await uploadFiles(uploadFilesRequest);
|
|
92
|
+
documents.refresh();
|
|
93
|
+
overrideFilesModalOpen = false;
|
|
94
|
+
overrideFiles = [];
|
|
95
|
+
uploadFilesRequest = void 0;
|
|
96
|
+
}
|
|
97
|
+
async function cancelOverrideFiles() {
|
|
98
|
+
overrideFilesModalOpen = false;
|
|
99
|
+
overrideFiles = [];
|
|
100
|
+
uploadFilesRequest = void 0;
|
|
101
|
+
}
|
|
76
102
|
function handleCreateDirectory(event) {
|
|
77
103
|
if (createDirectory) {
|
|
78
104
|
createDirectoryPath = event.detail.path;
|
|
@@ -106,12 +132,21 @@ function deleteFileInternal() {
|
|
|
106
132
|
});
|
|
107
133
|
}
|
|
108
134
|
}
|
|
135
|
+
function* flattenedFiles(documents2) {
|
|
136
|
+
for (const document2 of documents2) {
|
|
137
|
+
if (document2.type === "file") {
|
|
138
|
+
yield document2;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
yield* flattenedFiles(document2.children);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
109
144
|
</script>
|
|
110
145
|
|
|
111
146
|
<div class="file-explorer file-explorer--display-mode-{displayMode}">
|
|
112
147
|
<div class="file-explorer__controls">
|
|
113
148
|
<FileExplorerBreadcrumb {filePath} {routePrefix} />
|
|
114
|
-
<FileExplorerDisplayModeSwitcher bind:displayMode />
|
|
149
|
+
<FileExplorerDisplayModeSwitcher {translations} bind:displayMode />
|
|
115
150
|
</div>
|
|
116
151
|
{#if $$slots.search}
|
|
117
152
|
<div class="file-explorer__search">
|
|
@@ -201,6 +236,32 @@ function deleteFileInternal() {
|
|
|
201
236
|
</Modal>
|
|
202
237
|
{/if}
|
|
203
238
|
|
|
239
|
+
{#if overrideFilesModalOpen}
|
|
240
|
+
<Modal
|
|
241
|
+
title={translations.overrideFiles}
|
|
242
|
+
bind:opened={overrideFilesModalOpen}
|
|
243
|
+
>
|
|
244
|
+
<div>
|
|
245
|
+
{translations.overrideFilesConfirmation}
|
|
246
|
+
</div>
|
|
247
|
+
<div class="file-explorer__text file-explorer__text--semi-bold">
|
|
248
|
+
{overrideFiles.map((file) => file.name).join(', ')}
|
|
249
|
+
</div>
|
|
250
|
+
<svelte:fragment slot="footer">
|
|
251
|
+
<Button
|
|
252
|
+
color="soft"
|
|
253
|
+
fill="clear"
|
|
254
|
+
on:click={cancelOverrideFiles}
|
|
255
|
+
>
|
|
256
|
+
{translations.cancel}
|
|
257
|
+
</Button>
|
|
258
|
+
<Button color="danger" on:click={confirmOverrideFiles}>
|
|
259
|
+
{translations.override}
|
|
260
|
+
</Button>
|
|
261
|
+
</svelte:fragment>
|
|
262
|
+
</Modal>
|
|
263
|
+
{/if}
|
|
264
|
+
|
|
204
265
|
<style>/* eslint-disable */
|
|
205
266
|
/**
|
|
206
267
|
* Media queries and devices
|
|
@@ -21,24 +21,34 @@ declare const __propDef: {
|
|
|
21
21
|
routePrefix: string;
|
|
22
22
|
canEdit: boolean | ((fileItem: FileSystemItem) => boolean);
|
|
23
23
|
translations?: {
|
|
24
|
-
createDirectoryModalTitle: string;
|
|
25
24
|
cancel: string;
|
|
26
25
|
create: string;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
createDirectory: string;
|
|
27
|
+
createDirectoryModalTitle: string;
|
|
28
|
+
createDirectoryPlaceholder: string;
|
|
29
|
+
createItemTitle: string;
|
|
31
30
|
createdAt: string;
|
|
32
|
-
|
|
31
|
+
creating: string;
|
|
32
|
+
delete: string;
|
|
33
33
|
deleteFile: string;
|
|
34
34
|
deleteFileConfirmation: string;
|
|
35
35
|
deleting: string;
|
|
36
|
-
delete: string;
|
|
37
|
-
createDirectory: string;
|
|
38
|
-
uploadFile: string;
|
|
39
|
-
size: string;
|
|
40
|
-
items: string;
|
|
41
36
|
directoryAlreadyExists: string;
|
|
37
|
+
displayModeLabels: {
|
|
38
|
+
columns: string;
|
|
39
|
+
index: string;
|
|
40
|
+
};
|
|
41
|
+
displayModeSwitch: string;
|
|
42
|
+
emptyDirectory: string;
|
|
43
|
+
itemTitle: (item: FileSystemItem) => string;
|
|
44
|
+
items: string;
|
|
45
|
+
modifiedAt: string;
|
|
46
|
+
override: string;
|
|
47
|
+
overrideFiles: string;
|
|
48
|
+
overrideFilesConfirmation: string;
|
|
49
|
+
previewFile: string;
|
|
50
|
+
size: string;
|
|
51
|
+
uploadFile: string;
|
|
42
52
|
};
|
|
43
53
|
getFilePreview: (fileItem: FileItem) => Promise<FilePreview>;
|
|
44
54
|
createDirectory?: ((request: CreateDirectoryRequest) => Promise<void>) | undefined;
|
|
@@ -4,20 +4,13 @@ export let createDirectoryModalOpen = false;
|
|
|
4
4
|
export let documents;
|
|
5
5
|
export let createDirectory;
|
|
6
6
|
export let translations;
|
|
7
|
-
|
|
8
|
-
$: directoryAlreadyExists = existingFoldersInPath.some((x) => x.name === request.name);
|
|
9
|
-
let request;
|
|
7
|
+
let request = { path: "", name: "" };
|
|
10
8
|
let submitting = false;
|
|
11
9
|
let input = void 0;
|
|
12
|
-
|
|
13
|
-
$:
|
|
10
|
+
$: existingFoldersInPath = getExistingFoldersInPath(createDirectoryPath, $documents);
|
|
11
|
+
$: directoryAlreadyExists = existingFoldersInPath.some((x) => x.name === request.name);
|
|
12
|
+
$: request.path = createDirectoryPath ?? "";
|
|
14
13
|
$: if (input) input.focus();
|
|
15
|
-
function initDocumentUpload() {
|
|
16
|
-
request = {
|
|
17
|
-
path: createDirectoryPath,
|
|
18
|
-
name: ""
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
14
|
async function submitFiles() {
|
|
22
15
|
submitting = true;
|
|
23
16
|
await createDirectory(request).then(() => {
|
|
@@ -28,8 +21,7 @@ async function submitFiles() {
|
|
|
28
21
|
documents.refresh();
|
|
29
22
|
}
|
|
30
23
|
function getExistingFoldersInPath(path, documents2) {
|
|
31
|
-
|
|
32
|
-
const pathParts = path.split("/");
|
|
24
|
+
const pathParts = path ? path.split("/") : [];
|
|
33
25
|
let existingFoldersInPath2 = documents2.filter((x) => x.type === "directory");
|
|
34
26
|
for (const pathPart of pathParts) {
|
|
35
27
|
const currentFolder = existingFoldersInPath2.find((document) => document.name === pathPart);
|
|
@@ -50,7 +42,13 @@ function getExistingFoldersInPath(path, documents2) {
|
|
|
50
42
|
error={directoryAlreadyExists ? translations.directoryAlreadyExists : undefined}
|
|
51
43
|
floatError
|
|
52
44
|
>
|
|
53
|
-
<input
|
|
45
|
+
<input
|
|
46
|
+
bind:this={input}
|
|
47
|
+
id="file-explorer-create-directory-name"
|
|
48
|
+
placeholder={translations.createDirectoryPlaceholder}
|
|
49
|
+
type="text"
|
|
50
|
+
bind:value={request.name}
|
|
51
|
+
/>
|
|
54
52
|
</FormElement>
|
|
55
53
|
</div>
|
|
56
54
|
<svelte:fragment slot="footer">
|
|
@@ -3,7 +3,7 @@ import type { Refreshable } from '@securancy/svelte-utilities';
|
|
|
3
3
|
import type { CreateDirectoryRequest, DocumentTranslations, FileSystemItem } from '../models/index.js';
|
|
4
4
|
declare const __propDef: {
|
|
5
5
|
props: {
|
|
6
|
-
createDirectoryPath: string;
|
|
6
|
+
createDirectoryPath: string | undefined;
|
|
7
7
|
createDirectoryModalOpen?: boolean;
|
|
8
8
|
documents: Refreshable<FileSystemItem[]>;
|
|
9
9
|
createDirectory: (request: CreateDirectoryRequest) => Promise<void>;
|
|
@@ -79,7 +79,7 @@ function handleDragOver(event) {
|
|
|
79
79
|
</span>
|
|
80
80
|
{#if canEdit}
|
|
81
81
|
<Popover placement="bottom-end" trigger="click">
|
|
82
|
-
<button slot="trigger" type="button">
|
|
82
|
+
<button slot="trigger" title={translations.createItemTitle} type="button">
|
|
83
83
|
<Icon class="fa-solid fa-add" />
|
|
84
84
|
</button>
|
|
85
85
|
<PopoverItem closeOnClick on:click={createDirectory}>{translations.createDirectory}</PopoverItem>
|
|
@@ -38,7 +38,7 @@ function createDirectory() {
|
|
|
38
38
|
{#if canEdit}
|
|
39
39
|
<div class="inline-block">
|
|
40
40
|
<Popover placement="bottom-end" trigger="click">
|
|
41
|
-
<button slot="trigger" type="button">
|
|
41
|
+
<button slot="trigger" title={translations.createItemTitle} type="button">
|
|
42
42
|
<Icon class="fa-solid fa-add" />
|
|
43
43
|
</button>
|
|
44
44
|
<PopoverItem closeOnClick on:click={createDirectory}>{translations.createDirectory}</PopoverItem>
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
<script>import { FileExplorerDisplayMode } from "../index.js";
|
|
2
2
|
import { Icon } from "@securancy/svelte-components";
|
|
3
3
|
export let displayMode;
|
|
4
|
+
export let translations;
|
|
4
5
|
let modes = Object.values(FileExplorerDisplayMode);
|
|
5
|
-
const modeLabels = {
|
|
6
|
-
columns: "Columns",
|
|
7
|
-
index: "Full directory"
|
|
8
|
-
};
|
|
9
6
|
const modeIcons = {
|
|
10
7
|
columns: "fa-columns-3",
|
|
11
8
|
index: "fa-grid"
|
|
@@ -15,8 +12,8 @@ const modeIcons = {
|
|
|
15
12
|
<div class="file-explorer__display-mode-switcher">
|
|
16
13
|
{#each modes as mode}
|
|
17
14
|
<label
|
|
18
|
-
aria-label={
|
|
19
|
-
title="
|
|
15
|
+
aria-label={translations.displayModeLabels[mode]}
|
|
16
|
+
title="{translations.displayModeSwitch} {translations.displayModeLabels[mode].toLowerCase()}"
|
|
20
17
|
>
|
|
21
18
|
<input
|
|
22
19
|
name="displayMode"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
|
2
|
-
import { FileExplorerDisplayMode } from '../index.js';
|
|
2
|
+
import { type DocumentTranslations, FileExplorerDisplayMode } from '../index.js';
|
|
3
3
|
declare const __propDef: {
|
|
4
4
|
props: {
|
|
5
5
|
displayMode: FileExplorerDisplayMode;
|
|
6
|
+
translations: DocumentTranslations;
|
|
6
7
|
};
|
|
7
8
|
events: {
|
|
8
9
|
[evt: string]: CustomEvent<any>;
|
|
@@ -74,7 +74,7 @@ async function refresh(_fileItem) {
|
|
|
74
74
|
{/if}
|
|
75
75
|
{#if canEdit}
|
|
76
76
|
<div class="file-explorer-preview__info">
|
|
77
|
-
<button type="button" on:click={() => dispatch('delete-file', fileItem)}>
|
|
77
|
+
<button id="file-explorer-delete-file" type="button" on:click={() => dispatch('delete-file', fileItem)}>
|
|
78
78
|
<Icon class="fa-solid fa-trash" />
|
|
79
79
|
</button>
|
|
80
80
|
</div>
|
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
import type { FileSystemItem } from './file-item.js';
|
|
2
2
|
export declare const defaultTranslations: {
|
|
3
|
-
createDirectoryModalTitle: string;
|
|
4
3
|
cancel: string;
|
|
5
4
|
create: string;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
createDirectory: string;
|
|
6
|
+
createDirectoryModalTitle: string;
|
|
7
|
+
createDirectoryPlaceholder: string;
|
|
8
|
+
createItemTitle: string;
|
|
10
9
|
createdAt: string;
|
|
11
|
-
|
|
10
|
+
creating: string;
|
|
11
|
+
delete: string;
|
|
12
12
|
deleteFile: string;
|
|
13
13
|
deleteFileConfirmation: string;
|
|
14
14
|
deleting: string;
|
|
15
|
-
delete: string;
|
|
16
|
-
createDirectory: string;
|
|
17
|
-
uploadFile: string;
|
|
18
|
-
size: string;
|
|
19
|
-
items: string;
|
|
20
15
|
directoryAlreadyExists: string;
|
|
16
|
+
displayModeLabels: {
|
|
17
|
+
columns: string;
|
|
18
|
+
index: string;
|
|
19
|
+
};
|
|
20
|
+
displayModeSwitch: string;
|
|
21
|
+
emptyDirectory: string;
|
|
22
|
+
itemTitle: (item: FileSystemItem) => string;
|
|
23
|
+
items: string;
|
|
24
|
+
modifiedAt: string;
|
|
25
|
+
override: string;
|
|
26
|
+
overrideFiles: string;
|
|
27
|
+
overrideFilesConfirmation: string;
|
|
28
|
+
previewFile: string;
|
|
29
|
+
size: string;
|
|
30
|
+
uploadFile: string;
|
|
21
31
|
};
|
|
22
32
|
export type DocumentTranslations = typeof defaultTranslations;
|
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
export const defaultTranslations = {
|
|
2
|
-
createDirectoryModalTitle: 'Create new directory',
|
|
3
2
|
cancel: 'Cancel',
|
|
4
3
|
create: 'Create',
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
createDirectory: 'Create directory',
|
|
5
|
+
createDirectoryModalTitle: 'Create new directory',
|
|
6
|
+
createDirectoryPlaceholder: 'Name',
|
|
7
|
+
createItemTitle: 'Create directory or upload file',
|
|
9
8
|
createdAt: 'Created at',
|
|
10
|
-
|
|
9
|
+
creating: 'Creating...',
|
|
10
|
+
delete: 'Delete',
|
|
11
11
|
deleteFile: 'Delete file',
|
|
12
12
|
deleteFileConfirmation: 'Are you sure you want to delete:',
|
|
13
13
|
deleting: 'Deleting...',
|
|
14
|
-
delete: 'Delete',
|
|
15
|
-
createDirectory: 'Create directory',
|
|
16
|
-
uploadFile: 'Upload file',
|
|
17
|
-
size: 'Size',
|
|
18
|
-
items: 'Items',
|
|
19
14
|
directoryAlreadyExists: 'Directory already exists',
|
|
15
|
+
displayModeLabels: {
|
|
16
|
+
columns: 'Columns',
|
|
17
|
+
index: 'Full directory',
|
|
18
|
+
},
|
|
19
|
+
displayModeSwitch: 'Switch display mode to',
|
|
20
|
+
emptyDirectory: 'Empty directory',
|
|
21
|
+
itemTitle: (item) => `Open ${item.type === 'directory' ? 'directory' : 'file'} ${item.name}`,
|
|
22
|
+
items: 'Items',
|
|
23
|
+
modifiedAt: 'Last modified at',
|
|
24
|
+
override: 'Override',
|
|
25
|
+
overrideFiles: 'Override files',
|
|
26
|
+
overrideFilesConfirmation: 'These files already exist, do you want to override them?',
|
|
27
|
+
previewFile: 'Preview file',
|
|
28
|
+
size: 'Size',
|
|
29
|
+
uploadFile: 'Upload file',
|
|
20
30
|
};
|
|
@@ -7,3 +7,4 @@ 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
9
|
export declare function joinPaths(...paths: string[]): string;
|
|
10
|
+
export declare function pathsEqual(path1: string, path2: string): boolean;
|
|
@@ -48,6 +48,12 @@ export function formatBytesParts(bytes, decimals = 2) {
|
|
|
48
48
|
}
|
|
49
49
|
export function joinPaths(...paths) {
|
|
50
50
|
return paths
|
|
51
|
+
.map((path) => path.split('/').filter((part) => part !== '').join('/'))
|
|
51
52
|
.filter((path) => path !== '')
|
|
52
53
|
.join('/');
|
|
53
54
|
}
|
|
55
|
+
export function pathsEqual(path1, path2) {
|
|
56
|
+
const path1Parts = path1.split('/').filter((part) => part !== '');
|
|
57
|
+
const path2Parts = path2.split('/').filter((part) => part !== '');
|
|
58
|
+
return path1Parts.length === path2Parts.length && path1Parts.every((part, index) => part === path2Parts[index]);
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@securancy/file-explorer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"svelte": "./dist/index.js",
|
|
@@ -48,8 +48,8 @@
|
|
|
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.3.
|
|
52
|
-
"@securancy/svelte-utilities": "2.0.
|
|
51
|
+
"@securancy/svelte-components": "4.3.2",
|
|
52
|
+
"@securancy/svelte-utilities": "2.0.2"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"svelte": "^4.2.18"
|