@elderbyte/ngx-starter 18.2.0-beta4 → 18.3.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.
- package/esm2022/lib/components/files/elder-file-select-input.mjs +119 -0
- package/esm2022/lib/components/files/elder-file-select.directive.mjs +85 -83
- package/esm2022/lib/components/files/file-system-api.mjs +96 -0
- package/esm2022/lib/components/files/listing/file-entry.mjs +31 -15
- package/esm2022/lib/components/files/listing/file-listing-rx.mjs +30 -70
- package/esm2022/lib/components/files/listing/file-listing-system-handle.mjs +35 -0
- package/esm2022/lib/components/files/listing/file-listing-webkit.mjs +77 -0
- package/fesm2022/elderbyte-ngx-starter.mjs +388 -107
- package/fesm2022/elderbyte-ngx-starter.mjs.map +1 -1
- package/lib/components/files/elder-file-select-input.d.ts +59 -0
- package/lib/components/files/elder-file-select.directive.d.ts +16 -23
- package/lib/components/files/file-system-api.d.ts +86 -0
- package/lib/components/files/listing/file-entry.d.ts +11 -1
- package/lib/components/files/listing/file-listing-rx.d.ts +3 -6
- package/lib/components/files/listing/file-listing-system-handle.d.ts +21 -0
- package/lib/components/files/listing/file-listing-webkit.d.ts +26 -0
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import { Pipe, LOCALE_ID, Inject, NgModule, Optional, SkipSelf, Directive, Outpu
|
|
|
5
5
|
import * as i1 from '@angular/platform-browser';
|
|
6
6
|
import { Duration, Period, TemporalQueries, LocalTime, Instant, LocalDate, nativeJs, ZoneId, DateTimeFormatter, convert, ZonedDateTime, Temporal } from '@js-joda/core';
|
|
7
7
|
import { LoggerFactory } from '@elderbyte/ts-logger';
|
|
8
|
-
import { timer, defer, ReplaySubject, concat, finalize, exhaustMap, BehaviorSubject, Subject, switchMap, of, combineLatest, EMPTY, merge, throwError, forkJoin, mergeWith, Observable, zip, mergeMap as mergeMap$1, fromEvent, skipUntil, combineLatestWith as combineLatestWith$1, NEVER } from 'rxjs';
|
|
8
|
+
import { timer, defer, ReplaySubject, concat, finalize, exhaustMap, BehaviorSubject, Subject, switchMap, of, combineLatest, EMPTY, merge, throwError, forkJoin, mergeWith, Observable, from, toArray, zip, mergeMap as mergeMap$1, fromEvent, skipUntil, combineLatestWith as combineLatestWith$1, NEVER } from 'rxjs';
|
|
9
9
|
import { tap, takeUntil, takeWhile, map, filter, distinctUntilChanged, debounceTime, catchError, first, take, switchMap as switchMap$1, mergeMap, expand, reduce, startWith, skip, delay, share, combineLatestWith, skipWhile, timeout } from 'rxjs/operators';
|
|
10
10
|
import * as i1$2 from '@angular/common/http';
|
|
11
11
|
import { HttpParams, HttpEventType, HttpRequest, HttpClient, HttpErrorResponse, HTTP_INTERCEPTORS } from '@angular/common/http';
|
|
@@ -36,6 +36,7 @@ import { MatAutocompleteTrigger, MatAutocomplete, MatAutocompleteModule } from '
|
|
|
36
36
|
import * as i1$7 from '@angular/material/chips';
|
|
37
37
|
import { MatChipGrid, MatChipRow, MatChipRemove, MatChipInput, MatChipsModule, MatChip, MatChipAvatar, MatChipSet, MatChipTrailingIcon, MatChipListbox, MatChipOption } from '@angular/material/chips';
|
|
38
38
|
import { MatInput, MatInputModule } from '@angular/material/input';
|
|
39
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
39
40
|
import { MatList, MatListSubheaderCssMatStyler, MatListItem, MatListModule } from '@angular/material/list';
|
|
40
41
|
import { MatProgressBar, MatProgressBarModule } from '@angular/material/progress-bar';
|
|
41
42
|
import * as i1$5 from '@angular/material/snack-bar';
|
|
@@ -50,7 +51,6 @@ import * as i1$6 from '@angular/material/dialog';
|
|
|
50
51
|
import { MatDialogClose, MAT_DIALOG_DATA, MatDialogConfig, MatDialogModule } from '@angular/material/dialog';
|
|
51
52
|
import { MatMenuTrigger, MatMenu, MatMenuItem, MatMenuModule } from '@angular/material/menu';
|
|
52
53
|
import { MatTooltipModule, MatTooltip } from '@angular/material/tooltip';
|
|
53
|
-
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
54
54
|
import { MatPaginator, MatPaginatorIntl, MatPaginatorModule } from '@angular/material/paginator';
|
|
55
55
|
import { MatColumnDef, MatRowDef, MatTable, MatHeaderCellDef, MatHeaderCell, MatCellDef, MatCell, MatFooterCellDef, MatFooterCell, MatHeaderRowDef, MatHeaderRow, MatRow, MatFooterRowDef, MatFooterRow, MatTableModule } from '@angular/material/table';
|
|
56
56
|
import * as i1$8 from '@angular/cdk/table';
|
|
@@ -9715,6 +9715,100 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImpor
|
|
|
9715
9715
|
}]
|
|
9716
9716
|
}] });
|
|
9717
9717
|
|
|
9718
|
+
/***************************************************************************
|
|
9719
|
+
* *
|
|
9720
|
+
* FileSystem API *
|
|
9721
|
+
* *
|
|
9722
|
+
**************************************************************************/
|
|
9723
|
+
/**
|
|
9724
|
+
* Holds experimental features of the browser `File System API`. No typescript lint checks
|
|
9725
|
+
* are performed here!
|
|
9726
|
+
*/
|
|
9727
|
+
class FileSystemApi {
|
|
9728
|
+
/***************************************************************************
|
|
9729
|
+
* *
|
|
9730
|
+
* DataTransferItem *
|
|
9731
|
+
* *
|
|
9732
|
+
**************************************************************************/
|
|
9733
|
+
static isGetAsFileSystemHandleSupported(transferItem) {
|
|
9734
|
+
// @ts-ignore
|
|
9735
|
+
return transferItem.getAsFileSystemHandle !== undefined;
|
|
9736
|
+
}
|
|
9737
|
+
/**
|
|
9738
|
+
* Returns an FileSystemHandle Observable of the given DataTransferItem. The FileSystemHandle can only be accessed in the `dragstart`
|
|
9739
|
+
* or `drop` event. Otherwise, it will return null!
|
|
9740
|
+
*/
|
|
9741
|
+
static getAsFileSystemHandle(transferItem) {
|
|
9742
|
+
// @ts-ignore
|
|
9743
|
+
return from(transferItem.getAsFileSystemHandle());
|
|
9744
|
+
}
|
|
9745
|
+
/***************************************************************************
|
|
9746
|
+
* *
|
|
9747
|
+
* File Picker *
|
|
9748
|
+
* *
|
|
9749
|
+
**************************************************************************/
|
|
9750
|
+
static isFileSystemSupported() {
|
|
9751
|
+
return 'showOpenFilePicker' in window;
|
|
9752
|
+
}
|
|
9753
|
+
static openFilePicker(pickerOpts) {
|
|
9754
|
+
try {
|
|
9755
|
+
// @ts-ignore
|
|
9756
|
+
return from(window.showOpenFilePicker(pickerOpts));
|
|
9757
|
+
}
|
|
9758
|
+
catch (e) {
|
|
9759
|
+
return throwError(() => new Error(e));
|
|
9760
|
+
}
|
|
9761
|
+
}
|
|
9762
|
+
/***************************************************************************
|
|
9763
|
+
* *
|
|
9764
|
+
* Directory Picker *
|
|
9765
|
+
* *
|
|
9766
|
+
**************************************************************************/
|
|
9767
|
+
static isDirectoryPickerSupported() {
|
|
9768
|
+
return 'showDirectoryPicker' in window;
|
|
9769
|
+
}
|
|
9770
|
+
static openDirectoryPicker(pickerOpts) {
|
|
9771
|
+
try {
|
|
9772
|
+
// @ts-ignore
|
|
9773
|
+
return from(window.showDirectoryPicker(pickerOpts));
|
|
9774
|
+
}
|
|
9775
|
+
catch (e) {
|
|
9776
|
+
return throwError(() => new Error(e));
|
|
9777
|
+
}
|
|
9778
|
+
}
|
|
9779
|
+
/***************************************************************************
|
|
9780
|
+
* *
|
|
9781
|
+
* FileSystemDirectoryHandle *
|
|
9782
|
+
* *
|
|
9783
|
+
**************************************************************************/
|
|
9784
|
+
static getHandlesFromDirectory(dirHandle) {
|
|
9785
|
+
return from(FileSystemApi.getHandlesFromDirectoryAsync(dirHandle));
|
|
9786
|
+
}
|
|
9787
|
+
static buildRelativeParent(fileOrDirHandle, rootDirectory) {
|
|
9788
|
+
if (rootDirectory == null) {
|
|
9789
|
+
return of(undefined);
|
|
9790
|
+
}
|
|
9791
|
+
const relativeParents$ = from(rootDirectory.resolve(fileOrDirHandle));
|
|
9792
|
+
return relativeParents$.pipe(map(parents => {
|
|
9793
|
+
// path/to/folder/file.jpg
|
|
9794
|
+
const path = parents;
|
|
9795
|
+
// root/path/to/folder/file.jpg
|
|
9796
|
+
path.unshift(rootDirectory.name);
|
|
9797
|
+
// root/path/to/folder
|
|
9798
|
+
path.pop();
|
|
9799
|
+
return path.join('/');
|
|
9800
|
+
}));
|
|
9801
|
+
}
|
|
9802
|
+
static async getHandlesFromDirectoryAsync(dirHandle) {
|
|
9803
|
+
const handles = [];
|
|
9804
|
+
// @ts-ignore
|
|
9805
|
+
for await (const handle of dirHandle.values()) {
|
|
9806
|
+
handles.push(handle);
|
|
9807
|
+
}
|
|
9808
|
+
return handles;
|
|
9809
|
+
}
|
|
9810
|
+
}
|
|
9811
|
+
|
|
9718
9812
|
/**
|
|
9719
9813
|
* Represents a file and the relative path where the user has selected it.
|
|
9720
9814
|
* This is useful if the user has selected a folder and files
|
|
@@ -9729,26 +9823,24 @@ class FileEntry {
|
|
|
9729
9823
|
* which is also supported by this method.
|
|
9730
9824
|
*/
|
|
9731
9825
|
static ofFile(file) {
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
}
|
|
9742
|
-
return FileEntry.relativeFile(file, relativeParent);
|
|
9826
|
+
return FileEntry.relativeFile(file, this.buildRelativeParent(file));
|
|
9827
|
+
}
|
|
9828
|
+
/**
|
|
9829
|
+
* Creates an Observable of a File Entry by resolving its FileSystemFileHandle.
|
|
9830
|
+
* @param fileHandle
|
|
9831
|
+
* @param rootDirectory is needed to build a relative parent. Additionally useful for requesting permissions
|
|
9832
|
+
*/
|
|
9833
|
+
static ofFileHandle(fileHandle, rootDirectory) {
|
|
9834
|
+
return from(fileHandle.getFile()).pipe(combineLatestWith(FileSystemApi.buildRelativeParent(fileHandle, rootDirectory)), map(([file, relativeParent]) => FileEntry.relativeFile(file, relativeParent, fileHandle, rootDirectory)));
|
|
9743
9835
|
}
|
|
9744
9836
|
/**
|
|
9745
9837
|
* Creates a file Entry with a relative parent path
|
|
9746
9838
|
*/
|
|
9747
|
-
static relativeFile(file, relativeParent) {
|
|
9839
|
+
static relativeFile(file, relativeParent, fileHandle, rootDirectoryHandle) {
|
|
9748
9840
|
if (relativeParent && relativeParent.endsWith('/')) {
|
|
9749
9841
|
relativeParent = relativeParent.substring(0, relativeParent.length - 1);
|
|
9750
9842
|
}
|
|
9751
|
-
return new FileEntry(file, relativeParent);
|
|
9843
|
+
return new FileEntry(file, relativeParent, fileHandle, rootDirectoryHandle);
|
|
9752
9844
|
}
|
|
9753
9845
|
static toFileMap(entries) {
|
|
9754
9846
|
const map = new Map();
|
|
@@ -9768,9 +9860,11 @@ class FileEntry {
|
|
|
9768
9860
|
* Contains the relative path from the selected folder
|
|
9769
9861
|
* to this file. Only relevant if the user has selected a folder.
|
|
9770
9862
|
*/
|
|
9771
|
-
relativeParent) {
|
|
9863
|
+
relativeParent, fileHandle, rootDirectoryHandle) {
|
|
9772
9864
|
this.file = file;
|
|
9773
9865
|
this.relativeParent = relativeParent;
|
|
9866
|
+
this.fileHandle = fileHandle;
|
|
9867
|
+
this.rootDirectoryHandle = rootDirectoryHandle;
|
|
9774
9868
|
this.relativePath = FileEntry.buildRelativePath(relativeParent, file);
|
|
9775
9869
|
}
|
|
9776
9870
|
/***************************************************************************
|
|
@@ -9789,42 +9883,46 @@ class FileEntry {
|
|
|
9789
9883
|
return file.name;
|
|
9790
9884
|
}
|
|
9791
9885
|
}
|
|
9886
|
+
static buildRelativeParent(file) {
|
|
9887
|
+
let relativeParent = null;
|
|
9888
|
+
if (file.webkitRelativePath) {
|
|
9889
|
+
if (file.webkitRelativePath.endsWith(file.name)) {
|
|
9890
|
+
const nameStart = file.webkitRelativePath.length - file.name.length;
|
|
9891
|
+
relativeParent = file.webkitRelativePath.substring(0, nameStart);
|
|
9892
|
+
}
|
|
9893
|
+
else {
|
|
9894
|
+
relativeParent = file.webkitRelativePath;
|
|
9895
|
+
}
|
|
9896
|
+
}
|
|
9897
|
+
return relativeParent;
|
|
9898
|
+
}
|
|
9792
9899
|
}
|
|
9793
9900
|
|
|
9794
|
-
class
|
|
9901
|
+
class ElderFileSelectInput {
|
|
9795
9902
|
/***************************************************************************
|
|
9796
9903
|
* *
|
|
9797
9904
|
* Constructor *
|
|
9798
9905
|
* *
|
|
9799
9906
|
**************************************************************************/
|
|
9800
|
-
constructor(
|
|
9801
|
-
this.el = el;
|
|
9907
|
+
constructor(renderer, el) {
|
|
9802
9908
|
this.renderer = renderer;
|
|
9909
|
+
this.el = el;
|
|
9803
9910
|
/***************************************************************************
|
|
9804
9911
|
* *
|
|
9805
9912
|
* Fields *
|
|
9806
9913
|
* *
|
|
9807
9914
|
**************************************************************************/
|
|
9808
9915
|
this.logger = LoggerFactory.getLogger(this.constructor.name);
|
|
9809
|
-
this.
|
|
9810
|
-
this.elderSingleFileSelectChange = new EventEmitter();
|
|
9811
|
-
}
|
|
9812
|
-
/***************************************************************************
|
|
9813
|
-
* *
|
|
9814
|
-
* Life Cycle *
|
|
9815
|
-
* *
|
|
9816
|
-
**************************************************************************/
|
|
9817
|
-
ngOnInit() {
|
|
9818
|
-
this.createFileSelect();
|
|
9819
|
-
}
|
|
9820
|
-
ngOnDestroy() {
|
|
9821
|
-
this.removeFileSelect();
|
|
9916
|
+
this._filesSelected = new Subject;
|
|
9822
9917
|
}
|
|
9823
9918
|
/***************************************************************************
|
|
9824
9919
|
* *
|
|
9825
9920
|
* Properties *
|
|
9826
9921
|
* *
|
|
9827
9922
|
**************************************************************************/
|
|
9923
|
+
get filesSelected() {
|
|
9924
|
+
return this._filesSelected;
|
|
9925
|
+
}
|
|
9828
9926
|
set elderFileSelect(value) {
|
|
9829
9927
|
this._accept = value;
|
|
9830
9928
|
if (this._fileInput) {
|
|
@@ -9837,11 +9935,6 @@ class ElderFileSelectDirective {
|
|
|
9837
9935
|
this.renderer.setProperty(this._fileInput, 'multiple', value);
|
|
9838
9936
|
}
|
|
9839
9937
|
}
|
|
9840
|
-
/**
|
|
9841
|
-
* Allow the user to select a directory. All files that are recursively contained are selected.
|
|
9842
|
-
* However, the user will no longer be able to select individual files if this is enabled.
|
|
9843
|
-
* @param value
|
|
9844
|
-
*/
|
|
9845
9938
|
set elderFileSelectDirectory(value) {
|
|
9846
9939
|
this._directory = coerceBooleanProperty(value);
|
|
9847
9940
|
if (this._fileInput) {
|
|
@@ -9854,17 +9947,6 @@ class ElderFileSelectDirective {
|
|
|
9854
9947
|
* Public API *
|
|
9855
9948
|
* *
|
|
9856
9949
|
**************************************************************************/
|
|
9857
|
-
onClick(event) {
|
|
9858
|
-
this.openFileSelectDialog();
|
|
9859
|
-
}
|
|
9860
|
-
openFileSelectDialog() {
|
|
9861
|
-
this._fileInput.click();
|
|
9862
|
-
}
|
|
9863
|
-
/***************************************************************************
|
|
9864
|
-
* *
|
|
9865
|
-
* Private methods *
|
|
9866
|
-
* *
|
|
9867
|
-
**************************************************************************/
|
|
9868
9950
|
/**
|
|
9869
9951
|
* <input type="file"
|
|
9870
9952
|
* hidden #fileInput
|
|
@@ -9888,7 +9970,9 @@ class ElderFileSelectDirective {
|
|
|
9888
9970
|
this.renderer.setAttribute(this._fileInput, 'webkitdirectory', 'true');
|
|
9889
9971
|
this.renderer.setAttribute(this._fileInput, 'directory', 'true');
|
|
9890
9972
|
}
|
|
9891
|
-
this._unlisten = this.renderer.listen(this._fileInput, 'change', event =>
|
|
9973
|
+
this._unlisten = this.renderer.listen(this._fileInput, 'change', event => {
|
|
9974
|
+
this.fileInputChanged(this._fileInput.files);
|
|
9975
|
+
});
|
|
9892
9976
|
}
|
|
9893
9977
|
removeFileSelect() {
|
|
9894
9978
|
if (this._unlisten) {
|
|
@@ -9900,26 +9984,24 @@ class ElderFileSelectDirective {
|
|
|
9900
9984
|
this._fileInput = null;
|
|
9901
9985
|
}
|
|
9902
9986
|
}
|
|
9903
|
-
|
|
9904
|
-
|
|
9987
|
+
openFileSelectDialog() {
|
|
9988
|
+
this._fileInput.click();
|
|
9989
|
+
}
|
|
9990
|
+
/***************************************************************************
|
|
9991
|
+
* *
|
|
9992
|
+
* Private methods *
|
|
9993
|
+
* *
|
|
9994
|
+
**************************************************************************/
|
|
9995
|
+
fileInputChanged(fileList) {
|
|
9905
9996
|
const files = this.toFileEntries(fileList);
|
|
9906
9997
|
this.logger.debug('fileInputChanged, files:', files);
|
|
9907
|
-
this.emitFileList(files);
|
|
9908
9998
|
this.clearFileList();
|
|
9999
|
+
this._filesSelected.next(files);
|
|
9909
10000
|
}
|
|
9910
10001
|
clearFileList() {
|
|
9911
10002
|
// not nice but works
|
|
9912
10003
|
this._fileInput.value = null;
|
|
9913
10004
|
}
|
|
9914
|
-
emitFileList(files) {
|
|
9915
|
-
if (files.length > 0) {
|
|
9916
|
-
this.elderFileSelectChange.next(files);
|
|
9917
|
-
this.elderSingleFileSelectChange.emit(files[0].file);
|
|
9918
|
-
}
|
|
9919
|
-
else {
|
|
9920
|
-
this.logger.warn('User did not select any File or the Folder was empty.');
|
|
9921
|
-
}
|
|
9922
|
-
}
|
|
9923
10005
|
toFileEntries(fileList) {
|
|
9924
10006
|
const files = [];
|
|
9925
10007
|
for (const key in fileList) {
|
|
@@ -9929,7 +10011,175 @@ class ElderFileSelectDirective {
|
|
|
9929
10011
|
}
|
|
9930
10012
|
return files;
|
|
9931
10013
|
}
|
|
9932
|
-
|
|
10014
|
+
}
|
|
10015
|
+
|
|
10016
|
+
class FileListingSystemHandle {
|
|
10017
|
+
/***************************************************************************
|
|
10018
|
+
* *
|
|
10019
|
+
* Public methods *
|
|
10020
|
+
* *
|
|
10021
|
+
**************************************************************************/
|
|
10022
|
+
/**
|
|
10023
|
+
* Creates a File Entry Array Observable by resolving its FileSystemHandle.
|
|
10024
|
+
* @returns An `Observable<FileEntry[]>` with the FileSystemFileHandle included
|
|
10025
|
+
*/
|
|
10026
|
+
static listFiles(handle, rootDirectory) {
|
|
10027
|
+
switch (handle.kind) {
|
|
10028
|
+
case 'file':
|
|
10029
|
+
return FileEntry.ofFileHandle(handle, rootDirectory).pipe(toArray());
|
|
10030
|
+
case 'directory':
|
|
10031
|
+
return FileListingSystemHandle.listFilesRecursive(handle, rootDirectory);
|
|
10032
|
+
}
|
|
10033
|
+
}
|
|
10034
|
+
/***************************************************************************
|
|
10035
|
+
* *
|
|
10036
|
+
* Private methods *
|
|
10037
|
+
* *
|
|
10038
|
+
**************************************************************************/
|
|
10039
|
+
static listFilesRecursive(dirHandle, rootDirectory) {
|
|
10040
|
+
return FileSystemApi.getHandlesFromDirectory(dirHandle).pipe(map(handles => FileListingSystemHandle.listFilesOfEach(handles, rootDirectory ? rootDirectory : dirHandle)), switchMap(fileEntriesArray$ => zip(fileEntriesArray$)), map(fileEntriesArray => fileEntriesArray.flat()));
|
|
10041
|
+
}
|
|
10042
|
+
static listFilesOfEach(handles, rootDirectory) {
|
|
10043
|
+
return handles.map(fileOrDirHandle => FileListingSystemHandle.listFiles(fileOrDirHandle, rootDirectory));
|
|
10044
|
+
}
|
|
10045
|
+
}
|
|
10046
|
+
|
|
10047
|
+
class ElderFileSelectDirective {
|
|
10048
|
+
/***************************************************************************
|
|
10049
|
+
* *
|
|
10050
|
+
* Constructor *
|
|
10051
|
+
* *
|
|
10052
|
+
**************************************************************************/
|
|
10053
|
+
constructor(renderer, el, destroyRef) {
|
|
10054
|
+
this.renderer = renderer;
|
|
10055
|
+
this.el = el;
|
|
10056
|
+
this.destroyRef = destroyRef;
|
|
10057
|
+
/***************************************************************************
|
|
10058
|
+
* *
|
|
10059
|
+
* Fields *
|
|
10060
|
+
* *
|
|
10061
|
+
**************************************************************************/
|
|
10062
|
+
this.logger = LoggerFactory.getLogger(this.constructor.name);
|
|
10063
|
+
this.elderFileSelectChange = new EventEmitter();
|
|
10064
|
+
this.elderSingleFileSelectChange = new EventEmitter();
|
|
10065
|
+
this._useDirectoryPicker = false;
|
|
10066
|
+
this._legacyInput = new ElderFileSelectInput(this.renderer, this.el);
|
|
10067
|
+
}
|
|
10068
|
+
/***************************************************************************
|
|
10069
|
+
* *
|
|
10070
|
+
* Life Cycle *
|
|
10071
|
+
* *
|
|
10072
|
+
**************************************************************************/
|
|
10073
|
+
ngOnInit() {
|
|
10074
|
+
if (!FileSystemApi.isFileSystemSupported() || !FileSystemApi.isDirectoryPickerSupported()) {
|
|
10075
|
+
this.logger.warn('showOpenFilePicker or showDirectoryPicker is not supported for this browser, falling back to legacy file picker');
|
|
10076
|
+
this._legacyInput.createFileSelect();
|
|
10077
|
+
this._legacyInput.filesSelected.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
10078
|
+
next: multiFileChange => this.pushFileChanges(multiFileChange)
|
|
10079
|
+
});
|
|
10080
|
+
}
|
|
10081
|
+
}
|
|
10082
|
+
ngOnDestroy() {
|
|
10083
|
+
this._legacyInput.removeFileSelect();
|
|
10084
|
+
}
|
|
10085
|
+
/***************************************************************************
|
|
10086
|
+
* *
|
|
10087
|
+
* Properties *
|
|
10088
|
+
* *
|
|
10089
|
+
**************************************************************************/
|
|
10090
|
+
set elderFileSelect(value) {
|
|
10091
|
+
this._filePickerOptions = {
|
|
10092
|
+
excludeAcceptAllOptions: true,
|
|
10093
|
+
id: this._filePickerOptions?.id,
|
|
10094
|
+
multiple: this._filePickerOptions?.multiple,
|
|
10095
|
+
startIn: this._filePickerOptions?.startIn,
|
|
10096
|
+
types: [this.buildTypeOption(value)]
|
|
10097
|
+
};
|
|
10098
|
+
this._legacyInput.elderFileSelect = value;
|
|
10099
|
+
}
|
|
10100
|
+
buildTypeOption(value) {
|
|
10101
|
+
if (this.isFileExtension(value)) {
|
|
10102
|
+
return {
|
|
10103
|
+
accept: {
|
|
10104
|
+
'*/*': value.trim()
|
|
10105
|
+
.replace(/\s+/g, '')
|
|
10106
|
+
.split(',')
|
|
10107
|
+
}
|
|
10108
|
+
};
|
|
10109
|
+
}
|
|
10110
|
+
else if (this.isMimeType(value)) {
|
|
10111
|
+
return {
|
|
10112
|
+
accept: { [value]: [] }
|
|
10113
|
+
};
|
|
10114
|
+
}
|
|
10115
|
+
return null;
|
|
10116
|
+
}
|
|
10117
|
+
isFileExtension(input) {
|
|
10118
|
+
const regex = new RegExp(/^(\.\w+)(, ?\.\w+)*$/);
|
|
10119
|
+
return regex.test(input);
|
|
10120
|
+
}
|
|
10121
|
+
isMimeType(input) {
|
|
10122
|
+
const regex = new RegExp(/^.+\/.+$/);
|
|
10123
|
+
return regex.test(input);
|
|
10124
|
+
}
|
|
10125
|
+
set elderFileSelectMultiple(value) {
|
|
10126
|
+
this._filePickerOptions = {
|
|
10127
|
+
excludeAcceptAllOptions: this._filePickerOptions?.excludeAcceptAllOptions,
|
|
10128
|
+
id: this._filePickerOptions?.id,
|
|
10129
|
+
multiple: coerceBooleanProperty(value),
|
|
10130
|
+
startIn: this._filePickerOptions?.startIn,
|
|
10131
|
+
types: this._filePickerOptions?.types
|
|
10132
|
+
};
|
|
10133
|
+
this._legacyInput.elderFileSelectMultiple = value;
|
|
10134
|
+
}
|
|
10135
|
+
/**
|
|
10136
|
+
* Allow the user to select a directory. All files that are recursively contained are selected.
|
|
10137
|
+
* However, the user will no longer be able to select individual files if this is enabled.
|
|
10138
|
+
* @param value
|
|
10139
|
+
*/
|
|
10140
|
+
set elderFileSelectDirectory(value) {
|
|
10141
|
+
this._useDirectoryPicker = coerceBooleanProperty(value);
|
|
10142
|
+
this._legacyInput.elderFileSelectDirectory = value;
|
|
10143
|
+
}
|
|
10144
|
+
/***************************************************************************
|
|
10145
|
+
* *
|
|
10146
|
+
* Public API *
|
|
10147
|
+
* *
|
|
10148
|
+
**************************************************************************/
|
|
10149
|
+
onClick(event) {
|
|
10150
|
+
if (!this._useDirectoryPicker && FileSystemApi.isFileSystemSupported()) {
|
|
10151
|
+
this.openFilePicker(this._filePickerOptions).subscribe({
|
|
10152
|
+
next: multiFileChange => this.pushFileChanges(multiFileChange)
|
|
10153
|
+
});
|
|
10154
|
+
}
|
|
10155
|
+
else if (this._useDirectoryPicker && FileSystemApi.isDirectoryPickerSupported()) {
|
|
10156
|
+
this.openDirectoryPicker(this._directoryPickerOptions).subscribe({
|
|
10157
|
+
next: multiFileChange => this.pushFileChanges(multiFileChange)
|
|
10158
|
+
});
|
|
10159
|
+
}
|
|
10160
|
+
else {
|
|
10161
|
+
this._legacyInput.openFileSelectDialog();
|
|
10162
|
+
}
|
|
10163
|
+
}
|
|
10164
|
+
/***************************************************************************
|
|
10165
|
+
* *
|
|
10166
|
+
* Private methods *
|
|
10167
|
+
* *
|
|
10168
|
+
**************************************************************************/
|
|
10169
|
+
pushFileChanges(multiFileChange) {
|
|
10170
|
+
this.elderFileSelectChange.next(multiFileChange);
|
|
10171
|
+
const file = multiFileChange[0]?.file;
|
|
10172
|
+
if (file) {
|
|
10173
|
+
this.elderSingleFileSelectChange.next(file);
|
|
10174
|
+
}
|
|
10175
|
+
}
|
|
10176
|
+
openFilePicker(pickerOpts) {
|
|
10177
|
+
return FileSystemApi.openFilePicker(pickerOpts).pipe(switchMap(files => combineLatest(files.map(file => FileEntry.ofFileHandle(file)))));
|
|
10178
|
+
}
|
|
10179
|
+
openDirectoryPicker(pickerOpts) {
|
|
10180
|
+
return FileSystemApi.openDirectoryPicker(pickerOpts).pipe(switchMap(dir => FileListingSystemHandle.listFiles(dir)));
|
|
10181
|
+
}
|
|
10182
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: ElderFileSelectDirective, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }, { token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
9933
10183
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.0.4", type: ElderFileSelectDirective, isStandalone: true, selector: "[elderFileSelect]", inputs: { elderFileSelect: "elderFileSelect", elderFileSelectMultiple: "elderFileSelectMultiple", elderFileSelectDirectory: "elderFileSelectDirectory" }, outputs: { elderFileSelectChange: "elderFileSelectChange", elderSingleFileSelectChange: "elderSingleFileSelectChange" }, host: { listeners: { "click": "onClick($event)" } }, ngImport: i0 }); }
|
|
9934
10184
|
}
|
|
9935
10185
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: ElderFileSelectDirective, decorators: [{
|
|
@@ -9938,7 +10188,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImpor
|
|
|
9938
10188
|
selector: '[elderFileSelect]',
|
|
9939
10189
|
standalone: true
|
|
9940
10190
|
}]
|
|
9941
|
-
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.
|
|
10191
|
+
}], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i0.DestroyRef }], propDecorators: { elderFileSelectChange: [{
|
|
9942
10192
|
type: Output
|
|
9943
10193
|
}], elderSingleFileSelectChange: [{
|
|
9944
10194
|
type: Output
|
|
@@ -10058,70 +10308,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImpor
|
|
|
10058
10308
|
type: Input
|
|
10059
10309
|
}] } });
|
|
10060
10310
|
|
|
10061
|
-
class
|
|
10311
|
+
class FileListingWebkit {
|
|
10312
|
+
/***************************************************************************
|
|
10313
|
+
* *
|
|
10314
|
+
* Constructor *
|
|
10315
|
+
* *
|
|
10316
|
+
**************************************************************************/
|
|
10062
10317
|
constructor() {
|
|
10063
|
-
this.log = LoggerFactory.getLogger(this.constructor.name);
|
|
10064
10318
|
}
|
|
10065
|
-
static { this.INSTANCE = new FileListingRx(); }
|
|
10066
10319
|
/***************************************************************************
|
|
10067
10320
|
* *
|
|
10068
|
-
* Public
|
|
10321
|
+
* Public methods *
|
|
10069
10322
|
* *
|
|
10070
10323
|
**************************************************************************/
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
for (let i = 0; i < transferItemList.length; i++) {
|
|
10074
|
-
const transferItem = transferItemList[i];
|
|
10075
|
-
const entry = transferItem.webkitGetAsEntry();
|
|
10076
|
-
if (entry) {
|
|
10077
|
-
obs.push(this.listFilesRecursive(entry));
|
|
10078
|
-
}
|
|
10079
|
-
else {
|
|
10080
|
-
const itemAsFile = transferItem.getAsFile();
|
|
10081
|
-
if (itemAsFile) {
|
|
10082
|
-
obs.push(of([FileEntry.ofFile(itemAsFile)]));
|
|
10083
|
-
}
|
|
10084
|
-
else {
|
|
10085
|
-
if (transferItem.kind == 'file') {
|
|
10086
|
-
obs.push(of([
|
|
10087
|
-
FileEntry.ofFile(new File([], '', {
|
|
10088
|
-
type: transferItem.type
|
|
10089
|
-
}))
|
|
10090
|
-
]));
|
|
10091
|
-
}
|
|
10092
|
-
else {
|
|
10093
|
-
this.log.warn('Could not handle DataTransferItem!', transferItem);
|
|
10094
|
-
}
|
|
10095
|
-
}
|
|
10096
|
-
}
|
|
10097
|
-
}
|
|
10098
|
-
return zip(obs).pipe(map(files => files.flat()));
|
|
10099
|
-
}
|
|
10100
|
-
listFilesRecursive(entry) {
|
|
10101
|
-
return this.listFilesRecursiveAsync(entry, null);
|
|
10324
|
+
static listFilesRecursive(entry) {
|
|
10325
|
+
return FileListingWebkit.listFilesRecursiveAsync(entry, null);
|
|
10102
10326
|
}
|
|
10103
10327
|
/***************************************************************************
|
|
10104
10328
|
* *
|
|
10105
10329
|
* Private methods *
|
|
10106
10330
|
* *
|
|
10107
10331
|
**************************************************************************/
|
|
10108
|
-
listFilesRecursiveAsync(entry, parent) {
|
|
10332
|
+
static listFilesRecursiveAsync(entry, parent) {
|
|
10109
10333
|
if (entry.isDirectory) {
|
|
10110
10334
|
const directoryEntry = entry;
|
|
10111
|
-
return
|
|
10335
|
+
return FileListingWebkit.readEntries(directoryEntry).pipe(mergeMap$1(entries => zip(entries.map(entry => this.listFilesRecursiveAsync(entry, directoryEntry)))), map(files => files.flat()));
|
|
10112
10336
|
}
|
|
10113
10337
|
else if (entry.isFile) {
|
|
10114
10338
|
const fileEntry = entry;
|
|
10115
|
-
return
|
|
10339
|
+
return FileListingWebkit.observableFile(fileEntry, parent).pipe(map(file => [file]));
|
|
10116
10340
|
}
|
|
10117
10341
|
else {
|
|
10118
10342
|
return EMPTY;
|
|
10119
10343
|
}
|
|
10120
10344
|
}
|
|
10121
|
-
observableFile(fileEntry, parent) {
|
|
10345
|
+
static observableFile(fileEntry, parent) {
|
|
10122
10346
|
return new Observable(observer => {
|
|
10123
10347
|
fileEntry.file(file => {
|
|
10124
|
-
observer.next(FileEntry.relativeFile(file,
|
|
10348
|
+
observer.next(FileEntry.relativeFile(file, FileListingWebkit.trimStaringSlash(parent?.fullPath)));
|
|
10125
10349
|
observer.complete();
|
|
10126
10350
|
}, err => {
|
|
10127
10351
|
observer.error(err);
|
|
@@ -10129,7 +10353,7 @@ class FileListingRx {
|
|
|
10129
10353
|
});
|
|
10130
10354
|
});
|
|
10131
10355
|
}
|
|
10132
|
-
trimStaringSlash(path) {
|
|
10356
|
+
static trimStaringSlash(path) {
|
|
10133
10357
|
if (path) {
|
|
10134
10358
|
if (path.startsWith('/')) {
|
|
10135
10359
|
path = path.substring(1);
|
|
@@ -10137,12 +10361,12 @@ class FileListingRx {
|
|
|
10137
10361
|
}
|
|
10138
10362
|
return path;
|
|
10139
10363
|
}
|
|
10140
|
-
readEntries(directoryEntry) {
|
|
10364
|
+
static readEntries(directoryEntry) {
|
|
10141
10365
|
return new Observable(observer => {
|
|
10142
|
-
|
|
10366
|
+
FileListingWebkit.consumeReaderToCompletion(observer, directoryEntry.createReader());
|
|
10143
10367
|
});
|
|
10144
10368
|
}
|
|
10145
|
-
consumeReaderToCompletion(observer, directoryReader) {
|
|
10369
|
+
static consumeReaderToCompletion(observer, directoryReader) {
|
|
10146
10370
|
directoryReader.readEntries(entries => {
|
|
10147
10371
|
if (entries && entries.length > 0) {
|
|
10148
10372
|
observer.next(entries);
|
|
@@ -10158,6 +10382,63 @@ class FileListingRx {
|
|
|
10158
10382
|
}
|
|
10159
10383
|
}
|
|
10160
10384
|
|
|
10385
|
+
class FileListingRx {
|
|
10386
|
+
constructor() {
|
|
10387
|
+
this.log = LoggerFactory.getLogger(this.constructor.name);
|
|
10388
|
+
}
|
|
10389
|
+
static { this.INSTANCE = new FileListingRx(); }
|
|
10390
|
+
/***************************************************************************
|
|
10391
|
+
* *
|
|
10392
|
+
* Public API *
|
|
10393
|
+
* *
|
|
10394
|
+
**************************************************************************/
|
|
10395
|
+
toFileList(transferItemList) {
|
|
10396
|
+
const obs = [];
|
|
10397
|
+
for (let i = 0; i < transferItemList.length; i++) {
|
|
10398
|
+
const transferItem = transferItemList[i];
|
|
10399
|
+
obs.push(this.resolveTransferItem(transferItem));
|
|
10400
|
+
}
|
|
10401
|
+
return zip(obs).pipe(map(files => files.flat()));
|
|
10402
|
+
}
|
|
10403
|
+
/***************************************************************************
|
|
10404
|
+
* *
|
|
10405
|
+
* Private methods *
|
|
10406
|
+
* *
|
|
10407
|
+
**************************************************************************/
|
|
10408
|
+
resolveTransferItem(transferItem) {
|
|
10409
|
+
if (FileSystemApi.isGetAsFileSystemHandleSupported(transferItem)) {
|
|
10410
|
+
return FileSystemApi.getAsFileSystemHandle(transferItem).pipe(switchMap(handle => handle ? FileListingSystemHandle.listFiles(handle) : this.legacyFileListing(transferItem)));
|
|
10411
|
+
}
|
|
10412
|
+
return this.legacyFileListing(transferItem);
|
|
10413
|
+
}
|
|
10414
|
+
legacyFileListing(transferItem) {
|
|
10415
|
+
const entry = transferItem.webkitGetAsEntry();
|
|
10416
|
+
if (entry) {
|
|
10417
|
+
return FileListingWebkit.listFilesRecursive(entry);
|
|
10418
|
+
}
|
|
10419
|
+
return this.fallbackFileListing(transferItem);
|
|
10420
|
+
}
|
|
10421
|
+
fallbackFileListing(transferItem) {
|
|
10422
|
+
const itemAsFile = transferItem.getAsFile();
|
|
10423
|
+
if (itemAsFile) {
|
|
10424
|
+
return of([FileEntry.ofFile(itemAsFile)]);
|
|
10425
|
+
}
|
|
10426
|
+
else {
|
|
10427
|
+
if (transferItem.kind == 'file') {
|
|
10428
|
+
return of([
|
|
10429
|
+
FileEntry.ofFile(new File([], '', {
|
|
10430
|
+
type: transferItem.type
|
|
10431
|
+
}))
|
|
10432
|
+
]);
|
|
10433
|
+
}
|
|
10434
|
+
else {
|
|
10435
|
+
this.log.warn('Could not handle DataTransferItem!', transferItem);
|
|
10436
|
+
return of([]);
|
|
10437
|
+
}
|
|
10438
|
+
}
|
|
10439
|
+
}
|
|
10440
|
+
}
|
|
10441
|
+
|
|
10161
10442
|
class ElderFileDropZoneDirective {
|
|
10162
10443
|
/***************************************************************************
|
|
10163
10444
|
* *
|