@leavittsoftware/web 1.22.0 → 1.23.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 (42) hide show
  1. package/leavitt/file-explorer/add-folder-modal.d.ts +359 -0
  2. package/leavitt/file-explorer/add-folder-modal.js +135 -0
  3. package/leavitt/file-explorer/add-folder-modal.js.map +1 -0
  4. package/leavitt/file-explorer/events/file-explorer-events.d.ts +4 -0
  5. package/leavitt/file-explorer/events/file-explorer-events.js +5 -0
  6. package/leavitt/file-explorer/events/file-explorer-events.js.map +1 -0
  7. package/leavitt/file-explorer/file-explorer-error.d.ts +7 -0
  8. package/leavitt/file-explorer/file-explorer-error.js +38 -0
  9. package/leavitt/file-explorer/file-explorer-error.js.map +1 -0
  10. package/leavitt/file-explorer/file-explorer-image.d.ts +9 -0
  11. package/leavitt/file-explorer/file-explorer-image.js +54 -0
  12. package/leavitt/file-explorer/file-explorer-image.js.map +1 -0
  13. package/leavitt/file-explorer/file-explorer-no-files.d.ts +7 -0
  14. package/leavitt/file-explorer/file-explorer-no-files.js +38 -0
  15. package/leavitt/file-explorer/file-explorer-no-files.js.map +1 -0
  16. package/leavitt/file-explorer/file-explorer-no-permission.d.ts +7 -0
  17. package/leavitt/file-explorer/file-explorer-no-permission.js +38 -0
  18. package/leavitt/file-explorer/file-explorer-no-permission.js.map +1 -0
  19. package/leavitt/file-explorer/file-explorer.d.ts +411 -0
  20. package/leavitt/file-explorer/file-explorer.js +871 -0
  21. package/leavitt/file-explorer/file-explorer.js.map +1 -0
  22. package/leavitt/file-explorer/file-list-item.d.ts +15 -0
  23. package/leavitt/file-explorer/file-list-item.js +273 -0
  24. package/leavitt/file-explorer/file-list-item.js.map +1 -0
  25. package/leavitt/file-explorer/file-modal.d.ts +362 -0
  26. package/leavitt/file-explorer/file-modal.js +323 -0
  27. package/leavitt/file-explorer/file-modal.js.map +1 -0
  28. package/leavitt/file-explorer/folder-list-item.d.ts +14 -0
  29. package/leavitt/file-explorer/folder-list-item.js +188 -0
  30. package/leavitt/file-explorer/folder-list-item.js.map +1 -0
  31. package/leavitt/file-explorer/folder-modal.d.ts +360 -0
  32. package/leavitt/file-explorer/folder-modal.js +226 -0
  33. package/leavitt/file-explorer/folder-modal.js.map +1 -0
  34. package/leavitt/file-explorer/helpers/file-types.d.ts +2 -0
  35. package/leavitt/file-explorer/helpers/file-types.js +19 -0
  36. package/leavitt/file-explorer/helpers/file-types.js.map +1 -0
  37. package/leavitt/file-explorer/helpers/format-bytes.d.ts +2 -0
  38. package/leavitt/file-explorer/helpers/format-bytes.js +8 -0
  39. package/leavitt/file-explorer/helpers/format-bytes.js.map +1 -0
  40. package/package.json +2 -2
  41. package/titanium/styles/data-row.js +7 -0
  42. package/titanium/styles/data-row.js.map +1 -1
@@ -0,0 +1,871 @@
1
+ import { __decorate } from "tslib";
2
+ import { LoadWhile } from '../../titanium/helpers/load-while';
3
+ import { css, html, LitElement, nothing } from 'lit';
4
+ import { customElement, property, query, state } from 'lit/decorators.js';
5
+ import { PendingStateEvent } from '../../titanium/types/pending-state-event';
6
+ import { ConfirmDialogOpenEvent } from '../../titanium/confirm-dialog/confirm-dialog-open-event';
7
+ import '../../titanium/confirm-dialog/confirm-dialog';
8
+ import fileExplorerEvents from './events/file-explorer-events';
9
+ import { TitaniumSnackbarSingleton } from '../../titanium/snackbar/snackbar';
10
+ import '@material/web/icon/icon';
11
+ import '@material/web/button/text-button';
12
+ import '@material/web/progress/linear-progress';
13
+ import '@material/web/menu/menu';
14
+ import '@material/web/menu/menu-item';
15
+ import './add-folder-modal';
16
+ import './file-explorer-no-files';
17
+ import './file-explorer-error';
18
+ import './file-explorer-no-permission';
19
+ import './file-modal';
20
+ import './folder-modal';
21
+ import './file-list-item';
22
+ import './folder-list-item';
23
+ import * as Throttle from 'promise-parallel-throttle';
24
+ import { join } from '../../titanium/helpers/helpers';
25
+ import { a, ellipsis, h1, h2 } from '../../titanium/styles/styles';
26
+ import { formatBytes } from './helpers/format-bytes';
27
+ /**
28
+ * Leavitt Group specific file explorer
29
+ *
30
+ * @element leavitt-file-explorer
31
+ *
32
+ * @cssprop {Color} [--leavitt-file-explorer-font-family='Roboto', 'Noto', sans-serif] - Font family
33
+ *
34
+ * @fires folder-added - Fired when a new folder is added.
35
+ * @fires folder-deleted - Fired when a folder is deleted.
36
+ * @fires file-added - Fired when a new file is added.
37
+ * @fires file-deleted - Fired when a file is deleted.
38
+ */
39
+ let LeavittFileExplorer = class LeavittFileExplorer extends LoadWhile(LitElement) {
40
+ #apiService_accessor_storage;
41
+ /**
42
+ * This is required.
43
+ */
44
+ get apiService() { return this.#apiService_accessor_storage; }
45
+ set apiService(value) { this.#apiService_accessor_storage = value; }
46
+ #fileExplorerId_accessor_storage = 0;
47
+ /**
48
+ * ID File explorer to display. This is required.
49
+ */
50
+ get fileExplorerId() { return this.#fileExplorerId_accessor_storage; }
51
+ set fileExplorerId(value) { this.#fileExplorerId_accessor_storage = value; }
52
+ #folderId_accessor_storage = null;
53
+ /**
54
+ * Optional folder to show on first load rather than showing the root
55
+ */
56
+ get folderId() { return this.#folderId_accessor_storage; }
57
+ set folderId(value) { this.#folderId_accessor_storage = value; }
58
+ #preventNavigationUp_accessor_storage = false;
59
+ /**
60
+ * Prevents a user from navigating up the folder tree past the initially provided folderId.
61
+ *
62
+ * FolderId must be set for this to operate properly.
63
+ */
64
+ get preventNavigationUp() { return this.#preventNavigationUp_accessor_storage; }
65
+ set preventNavigationUp(value) { this.#preventNavigationUp_accessor_storage = value; }
66
+ get display() {
67
+ return window.localStorage.getItem('leavitt-file-explorer-display') || 'grid';
68
+ }
69
+ set display(val) {
70
+ localStorage.setItem('leavitt-file-explorer-display', val);
71
+ }
72
+ #state_accessor_storage = 'files';
73
+ get state() { return this.#state_accessor_storage; }
74
+ set state(value) { this.#state_accessor_storage = value; }
75
+ #isAdmin_accessor_storage = false;
76
+ get isAdmin() { return this.#isAdmin_accessor_storage; }
77
+ set isAdmin(value) { this.#isAdmin_accessor_storage = value; }
78
+ #fileExplorer_accessor_storage = null;
79
+ get fileExplorer() { return this.#fileExplorer_accessor_storage; }
80
+ set fileExplorer(value) { this.#fileExplorer_accessor_storage = value; }
81
+ #files_accessor_storage = [];
82
+ get files() { return this.#files_accessor_storage; }
83
+ set files(value) { this.#files_accessor_storage = value; }
84
+ #folders_accessor_storage = [];
85
+ get folders() { return this.#folders_accessor_storage; }
86
+ set folders(value) { this.#folders_accessor_storage = value; }
87
+ #path_accessor_storage = [];
88
+ get path() { return this.#path_accessor_storage; }
89
+ set path(value) { this.#path_accessor_storage = value; }
90
+ #selected_accessor_storage = [];
91
+ get selected() { return this.#selected_accessor_storage; }
92
+ set selected(value) { this.#selected_accessor_storage = value; }
93
+ #uploadMenu_accessor_storage;
94
+ get uploadMenu() { return this.#uploadMenu_accessor_storage; }
95
+ set uploadMenu(value) { this.#uploadMenu_accessor_storage = value; }
96
+ #folderDialog_accessor_storage;
97
+ get folderDialog() { return this.#folderDialog_accessor_storage; }
98
+ set folderDialog(value) { this.#folderDialog_accessor_storage = value; }
99
+ #addFolderDialog_accessor_storage;
100
+ get addFolderDialog() { return this.#addFolderDialog_accessor_storage; }
101
+ set addFolderDialog(value) { this.#addFolderDialog_accessor_storage = value; }
102
+ #fileDialog_accessor_storage;
103
+ get fileDialog() { return this.#fileDialog_accessor_storage; }
104
+ set fileDialog(value) { this.#fileDialog_accessor_storage = value; }
105
+ #fileInput_accessor_storage;
106
+ get fileInput() { return this.#fileInput_accessor_storage; }
107
+ set fileInput(value) { this.#fileInput_accessor_storage = value; }
108
+ #folderInput_accessor_storage;
109
+ get folderInput() { return this.#folderInput_accessor_storage; }
110
+ set folderInput(value) { this.#folderInput_accessor_storage = value; }
111
+ #confirmDialog_accessor_storage;
112
+ get confirmDialog() { return this.#confirmDialog_accessor_storage; }
113
+ set confirmDialog(value) { this.#confirmDialog_accessor_storage = value; }
114
+ #originalFolderId = 0;
115
+ firstUpdated() {
116
+ //force attribute to reflect
117
+ this.display = this.display;
118
+ this.addEventListener(ConfirmDialogOpenEvent.eventType, async (e) => {
119
+ e.stopPropagation();
120
+ this.confirmDialog.handleEvent(e);
121
+ });
122
+ this.addEventListener(PendingStateEvent.eventType, async (e) => {
123
+ e.stopPropagation();
124
+ this.loadWhile(e.detail.promise);
125
+ });
126
+ fileExplorerEvents.subscribe('FileExplorerFileDto', 'Update', (o) => {
127
+ const index = this.files.findIndex((file) => file.Id === o.Id);
128
+ if (index > -1) {
129
+ this.files[index] = { ...this.files[index], ...o };
130
+ this.requestUpdate('files');
131
+ }
132
+ });
133
+ fileExplorerEvents.subscribe('FileExplorerFolder', 'Update', (o) => {
134
+ const index = this.folders.findIndex((folder) => folder.Id === o.Id);
135
+ if (index > -1) {
136
+ this.folders[index] = { ...this.folders[index], ...o };
137
+ this.requestUpdate('folders');
138
+ }
139
+ });
140
+ }
141
+ async updated(changedProps) {
142
+ if ((this.fileExplorerId > 0 && changedProps.has('fileExplorerId')) || changedProps.has('folderId')) {
143
+ if (this.folderId && this.#originalFolderId === 0) {
144
+ this.#originalFolderId = this.folderId;
145
+ }
146
+ this.isAdmin = false;
147
+ await this.reload();
148
+ }
149
+ }
150
+ #isFolder(fileOrFolder) {
151
+ return fileOrFolder?.type === 'folder';
152
+ }
153
+ /**
154
+ * Refetches current file explorer data and re-renders
155
+ */
156
+ async reload() {
157
+ await this.#getExplorerData(this.fileExplorerId, this.folderId);
158
+ }
159
+ async #getExplorerData(fileExplorerId, folderId) {
160
+ try {
161
+ const get = this.apiService?.getAsync(`FileExplorers(${fileExplorerId})/FileExplorerView(folderId=${folderId})`);
162
+ if (get) {
163
+ this.loadWhile(get);
164
+ }
165
+ const result = await get;
166
+ if (result?.status == 200 && result.entity) {
167
+ this.fileExplorer = result.entity;
168
+ this.folders = result.entity.Folders;
169
+ this.files = result.entity.Files;
170
+ const path = result.entity.Path;
171
+ if (this.preventNavigationUp && this.#originalFolderId > 0) {
172
+ const shortenedPath = [];
173
+ for (let index = path.length - 1; index > 0; index--) {
174
+ const p = path[index];
175
+ shortenedPath.unshift(p);
176
+ if (p.FolderId === this.#originalFolderId) {
177
+ break;
178
+ }
179
+ }
180
+ this.path = shortenedPath;
181
+ }
182
+ else {
183
+ this.path = path;
184
+ }
185
+ this.isAdmin = result.entity.CanEdit;
186
+ this.state = this.folders.length > 0 || this.files.length > 0 ? 'files' : 'no-files';
187
+ }
188
+ }
189
+ catch (error) {
190
+ if (error?.statusCode == 401 || error?.statusCode == 404) {
191
+ this.path = [{ Name: 'Files' }];
192
+ this.state = 'no-permission';
193
+ return;
194
+ }
195
+ this.path = [{ Name: 'Files' }];
196
+ this.state = 'error';
197
+ }
198
+ }
199
+ async #addFolderClick() {
200
+ const newFolder = await this.addFolderDialog.open();
201
+ if (newFolder) {
202
+ this.folders = [...this.folders, newFolder];
203
+ if (this.fileExplorer) {
204
+ this.fileExplorer.FoldersCount = this.fileExplorer?.FoldersCount + 1;
205
+ this.requestUpdate('fileExplorer');
206
+ }
207
+ this.state = 'files';
208
+ this.dispatchEvent(new CustomEvent('folder-added', { detail: newFolder }));
209
+ }
210
+ }
211
+ /**
212
+ * @internal
213
+ */
214
+ async #deleteSelectedClick() {
215
+ const confirmationDialogEvent = new ConfirmDialogOpenEvent('Please confirm delete', `Deleting folders will delete all of their contents. Are you sure you would like to delete the selected item${this.selected.length === 1 ? '' : 's'}?`);
216
+ this.dispatchEvent(confirmationDialogEvent);
217
+ if (await confirmationDialogEvent.dialogResult) {
218
+ const items = [...this.selected];
219
+ const errorMessageToCount = new Map();
220
+ let totalErrorCount = 0;
221
+ const requests = Promise.all(items.map(async (o) => {
222
+ try {
223
+ if (this.#isFolder(o)) {
224
+ await this.apiService?.deleteAsync(`FileExplorerFolders(${o.Id})`);
225
+ this.folders.splice(this.folders.findIndex((folder) => folder.Id === o.Id), 1);
226
+ this.dispatchEvent(new CustomEvent('folder-deleted'));
227
+ if (this.fileExplorer) {
228
+ this.fileExplorer.FoldersCount = this.fileExplorer?.FoldersCount - 1;
229
+ this.requestUpdate('fileExplorer');
230
+ }
231
+ this.requestUpdate('folders');
232
+ }
233
+ else {
234
+ await this.apiService?.deleteAsync(`FileExplorerAttachments(${o.Id})`);
235
+ this.files.splice(this.files.findIndex((file) => file.Id === o.Id), 1);
236
+ this.requestUpdate('files');
237
+ if (this.fileExplorer) {
238
+ this.fileExplorer.FilesCount = this.fileExplorer?.FilesCount - 1;
239
+ this.fileExplorer.Size = this.fileExplorer?.Size - o.Size;
240
+ this.requestUpdate('fileExplorer');
241
+ }
242
+ this.dispatchEvent(new CustomEvent('file-deleted'));
243
+ }
244
+ }
245
+ catch (newError) {
246
+ const newErrorCount = (errorMessageToCount.get(newError) ?? 0) + 1;
247
+ errorMessageToCount.set(newError, newErrorCount);
248
+ totalErrorCount++;
249
+ }
250
+ }));
251
+ this.loadWhile(requests);
252
+ await requests;
253
+ this.selected = [];
254
+ this.state = this.folders.length > 0 || this.files.length > 0 ? 'files' : 'no-files';
255
+ await this.reload();
256
+ if (totalErrorCount > 0) {
257
+ TitaniumSnackbarSingleton.open(html `Failed to delete ${totalErrorCount === 1 ? 'files and folders' : `${totalErrorCount} files and folders: <br />`}.
258
+ ${errorMessageToCount.size === 1
259
+ ? Array.from(errorMessageToCount.keys())[0]
260
+ : Array.from(errorMessageToCount.entries()).map(([error, count]) => `(${count}) ${error} <br />`)}`);
261
+ }
262
+ }
263
+ }
264
+ #getFolderPath(file) {
265
+ return file.webkitRelativePath.replace('/' + file.name, '');
266
+ }
267
+ async #createDirectoryStructure(files) {
268
+ const pathToFolderId = new Map();
269
+ const filesArr = Array.from(files ?? []);
270
+ for (let index = 0; index < filesArr.length; index++) {
271
+ const file = filesArr[index];
272
+ const parentFolderPath = this.#getFolderPath(file).split('/');
273
+ const parentFolders = [];
274
+ let parentId = this.folderId;
275
+ for (let index = 0; index < parentFolderPath.length; index++) {
276
+ const folderName = parentFolderPath[index];
277
+ const newPath = [...parentFolders, folderName].join('/');
278
+ if (pathToFolderId.has(newPath)) {
279
+ parentFolders.push(folderName);
280
+ parentId = pathToFolderId.get(newPath) ?? 0;
281
+ continue;
282
+ }
283
+ const folder = await this.#createFolder(folderName, parentId || null);
284
+ parentId = folder?.Id ?? 0;
285
+ parentFolders.push(folderName);
286
+ pathToFolderId.set(parentFolders.join('/'), folder?.Id ?? 0);
287
+ if ((folder?.ParentFolderId && folder?.ParentFolderId === this.folderId) || (!folder?.ParentFolderId && !this.folderId)) {
288
+ const folderDto = {
289
+ ...folder,
290
+ CreatorLastName: folder?.CreatorPerson?.LastName,
291
+ CreatorFirstName: folder?.CreatorPerson?.FirstName,
292
+ };
293
+ this.folders = [...this.folders, folderDto];
294
+ this.state = 'files';
295
+ this.dispatchEvent(new CustomEvent('folder-added', { detail: folderDto }));
296
+ }
297
+ }
298
+ }
299
+ return pathToFolderId;
300
+ }
301
+ async #uploadFiles(files) {
302
+ const uri = this.folderId
303
+ ? `FileExplorerFolders(${this.folderId})/UploadAttachment?expand=Creator(select=FullName,ProfilePictureCdnFileName)`
304
+ : `FileExplorers(${this.fileExplorerId})/UploadAttachment?expand=Creator(select=FullName,ProfilePictureCdnFileName)`;
305
+ const failedFiles = [];
306
+ const requests = Array.from(files ?? []).map((file) => async () => {
307
+ try {
308
+ const result = (await this.apiService?.uploadFile(uri, file, () => console.log))?.entity;
309
+ if (result) {
310
+ const attachment = {
311
+ ...result,
312
+ CreatorProfilePictureCndFileName: result.Creator?.ProfilePictureCdnFileName ?? '',
313
+ CreatorFullName: result.Creator?.FullName ?? '',
314
+ CreatorFirstName: '',
315
+ CreatorLastName: '',
316
+ };
317
+ this.files = [...this.files, attachment];
318
+ this.state = 'files';
319
+ this.dispatchEvent(new CustomEvent('file-added'));
320
+ if (this.fileExplorer) {
321
+ this.fileExplorer.FilesCount = this.fileExplorer?.FilesCount + 1;
322
+ this.fileExplorer.Size = this.fileExplorer?.Size + result.Size;
323
+ this.requestUpdate('fileExplorer');
324
+ }
325
+ }
326
+ }
327
+ catch (error) {
328
+ failedFiles.push(file.name + ': ' + error);
329
+ }
330
+ });
331
+ const uploadAll = Throttle.all(requests, { maxInProgress: 4 });
332
+ this.loadWhile(uploadAll);
333
+ await uploadAll;
334
+ if (failedFiles.length > 0) {
335
+ TitaniumSnackbarSingleton.open(html `Failed to upload ${failedFiles.length} file${failedFiles.length === 1 ? '' : 's'}: <br />
336
+ ${join(failedFiles, html `<br />`)}`);
337
+ console.warn(`Failed to upload ${failedFiles.length} file${failedFiles.length === 1 ? '' : 's'}: \r\n${failedFiles.join('\r\n')}`);
338
+ }
339
+ this.fileInput.value = '';
340
+ }
341
+ async #uploadFolders(files) {
342
+ const directoryToIdMap = this.#createDirectoryStructure(files);
343
+ const failedFiles = [];
344
+ const requests = Array.from(files ?? []).map((file) => async () => {
345
+ try {
346
+ const path = this.#getFolderPath(file);
347
+ const folderId = (await directoryToIdMap).get(path);
348
+ const uri = folderId
349
+ ? `FileExplorerFolders(${folderId})/UploadAttachment?expand=Creator(select=FullName,ProfilePictureCdnFileName)`
350
+ : `FileExplorers(${this.fileExplorerId})/UploadAttachment?expand=Creator(select=FullName,ProfilePictureCdnFileName)`;
351
+ const result = (await this.apiService?.uploadFile(uri, file, () => console.log))?.entity;
352
+ if (result) {
353
+ this.dispatchEvent(new CustomEvent('file-added'));
354
+ if (this.fileExplorer) {
355
+ this.fileExplorer.FilesCount = this.fileExplorer?.FilesCount + 1;
356
+ this.fileExplorer.Size = this.fileExplorer?.Size + result.Size;
357
+ this.requestUpdate('fileExplorer');
358
+ }
359
+ }
360
+ }
361
+ catch (error) {
362
+ failedFiles.push(file.webkitRelativePath + ': ' + error);
363
+ }
364
+ });
365
+ const uploadAll = Throttle.all(requests, { maxInProgress: 4 });
366
+ this.loadWhile(uploadAll);
367
+ await uploadAll;
368
+ if (failedFiles.length > 0) {
369
+ TitaniumSnackbarSingleton.open(html `Failed to upload ${failedFiles.length} file${failedFiles.length === 1 ? '' : 's'}: <br />
370
+ ${join(failedFiles, html `<br />`)}`);
371
+ console.warn(`Failed to upload ${failedFiles.length} file${failedFiles.length === 1 ? '' : 's'}: \r\n${failedFiles.join('\r\n')}`);
372
+ }
373
+ await this.reload();
374
+ this.folderInput.value = '';
375
+ }
376
+ async #createFolder(name, parentFolderId) {
377
+ const dto = {
378
+ FileExplorerId: this.fileExplorerId,
379
+ Name: name,
380
+ ParentFolderId: parentFolderId || undefined,
381
+ };
382
+ try {
383
+ const post = this.apiService?.postAsync('FileExplorerFolders?expand=CreatorPerson(select=FirstName,LastName)', dto);
384
+ if (post) {
385
+ /**
386
+ * @ignore
387
+ */
388
+ this.dispatchEvent(new PendingStateEvent(post));
389
+ }
390
+ const result = (await post)?.entity;
391
+ if (this.fileExplorer) {
392
+ this.fileExplorer.FoldersCount = this.fileExplorer?.FoldersCount + 1;
393
+ this.requestUpdate('fileExplorer');
394
+ }
395
+ return result;
396
+ }
397
+ catch (error) {
398
+ TitaniumSnackbarSingleton.open(error);
399
+ }
400
+ return null;
401
+ }
402
+ #toggleSelected(item, type) {
403
+ const selected = this.selected.find((s) => s?.Id === item.Id && s.type === type);
404
+ if (selected) {
405
+ this.selected = [...this.selected.filter((o) => o !== selected)];
406
+ }
407
+ else {
408
+ this.selected = [...this.selected, { ...item, type: type }];
409
+ }
410
+ }
411
+ static { this.styles = [
412
+ h1,
413
+ h2,
414
+ ellipsis,
415
+ a,
416
+ css `
417
+ :host {
418
+ display: grid;
419
+ grid:
420
+ 'header' 69px
421
+ 'main' minmax(150px, 1fr)
422
+ 'footer' auto;
423
+
424
+ border: 1px solid var(--md-sys-color-outline-variant);
425
+ background-color: var(--md-sys-color-surface);
426
+ color: var(--md-sys-color-on-surface);
427
+ border-radius: 8px;
428
+
429
+ font-family: var(--leavitt-file-explorer-font-family, 'Roboto', 'Noto', sans-serif);
430
+ -webkit-font-smoothing: antialiased;
431
+ width: 100%;
432
+ }
433
+
434
+ header {
435
+ display: grid;
436
+ grid: 'nav actions' / 4fr auto;
437
+ gap: 0px 12px;
438
+ position: relative;
439
+
440
+ margin: 0;
441
+ padding: 0 12px 0 12px;
442
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
443
+ }
444
+
445
+ header aside {
446
+ grid-area: nav;
447
+ }
448
+
449
+ header nav {
450
+ font-family: Metropolis;
451
+ font-size: 18px;
452
+ letter-spacing: -0.264px;
453
+ font-weight: 400;
454
+ flex-direction: row;
455
+ align-items: center;
456
+ gap: 2px;
457
+ display: flex;
458
+ margin: 12px 0 0 12px;
459
+ }
460
+
461
+ header md-icon-button {
462
+ justify-self: center;
463
+ align-self: center;
464
+ }
465
+
466
+ main {
467
+ position: relative;
468
+ overflow-y: auto;
469
+ }
470
+
471
+ content-veil {
472
+ display: none;
473
+ position: absolute;
474
+ top: 0;
475
+ left: 0;
476
+ width: 100%;
477
+ height: 100%;
478
+ background-color: var(--md-sys-color-scrim);
479
+ opacity: 0;
480
+ -webkit-transition: opacity 75ms linear;
481
+ -o-transition: opacity 75ms linear;
482
+ transition: opacity 75ms linear;
483
+ z-index: 6;
484
+ backdrop-filter: blur(6px);
485
+ }
486
+
487
+ content-veil[opened] {
488
+ opacity: 0.32;
489
+ display: block;
490
+ }
491
+
492
+ main > section {
493
+ display: flex;
494
+ flex-direction: column;
495
+ }
496
+
497
+ :host([display='grid']) section:last-of-type {
498
+ margin-bottom: 12px;
499
+ }
500
+
501
+ main > h3 {
502
+ display: none;
503
+ }
504
+
505
+ :host([display='grid']) main > h3 {
506
+ display: block;
507
+ -moz-osx-font-smoothing: grayscale;
508
+ -webkit-font-smoothing: antialiased;
509
+ letter-spacing: -0.264px;
510
+ font-weight: 400;
511
+ font-size: 14px;
512
+ line-height: 18px;
513
+ margin: 24px 12px 0 24px;
514
+ }
515
+
516
+ :host([display='grid']) main > section {
517
+ display: grid;
518
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
519
+ grid-gap: 12px;
520
+ padding: 12px 24px;
521
+ }
522
+
523
+ md-linear-progress {
524
+ position: absolute;
525
+ right: 0;
526
+ left: 0;
527
+ bottom: 0;
528
+ width: unset;
529
+ }
530
+
531
+ file-summary {
532
+ grid-area: summary;
533
+ display: block;
534
+ padding-left: 12px !important;
535
+ font-size: 12px;
536
+ }
537
+
538
+ :host([prevent-navigation-up]) file-summary {
539
+ display: none;
540
+ }
541
+
542
+ header nav md-icon {
543
+ color: var(--md-sys-color-surface-variant);
544
+ flex-shrink: 0;
545
+ }
546
+
547
+ selected-actions {
548
+ display: grid;
549
+ gap: 6px;
550
+ grid: 'deselect selected-text buttons' / auto 1fr auto;
551
+ background-color: var(--md-sys-color-secondary-container);
552
+ position: absolute;
553
+ top: 0px;
554
+ left: 0px;
555
+ right: 0px;
556
+ bottom: 0px;
557
+ margin: 0 !important;
558
+ align-content: center;
559
+ align-items: center;
560
+ padding: 12px;
561
+ z-index: 1;
562
+ }
563
+
564
+ selected-actions h2 {
565
+ color: var(--md-sys-color-on-secondary-container);
566
+ font-size: 18px;
567
+ font-weight: 400;
568
+ }
569
+
570
+ selected-actions div[buttons] {
571
+ display: flex;
572
+ align-items: center;
573
+ gap: 0 8px;
574
+ justify-content: flex-end;
575
+ }
576
+
577
+ nav a:visited,
578
+ nav span,
579
+ nav a {
580
+ display: block;
581
+ font-size: 18px;
582
+ min-width: 20px;
583
+ }
584
+
585
+ a[disabled] {
586
+ pointer-events: none;
587
+ cursor: default;
588
+ }
589
+
590
+ nav a span {
591
+ padding: 0;
592
+ }
593
+
594
+ header span {
595
+ grid-area: text;
596
+ position: relative;
597
+ }
598
+
599
+ /*File item styles */
600
+
601
+ footer {
602
+ display: grid;
603
+ grid: 'count . actions' / auto 1fr auto;
604
+ gap: 12px;
605
+ align-items: center;
606
+ padding: 6px 8px 6px 24px;
607
+ border-top: 1px solid var(--md-sys-color-outline-variant);
608
+ }
609
+
610
+ footer span[counts] {
611
+ grid-area: count;
612
+ font-size: 12px;
613
+ font-weight: 400;
614
+ letter-spacing: 0.011em;
615
+ line-height: 20px;
616
+
617
+ user-select: none;
618
+ -webkit-user-select: none;
619
+ -moz-user-select: none;
620
+ }
621
+
622
+ footer-actions {
623
+ grid-area: actions;
624
+ display: flex;
625
+ flex-direction: row;
626
+ flex-wrap: wrap;
627
+ }
628
+
629
+ md-menu[upload-menu] md-menu-item {
630
+ min-width: 200px;
631
+ }
632
+
633
+ :host([display='grid']) footer span[counts] {
634
+ display: none;
635
+ }
636
+
637
+ [hidden] {
638
+ display: none !important;
639
+ }
640
+ `,
641
+ ]; }
642
+ render() {
643
+ return html `
644
+ <header>
645
+ <aside ellipsis>
646
+ <nav ellipsis>
647
+ ${this.fileExplorer
648
+ ? this.path.map((o, i) => html `
649
+ ${i == this.path.length - 1
650
+ ? html ` <span ellipsis end title=${o?.Name ?? ''}> ${o.Name} </span> `
651
+ : html `
652
+ <a
653
+ ?disabled=${this.isLoading}
654
+ ellipsis
655
+ title=${o.Name ?? ''}
656
+ href="#"
657
+ @click=${(e) => {
658
+ e.preventDefault();
659
+ this.folderId = o.FolderId ?? null;
660
+ this.selected = [];
661
+ }}
662
+ >
663
+ ${o.Name}</a
664
+ >
665
+ <md-icon>navigate_next</md-icon>
666
+ `}
667
+ `)
668
+ : html `<span ellipsis end> File explorer</span>`}
669
+ </nav>
670
+ <file-summary ?hidden=${!this.fileExplorer} ellipsis heading3
671
+ >${this.fileExplorer?.FilesCount} files | ${this.fileExplorer?.FoldersCount} folders | ${formatBytes(this.fileExplorer?.Size)}</file-summary
672
+ >
673
+ </aside>
674
+ <md-icon-button
675
+ ?disabled=${this.isLoading}
676
+ view-style
677
+ @click=${() => (this.display = this.display === 'grid' ? 'list' : 'grid')}
678
+ title=${this.display === 'grid' ? 'Show list view' : 'Show grid view'}
679
+ >
680
+ <md-icon>${this.display === 'grid' ? 'view_list' : 'view_module'}</md-icon>
681
+ </md-icon-button>
682
+ <selected-actions ?hidden=${this.selected.length === 0}>
683
+ <md-icon-button title="Clear selected" @click=${() => (this.selected = [])}> <md-icon>clear</md-icon></md-icon-button>
684
+ <h2 ellipsis>${this.selected.length} selected</h2>
685
+ <div buttons part="selected-actions-container">
686
+ ${this.isAdmin
687
+ ? html ` <md-icon-button title="Delete selected" @click=${this.#deleteSelectedClick}> <md-icon>delete</md-icon></md-icon-button> `
688
+ : nothing}
689
+ <md-icon-button
690
+ primary
691
+ ?hidden=${!this.selected.length}
692
+ ?disabled=${this.selected.length !== 1}
693
+ @click=${() => {
694
+ if (!this.selected) {
695
+ return;
696
+ }
697
+ if (this.#isFolder(this.selected[0])) {
698
+ this.folderDialog.open(this.selected[0]);
699
+ }
700
+ else {
701
+ this.fileDialog.open(this.selected[0]);
702
+ }
703
+ }}
704
+ >
705
+ <md-icon>info</md-icon>
706
+ </md-icon-button>
707
+ </div>
708
+ </selected-actions>
709
+
710
+ <md-linear-progress ?hidden=${!this.isLoading} indeterminate></md-linear-progress>
711
+ </header>
712
+ <main>
713
+ <leavitt-file-explorer-no-files ?hidden=${this.state !== 'no-files'}> </leavitt-file-explorer-no-files>
714
+ <leavitt-file-explorer-no-permission ?hidden=${this.state !== 'no-permission'}> </leavitt-file-explorer-no-permission>
715
+ <leavitt-file-explorer-error ?hidden=${this.state !== 'error'}> </leavitt-file-explorer-error>
716
+
717
+ <h3 ?hidden=${this.folders.length === 0 || this.state != 'files'}>Folders (${this.folders.length})</h3>
718
+ <section ?hidden=${this.folders.length === 0 || this.state != 'files'}>
719
+ ${this.folders.map((folder) => html `
720
+ <folder-list-item
721
+ .folder=${folder}
722
+ ?selected=${this.selected.some((s) => s?.Id === folder.Id && s.type === 'folder')}
723
+ .selectedCount=${this.selected.length}
724
+ .display=${this.display}
725
+ @show-details=${() => this.folderDialog.open(folder)}
726
+ @toggle-selected=${() => this.#toggleSelected(folder, 'folder')}
727
+ @navigate=${() => (this.folderId = folder.Id ?? null)}
728
+ ></folder-list-item>
729
+ `)}
730
+ </section>
731
+ <h3 ?hidden=${this.files.length === 0 || this.state != 'files'}>Files (${this.files.length})</h3>
732
+ <section ?hidden=${this.files.length === 0 || this.state != 'files'}>
733
+ ${this.files.map((file) => html `
734
+ <file-list-item
735
+ .file=${file}
736
+ ?selected=${this.selected.some((s) => s?.Id === file.Id && s.type === 'file')}
737
+ .selectedCount=${this.selected.length}
738
+ .display=${this.display}
739
+ @show-details=${() => this.fileDialog.open(file)}
740
+ @toggle-selected=${() => this.#toggleSelected(file, 'file')}
741
+ ></file-list-item>
742
+ `)}
743
+ </section>
744
+ <content-veil ?opened=${this.isLoading}></content-veil>
745
+ </main>
746
+ <footer>
747
+ <span counts> ${this.files.length} files | ${this.folders.length} folders </span>
748
+
749
+ ${this.isAdmin
750
+ ? html `
751
+ <footer-actions>
752
+ <md-text-button ?disabled=${this.isLoading} @click=${this.#addFolderClick}>
753
+ <md-icon slot="icon">create_new_folder</md-icon>
754
+ <span> Add folder</span>
755
+ </md-text-button>
756
+ <div style="position: relative;">
757
+ <md-text-button
758
+ id="upload-button"
759
+ ?disabled=${this.isLoading}
760
+ @click=${() => {
761
+ this.uploadMenu.open = !this.uploadMenu.open;
762
+ }}
763
+ >
764
+ <md-icon slot="icon">backup</md-icon>
765
+ Upload
766
+ </md-text-button>
767
+ <md-menu
768
+ upload-menu
769
+ anchor="upload-button"
770
+ @close-menu=${(e) => {
771
+ e.detail.itemPath?.[0]?.action?.();
772
+ }}
773
+ >
774
+ <md-menu-item .action=${() => this.fileInput.click()}>
775
+ <span slot="headline">Upload files</span>
776
+ <md-icon slot="start">upload_file</md-icon>
777
+ </md-menu-item>
778
+ <md-menu-item .action=${() => this.folderInput.click()}>
779
+ <span slot="headline">Upload folders</span>
780
+ <md-icon slot="start">perm_media</md-icon>
781
+ </md-menu-item>
782
+ </md-menu>
783
+ </div>
784
+ <input
785
+ folders
786
+ @change=${async () => this.#uploadFolders(this.folderInput.files)}
787
+ type="file"
788
+ webkitdirectory
789
+ directory
790
+ multiple
791
+ id="file"
792
+ style="display:none;"
793
+ />
794
+ <input files @change=${async () => this.#uploadFiles(this.fileInput.files)} type="file" multiple id="file" style="display:none;" />
795
+ </footer-actions>
796
+ `
797
+ : nothing}
798
+ </footer>
799
+ <leavitt-add-folder-modal
800
+ .apiService=${this.apiService}
801
+ .fileExplorerId=${this.fileExplorerId}
802
+ .parentFolderId=${this?.folderId ?? 0}
803
+ ></leavitt-add-folder-modal>
804
+ <leavitt-folder-modal .apiService=${this.apiService} .enableEditing=${this.isAdmin}></leavitt-folder-modal>
805
+ <leavitt-file-modal .apiService=${this.apiService} .enableEditing=${this.isAdmin}></leavitt-file-modal>
806
+ <titanium-confirm-dialog></titanium-confirm-dialog>
807
+ `;
808
+ }
809
+ };
810
+ __decorate([
811
+ property({ attribute: false })
812
+ ], LeavittFileExplorer.prototype, "apiService", null);
813
+ __decorate([
814
+ property({ type: Number, attribute: 'file-explorer-id' })
815
+ ], LeavittFileExplorer.prototype, "fileExplorerId", null);
816
+ __decorate([
817
+ property({ type: Number, attribute: 'folder-id' })
818
+ ], LeavittFileExplorer.prototype, "folderId", null);
819
+ __decorate([
820
+ property({ type: Boolean, reflect: true, attribute: 'prevent-navigation-up' })
821
+ ], LeavittFileExplorer.prototype, "preventNavigationUp", null);
822
+ __decorate([
823
+ property({ type: String, reflect: true, attribute: 'display' })
824
+ ], LeavittFileExplorer.prototype, "display", null);
825
+ __decorate([
826
+ property({ type: String })
827
+ ], LeavittFileExplorer.prototype, "state", null);
828
+ __decorate([
829
+ state()
830
+ ], LeavittFileExplorer.prototype, "isAdmin", null);
831
+ __decorate([
832
+ state()
833
+ ], LeavittFileExplorer.prototype, "fileExplorer", null);
834
+ __decorate([
835
+ state()
836
+ ], LeavittFileExplorer.prototype, "files", null);
837
+ __decorate([
838
+ state()
839
+ ], LeavittFileExplorer.prototype, "folders", null);
840
+ __decorate([
841
+ state()
842
+ ], LeavittFileExplorer.prototype, "path", null);
843
+ __decorate([
844
+ state()
845
+ ], LeavittFileExplorer.prototype, "selected", null);
846
+ __decorate([
847
+ query('md-menu[upload-menu]')
848
+ ], LeavittFileExplorer.prototype, "uploadMenu", null);
849
+ __decorate([
850
+ query('leavitt-folder-modal')
851
+ ], LeavittFileExplorer.prototype, "folderDialog", null);
852
+ __decorate([
853
+ query('leavitt-add-folder-modal')
854
+ ], LeavittFileExplorer.prototype, "addFolderDialog", null);
855
+ __decorate([
856
+ query('leavitt-file-modal')
857
+ ], LeavittFileExplorer.prototype, "fileDialog", null);
858
+ __decorate([
859
+ query('input[files]')
860
+ ], LeavittFileExplorer.prototype, "fileInput", null);
861
+ __decorate([
862
+ query('input[folders]')
863
+ ], LeavittFileExplorer.prototype, "folderInput", null);
864
+ __decorate([
865
+ query('titanium-confirm-dialog')
866
+ ], LeavittFileExplorer.prototype, "confirmDialog", null);
867
+ LeavittFileExplorer = __decorate([
868
+ customElement('leavitt-file-explorer')
869
+ ], LeavittFileExplorer);
870
+ export { LeavittFileExplorer };
871
+ //# sourceMappingURL=file-explorer.js.map