@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.
- package/dist/components/FileExplorer.svelte +55 -37
- package/dist/components/FileExplorer.svelte.d.ts +2 -1
- package/dist/components/FileExplorerBreadcrumb.svelte +25 -5
- package/dist/components/FileExplorerBreadcrumb.svelte.d.ts +1 -0
- package/dist/components/FileExplorerCreateDirectory.svelte +19 -3
- package/dist/components/FileExplorerDirectory.svelte +329 -0
- package/dist/components/FileExplorerDirectory.svelte.d.ts +30 -0
- package/dist/components/FileExplorerDirectoryColumn.svelte +36 -117
- package/dist/components/FileExplorerDirectoryColumn.svelte.d.ts +8 -17
- package/dist/components/FileExplorerDirectoryIndex.svelte +21 -54
- package/dist/components/FileExplorerDirectoryIndex.svelte.d.ts +3 -9
- package/dist/components/FileExplorerDisplayModeSwitcher.svelte +5 -2
- package/dist/components/FileExplorerFileDetailColumn.svelte +13 -7
- package/dist/components/FileExplorerFileDetailColumn.svelte.d.ts +1 -0
- package/dist/components/FileIcon.svelte +5 -2
- package/dist/file-explorer.pcss +5 -2
- package/dist/models/translations.d.ts +1 -0
- package/dist/models/translations.js +1 -0
- package/dist/utilities/file-utilities.d.ts +2 -1
- package/dist/utilities/file-utilities.js +7 -3
- package/package.json +2 -2
|
@@ -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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
{
|
|
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
|
-
<
|
|
139
|
-
{
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
{
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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>
|
|
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
|
-
{
|
|
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
|
-
|
|
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>
|
|
@@ -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
|
|
37
|
-
|
|
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 {
|
|
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,
|
|
5
|
+
import { getExtension, getItemHref } from "../utilities/index.js";
|
|
7
6
|
import FileIcon from "./FileIcon.svelte";
|
|
8
|
-
import
|
|
9
|
-
import FileExplorerDirectoryIndex from "./FileExplorerDirectoryIndex.svelte";
|
|
7
|
+
import { joinPaths } from "../utilities/index.js";
|
|
10
8
|
const dispatch = createEventDispatcher();
|
|
11
|
-
export let
|
|
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
|
|
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:
|
|
18
|
+
dispatch("upload-file", { path: joinPaths(item.path, item.name) });
|
|
37
19
|
}
|
|
38
20
|
function createDirectory() {
|
|
39
|
-
dispatch("create-directory", { path:
|
|
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:
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
|
112
|
-
{#each
|
|
113
|
-
<
|
|
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 ===
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
121
|
-
<Icon class="fa-light fa-folder{activeFileItem ===
|
|
98
|
+
{#if child.type === 'directory'}
|
|
99
|
+
<Icon class="fa-light fa-folder{activeFileItem === child ? '-open' : ''}" />
|
|
122
100
|
{:else}
|
|
123
|
-
<FileIcon extension={getExtension(
|
|
101
|
+
<FileIcon extension={getExtension(child.name)} />
|
|
124
102
|
{/if}
|
|
125
|
-
<span>{
|
|
126
|
-
{#if
|
|
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
|
-
</
|
|
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
|
-
|
|
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
|
|
3
|
-
import type
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
5
|
+
import { getExtension, getItemHref, joinPaths } from "../utilities/index.js";
|
|
7
6
|
const dispatch = createEventDispatcher();
|
|
8
|
-
export let
|
|
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
|
|
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:
|
|
14
|
+
dispatch("upload-file", { path: joinPaths(item.path, item.name) });
|
|
26
15
|
}
|
|
27
16
|
function createDirectory() {
|
|
28
|
-
dispatch("create-directory", { path:
|
|
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
|
-
{
|
|
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
|
|
62
|
-
<
|
|
50
|
+
{#each item.children as child}
|
|
51
|
+
<a
|
|
63
52
|
class="file-explorer__grid-item"
|
|
64
|
-
class:file-explorer__grid-item--active={activeFileItem ===
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
57
|
+
{#if child.type === 'directory'}
|
|
70
58
|
<Icon
|
|
71
|
-
class="fa-light fa-2xl fa-folder{activeFileItem?.name ===
|
|
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(
|
|
63
|
+
<FileIcon extension={getExtension(child.name)} size="grid" />
|
|
76
64
|
{/if}
|
|
77
|
-
<span>{
|
|
78
|
-
</
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
<
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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>
|
|
@@ -161,8 +161,7 @@ function determineIconString(_extension) {
|
|
|
161
161
|
margin-left: auto;
|
|
162
162
|
}
|
|
163
163
|
.file-explorer__search {
|
|
164
|
-
|
|
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>
|
package/dist/file-explorer.pcss
CHANGED
|
@@ -121,8 +121,7 @@
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
&__search {
|
|
124
|
-
|
|
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
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { FileSystemItem } from '../models/file-item.js';
|
|
2
|
-
export declare function
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
51
|
+
"@securancy/svelte-components": "4.3.0",
|
|
52
52
|
"@securancy/svelte-utilities": "2.0.1"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|