@elderbyte/ngx-starter 18.2.0-beta4 → 18.2.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 +30 -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 +387 -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 +10 -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
|
|
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)));
|
|
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) {
|
|
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);
|
|
9752
9844
|
}
|
|
9753
9845
|
static toFileMap(entries) {
|
|
9754
9846
|
const map = new Map();
|
|
@@ -9768,9 +9860,10 @@ 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) {
|
|
9772
9864
|
this.file = file;
|
|
9773
9865
|
this.relativeParent = relativeParent;
|
|
9866
|
+
this.fileHandle = fileHandle;
|
|
9774
9867
|
this.relativePath = FileEntry.buildRelativePath(relativeParent, file);
|
|
9775
9868
|
}
|
|
9776
9869
|
/***************************************************************************
|
|
@@ -9789,42 +9882,46 @@ class FileEntry {
|
|
|
9789
9882
|
return file.name;
|
|
9790
9883
|
}
|
|
9791
9884
|
}
|
|
9885
|
+
static buildRelativeParent(file) {
|
|
9886
|
+
let relativeParent = null;
|
|
9887
|
+
if (file.webkitRelativePath) {
|
|
9888
|
+
if (file.webkitRelativePath.endsWith(file.name)) {
|
|
9889
|
+
const nameStart = file.webkitRelativePath.length - file.name.length;
|
|
9890
|
+
relativeParent = file.webkitRelativePath.substring(0, nameStart);
|
|
9891
|
+
}
|
|
9892
|
+
else {
|
|
9893
|
+
relativeParent = file.webkitRelativePath;
|
|
9894
|
+
}
|
|
9895
|
+
}
|
|
9896
|
+
return relativeParent;
|
|
9897
|
+
}
|
|
9792
9898
|
}
|
|
9793
9899
|
|
|
9794
|
-
class
|
|
9900
|
+
class ElderFileSelectInput {
|
|
9795
9901
|
/***************************************************************************
|
|
9796
9902
|
* *
|
|
9797
9903
|
* Constructor *
|
|
9798
9904
|
* *
|
|
9799
9905
|
**************************************************************************/
|
|
9800
|
-
constructor(
|
|
9801
|
-
this.el = el;
|
|
9906
|
+
constructor(renderer, el) {
|
|
9802
9907
|
this.renderer = renderer;
|
|
9908
|
+
this.el = el;
|
|
9803
9909
|
/***************************************************************************
|
|
9804
9910
|
* *
|
|
9805
9911
|
* Fields *
|
|
9806
9912
|
* *
|
|
9807
9913
|
**************************************************************************/
|
|
9808
9914
|
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();
|
|
9915
|
+
this._filesSelected = new Subject;
|
|
9822
9916
|
}
|
|
9823
9917
|
/***************************************************************************
|
|
9824
9918
|
* *
|
|
9825
9919
|
* Properties *
|
|
9826
9920
|
* *
|
|
9827
9921
|
**************************************************************************/
|
|
9922
|
+
get filesSelected() {
|
|
9923
|
+
return this._filesSelected;
|
|
9924
|
+
}
|
|
9828
9925
|
set elderFileSelect(value) {
|
|
9829
9926
|
this._accept = value;
|
|
9830
9927
|
if (this._fileInput) {
|
|
@@ -9837,11 +9934,6 @@ class ElderFileSelectDirective {
|
|
|
9837
9934
|
this.renderer.setProperty(this._fileInput, 'multiple', value);
|
|
9838
9935
|
}
|
|
9839
9936
|
}
|
|
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
9937
|
set elderFileSelectDirectory(value) {
|
|
9846
9938
|
this._directory = coerceBooleanProperty(value);
|
|
9847
9939
|
if (this._fileInput) {
|
|
@@ -9854,17 +9946,6 @@ class ElderFileSelectDirective {
|
|
|
9854
9946
|
* Public API *
|
|
9855
9947
|
* *
|
|
9856
9948
|
**************************************************************************/
|
|
9857
|
-
onClick(event) {
|
|
9858
|
-
this.openFileSelectDialog();
|
|
9859
|
-
}
|
|
9860
|
-
openFileSelectDialog() {
|
|
9861
|
-
this._fileInput.click();
|
|
9862
|
-
}
|
|
9863
|
-
/***************************************************************************
|
|
9864
|
-
* *
|
|
9865
|
-
* Private methods *
|
|
9866
|
-
* *
|
|
9867
|
-
**************************************************************************/
|
|
9868
9949
|
/**
|
|
9869
9950
|
* <input type="file"
|
|
9870
9951
|
* hidden #fileInput
|
|
@@ -9888,7 +9969,9 @@ class ElderFileSelectDirective {
|
|
|
9888
9969
|
this.renderer.setAttribute(this._fileInput, 'webkitdirectory', 'true');
|
|
9889
9970
|
this.renderer.setAttribute(this._fileInput, 'directory', 'true');
|
|
9890
9971
|
}
|
|
9891
|
-
this._unlisten = this.renderer.listen(this._fileInput, 'change', event =>
|
|
9972
|
+
this._unlisten = this.renderer.listen(this._fileInput, 'change', event => {
|
|
9973
|
+
this.fileInputChanged(this._fileInput.files);
|
|
9974
|
+
});
|
|
9892
9975
|
}
|
|
9893
9976
|
removeFileSelect() {
|
|
9894
9977
|
if (this._unlisten) {
|
|
@@ -9900,26 +9983,24 @@ class ElderFileSelectDirective {
|
|
|
9900
9983
|
this._fileInput = null;
|
|
9901
9984
|
}
|
|
9902
9985
|
}
|
|
9903
|
-
|
|
9904
|
-
|
|
9986
|
+
openFileSelectDialog() {
|
|
9987
|
+
this._fileInput.click();
|
|
9988
|
+
}
|
|
9989
|
+
/***************************************************************************
|
|
9990
|
+
* *
|
|
9991
|
+
* Private methods *
|
|
9992
|
+
* *
|
|
9993
|
+
**************************************************************************/
|
|
9994
|
+
fileInputChanged(fileList) {
|
|
9905
9995
|
const files = this.toFileEntries(fileList);
|
|
9906
9996
|
this.logger.debug('fileInputChanged, files:', files);
|
|
9907
|
-
this.emitFileList(files);
|
|
9908
9997
|
this.clearFileList();
|
|
9998
|
+
this._filesSelected.next(files);
|
|
9909
9999
|
}
|
|
9910
10000
|
clearFileList() {
|
|
9911
10001
|
// not nice but works
|
|
9912
10002
|
this._fileInput.value = null;
|
|
9913
10003
|
}
|
|
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
10004
|
toFileEntries(fileList) {
|
|
9924
10005
|
const files = [];
|
|
9925
10006
|
for (const key in fileList) {
|
|
@@ -9929,7 +10010,175 @@ class ElderFileSelectDirective {
|
|
|
9929
10010
|
}
|
|
9930
10011
|
return files;
|
|
9931
10012
|
}
|
|
9932
|
-
|
|
10013
|
+
}
|
|
10014
|
+
|
|
10015
|
+
class FileListingSystemHandle {
|
|
10016
|
+
/***************************************************************************
|
|
10017
|
+
* *
|
|
10018
|
+
* Public methods *
|
|
10019
|
+
* *
|
|
10020
|
+
**************************************************************************/
|
|
10021
|
+
/**
|
|
10022
|
+
* Creates a File Entry Array Observable by resolving its FileSystemHandle.
|
|
10023
|
+
* @returns An `Observable<FileEntry[]>` with the FileSystemFileHandle included
|
|
10024
|
+
*/
|
|
10025
|
+
static listFiles(handle, rootDirectory) {
|
|
10026
|
+
switch (handle.kind) {
|
|
10027
|
+
case 'file':
|
|
10028
|
+
return FileEntry.ofFileHandle(handle, rootDirectory).pipe(toArray());
|
|
10029
|
+
case 'directory':
|
|
10030
|
+
return FileListingSystemHandle.listFilesRecursive(handle, rootDirectory);
|
|
10031
|
+
}
|
|
10032
|
+
}
|
|
10033
|
+
/***************************************************************************
|
|
10034
|
+
* *
|
|
10035
|
+
* Private methods *
|
|
10036
|
+
* *
|
|
10037
|
+
**************************************************************************/
|
|
10038
|
+
static listFilesRecursive(dirHandle, rootDirectory) {
|
|
10039
|
+
return FileSystemApi.getHandlesFromDirectory(dirHandle).pipe(map(handles => FileListingSystemHandle.listFilesOfEach(handles, rootDirectory ? rootDirectory : dirHandle)), switchMap(fileEntriesArray$ => zip(fileEntriesArray$)), map(fileEntriesArray => fileEntriesArray.flat()));
|
|
10040
|
+
}
|
|
10041
|
+
static listFilesOfEach(handles, rootDirectory) {
|
|
10042
|
+
return handles.map(fileOrDirHandle => FileListingSystemHandle.listFiles(fileOrDirHandle, rootDirectory));
|
|
10043
|
+
}
|
|
10044
|
+
}
|
|
10045
|
+
|
|
10046
|
+
class ElderFileSelectDirective {
|
|
10047
|
+
/***************************************************************************
|
|
10048
|
+
* *
|
|
10049
|
+
* Constructor *
|
|
10050
|
+
* *
|
|
10051
|
+
**************************************************************************/
|
|
10052
|
+
constructor(renderer, el, destroyRef) {
|
|
10053
|
+
this.renderer = renderer;
|
|
10054
|
+
this.el = el;
|
|
10055
|
+
this.destroyRef = destroyRef;
|
|
10056
|
+
/***************************************************************************
|
|
10057
|
+
* *
|
|
10058
|
+
* Fields *
|
|
10059
|
+
* *
|
|
10060
|
+
**************************************************************************/
|
|
10061
|
+
this.logger = LoggerFactory.getLogger(this.constructor.name);
|
|
10062
|
+
this.elderFileSelectChange = new EventEmitter();
|
|
10063
|
+
this.elderSingleFileSelectChange = new EventEmitter();
|
|
10064
|
+
this._useDirectoryPicker = false;
|
|
10065
|
+
this._legacyInput = new ElderFileSelectInput(this.renderer, this.el);
|
|
10066
|
+
}
|
|
10067
|
+
/***************************************************************************
|
|
10068
|
+
* *
|
|
10069
|
+
* Life Cycle *
|
|
10070
|
+
* *
|
|
10071
|
+
**************************************************************************/
|
|
10072
|
+
ngOnInit() {
|
|
10073
|
+
if (!FileSystemApi.isFileSystemSupported() || !FileSystemApi.isDirectoryPickerSupported()) {
|
|
10074
|
+
this.logger.warn('showOpenFilePicker or showDirectoryPicker is not supported for this browser, falling back to legacy file picker');
|
|
10075
|
+
this._legacyInput.createFileSelect();
|
|
10076
|
+
this._legacyInput.filesSelected.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
10077
|
+
next: multiFileChange => this.pushFileChanges(multiFileChange)
|
|
10078
|
+
});
|
|
10079
|
+
}
|
|
10080
|
+
}
|
|
10081
|
+
ngOnDestroy() {
|
|
10082
|
+
this._legacyInput.removeFileSelect();
|
|
10083
|
+
}
|
|
10084
|
+
/***************************************************************************
|
|
10085
|
+
* *
|
|
10086
|
+
* Properties *
|
|
10087
|
+
* *
|
|
10088
|
+
**************************************************************************/
|
|
10089
|
+
set elderFileSelect(value) {
|
|
10090
|
+
this._filePickerOptions = {
|
|
10091
|
+
excludeAcceptAllOptions: true,
|
|
10092
|
+
id: this._filePickerOptions?.id,
|
|
10093
|
+
multiple: this._filePickerOptions?.multiple,
|
|
10094
|
+
startIn: this._filePickerOptions?.startIn,
|
|
10095
|
+
types: [this.buildTypeOption(value)]
|
|
10096
|
+
};
|
|
10097
|
+
this._legacyInput.elderFileSelect = value;
|
|
10098
|
+
}
|
|
10099
|
+
buildTypeOption(value) {
|
|
10100
|
+
if (this.isFileExtension(value)) {
|
|
10101
|
+
return {
|
|
10102
|
+
accept: {
|
|
10103
|
+
'*/*': value.trim()
|
|
10104
|
+
.replace(/\s+/g, '')
|
|
10105
|
+
.split(',')
|
|
10106
|
+
}
|
|
10107
|
+
};
|
|
10108
|
+
}
|
|
10109
|
+
else if (this.isMimeType(value)) {
|
|
10110
|
+
return {
|
|
10111
|
+
accept: { [value]: [] }
|
|
10112
|
+
};
|
|
10113
|
+
}
|
|
10114
|
+
return null;
|
|
10115
|
+
}
|
|
10116
|
+
isFileExtension(input) {
|
|
10117
|
+
const regex = new RegExp(/^(\.\w+)(, ?\.\w+)*$/);
|
|
10118
|
+
return regex.test(input);
|
|
10119
|
+
}
|
|
10120
|
+
isMimeType(input) {
|
|
10121
|
+
const regex = new RegExp(/^.+\/.+$/);
|
|
10122
|
+
return regex.test(input);
|
|
10123
|
+
}
|
|
10124
|
+
set elderFileSelectMultiple(value) {
|
|
10125
|
+
this._filePickerOptions = {
|
|
10126
|
+
excludeAcceptAllOptions: this._filePickerOptions?.excludeAcceptAllOptions,
|
|
10127
|
+
id: this._filePickerOptions?.id,
|
|
10128
|
+
multiple: coerceBooleanProperty(value),
|
|
10129
|
+
startIn: this._filePickerOptions?.startIn,
|
|
10130
|
+
types: this._filePickerOptions?.types
|
|
10131
|
+
};
|
|
10132
|
+
this._legacyInput.elderFileSelectMultiple = value;
|
|
10133
|
+
}
|
|
10134
|
+
/**
|
|
10135
|
+
* Allow the user to select a directory. All files that are recursively contained are selected.
|
|
10136
|
+
* However, the user will no longer be able to select individual files if this is enabled.
|
|
10137
|
+
* @param value
|
|
10138
|
+
*/
|
|
10139
|
+
set elderFileSelectDirectory(value) {
|
|
10140
|
+
this._useDirectoryPicker = coerceBooleanProperty(value);
|
|
10141
|
+
this._legacyInput.elderFileSelectDirectory = value;
|
|
10142
|
+
}
|
|
10143
|
+
/***************************************************************************
|
|
10144
|
+
* *
|
|
10145
|
+
* Public API *
|
|
10146
|
+
* *
|
|
10147
|
+
**************************************************************************/
|
|
10148
|
+
onClick(event) {
|
|
10149
|
+
if (!this._useDirectoryPicker && FileSystemApi.isFileSystemSupported()) {
|
|
10150
|
+
this.openFilePicker(this._filePickerOptions).subscribe({
|
|
10151
|
+
next: multiFileChange => this.pushFileChanges(multiFileChange)
|
|
10152
|
+
});
|
|
10153
|
+
}
|
|
10154
|
+
else if (this._useDirectoryPicker && FileSystemApi.isDirectoryPickerSupported()) {
|
|
10155
|
+
this.openDirectoryPicker(this._directoryPickerOptions).subscribe({
|
|
10156
|
+
next: multiFileChange => this.pushFileChanges(multiFileChange)
|
|
10157
|
+
});
|
|
10158
|
+
}
|
|
10159
|
+
else {
|
|
10160
|
+
this._legacyInput.openFileSelectDialog();
|
|
10161
|
+
}
|
|
10162
|
+
}
|
|
10163
|
+
/***************************************************************************
|
|
10164
|
+
* *
|
|
10165
|
+
* Private methods *
|
|
10166
|
+
* *
|
|
10167
|
+
**************************************************************************/
|
|
10168
|
+
pushFileChanges(multiFileChange) {
|
|
10169
|
+
this.elderFileSelectChange.next(multiFileChange);
|
|
10170
|
+
const file = multiFileChange[0]?.file;
|
|
10171
|
+
if (file) {
|
|
10172
|
+
this.elderSingleFileSelectChange.next(file);
|
|
10173
|
+
}
|
|
10174
|
+
}
|
|
10175
|
+
openFilePicker(pickerOpts) {
|
|
10176
|
+
return FileSystemApi.openFilePicker(pickerOpts).pipe(switchMap(files => combineLatest(files.map(file => FileEntry.ofFileHandle(file)))));
|
|
10177
|
+
}
|
|
10178
|
+
openDirectoryPicker(pickerOpts) {
|
|
10179
|
+
return FileSystemApi.openDirectoryPicker(pickerOpts).pipe(switchMap(dir => FileListingSystemHandle.listFiles(dir)));
|
|
10180
|
+
}
|
|
10181
|
+
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
10182
|
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
10183
|
}
|
|
9935
10184
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: ElderFileSelectDirective, decorators: [{
|
|
@@ -9938,7 +10187,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImpor
|
|
|
9938
10187
|
selector: '[elderFileSelect]',
|
|
9939
10188
|
standalone: true
|
|
9940
10189
|
}]
|
|
9941
|
-
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.
|
|
10190
|
+
}], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i0.DestroyRef }], propDecorators: { elderFileSelectChange: [{
|
|
9942
10191
|
type: Output
|
|
9943
10192
|
}], elderSingleFileSelectChange: [{
|
|
9944
10193
|
type: Output
|
|
@@ -10058,70 +10307,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImpor
|
|
|
10058
10307
|
type: Input
|
|
10059
10308
|
}] } });
|
|
10060
10309
|
|
|
10061
|
-
class
|
|
10310
|
+
class FileListingWebkit {
|
|
10311
|
+
/***************************************************************************
|
|
10312
|
+
* *
|
|
10313
|
+
* Constructor *
|
|
10314
|
+
* *
|
|
10315
|
+
**************************************************************************/
|
|
10062
10316
|
constructor() {
|
|
10063
|
-
this.log = LoggerFactory.getLogger(this.constructor.name);
|
|
10064
10317
|
}
|
|
10065
|
-
static { this.INSTANCE = new FileListingRx(); }
|
|
10066
10318
|
/***************************************************************************
|
|
10067
10319
|
* *
|
|
10068
|
-
* Public
|
|
10320
|
+
* Public methods *
|
|
10069
10321
|
* *
|
|
10070
10322
|
**************************************************************************/
|
|
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);
|
|
10323
|
+
static listFilesRecursive(entry) {
|
|
10324
|
+
return FileListingWebkit.listFilesRecursiveAsync(entry, null);
|
|
10102
10325
|
}
|
|
10103
10326
|
/***************************************************************************
|
|
10104
10327
|
* *
|
|
10105
10328
|
* Private methods *
|
|
10106
10329
|
* *
|
|
10107
10330
|
**************************************************************************/
|
|
10108
|
-
listFilesRecursiveAsync(entry, parent) {
|
|
10331
|
+
static listFilesRecursiveAsync(entry, parent) {
|
|
10109
10332
|
if (entry.isDirectory) {
|
|
10110
10333
|
const directoryEntry = entry;
|
|
10111
|
-
return
|
|
10334
|
+
return FileListingWebkit.readEntries(directoryEntry).pipe(mergeMap$1(entries => zip(entries.map(entry => this.listFilesRecursiveAsync(entry, directoryEntry)))), map(files => files.flat()));
|
|
10112
10335
|
}
|
|
10113
10336
|
else if (entry.isFile) {
|
|
10114
10337
|
const fileEntry = entry;
|
|
10115
|
-
return
|
|
10338
|
+
return FileListingWebkit.observableFile(fileEntry, parent).pipe(map(file => [file]));
|
|
10116
10339
|
}
|
|
10117
10340
|
else {
|
|
10118
10341
|
return EMPTY;
|
|
10119
10342
|
}
|
|
10120
10343
|
}
|
|
10121
|
-
observableFile(fileEntry, parent) {
|
|
10344
|
+
static observableFile(fileEntry, parent) {
|
|
10122
10345
|
return new Observable(observer => {
|
|
10123
10346
|
fileEntry.file(file => {
|
|
10124
|
-
observer.next(FileEntry.relativeFile(file,
|
|
10347
|
+
observer.next(FileEntry.relativeFile(file, FileListingWebkit.trimStaringSlash(parent?.fullPath)));
|
|
10125
10348
|
observer.complete();
|
|
10126
10349
|
}, err => {
|
|
10127
10350
|
observer.error(err);
|
|
@@ -10129,7 +10352,7 @@ class FileListingRx {
|
|
|
10129
10352
|
});
|
|
10130
10353
|
});
|
|
10131
10354
|
}
|
|
10132
|
-
trimStaringSlash(path) {
|
|
10355
|
+
static trimStaringSlash(path) {
|
|
10133
10356
|
if (path) {
|
|
10134
10357
|
if (path.startsWith('/')) {
|
|
10135
10358
|
path = path.substring(1);
|
|
@@ -10137,12 +10360,12 @@ class FileListingRx {
|
|
|
10137
10360
|
}
|
|
10138
10361
|
return path;
|
|
10139
10362
|
}
|
|
10140
|
-
readEntries(directoryEntry) {
|
|
10363
|
+
static readEntries(directoryEntry) {
|
|
10141
10364
|
return new Observable(observer => {
|
|
10142
|
-
|
|
10365
|
+
FileListingWebkit.consumeReaderToCompletion(observer, directoryEntry.createReader());
|
|
10143
10366
|
});
|
|
10144
10367
|
}
|
|
10145
|
-
consumeReaderToCompletion(observer, directoryReader) {
|
|
10368
|
+
static consumeReaderToCompletion(observer, directoryReader) {
|
|
10146
10369
|
directoryReader.readEntries(entries => {
|
|
10147
10370
|
if (entries && entries.length > 0) {
|
|
10148
10371
|
observer.next(entries);
|
|
@@ -10158,6 +10381,63 @@ class FileListingRx {
|
|
|
10158
10381
|
}
|
|
10159
10382
|
}
|
|
10160
10383
|
|
|
10384
|
+
class FileListingRx {
|
|
10385
|
+
constructor() {
|
|
10386
|
+
this.log = LoggerFactory.getLogger(this.constructor.name);
|
|
10387
|
+
}
|
|
10388
|
+
static { this.INSTANCE = new FileListingRx(); }
|
|
10389
|
+
/***************************************************************************
|
|
10390
|
+
* *
|
|
10391
|
+
* Public API *
|
|
10392
|
+
* *
|
|
10393
|
+
**************************************************************************/
|
|
10394
|
+
toFileList(transferItemList) {
|
|
10395
|
+
const obs = [];
|
|
10396
|
+
for (let i = 0; i < transferItemList.length; i++) {
|
|
10397
|
+
const transferItem = transferItemList[i];
|
|
10398
|
+
obs.push(this.resolveTransferItem(transferItem));
|
|
10399
|
+
}
|
|
10400
|
+
return zip(obs).pipe(map(files => files.flat()));
|
|
10401
|
+
}
|
|
10402
|
+
/***************************************************************************
|
|
10403
|
+
* *
|
|
10404
|
+
* Private methods *
|
|
10405
|
+
* *
|
|
10406
|
+
**************************************************************************/
|
|
10407
|
+
resolveTransferItem(transferItem) {
|
|
10408
|
+
if (FileSystemApi.isGetAsFileSystemHandleSupported(transferItem)) {
|
|
10409
|
+
return FileSystemApi.getAsFileSystemHandle(transferItem).pipe(switchMap(handle => handle ? FileListingSystemHandle.listFiles(handle) : this.legacyFileListing(transferItem)));
|
|
10410
|
+
}
|
|
10411
|
+
return this.legacyFileListing(transferItem);
|
|
10412
|
+
}
|
|
10413
|
+
legacyFileListing(transferItem) {
|
|
10414
|
+
const entry = transferItem.webkitGetAsEntry();
|
|
10415
|
+
if (entry) {
|
|
10416
|
+
return FileListingWebkit.listFilesRecursive(entry);
|
|
10417
|
+
}
|
|
10418
|
+
return this.fallbackFileListing(transferItem);
|
|
10419
|
+
}
|
|
10420
|
+
fallbackFileListing(transferItem) {
|
|
10421
|
+
const itemAsFile = transferItem.getAsFile();
|
|
10422
|
+
if (itemAsFile) {
|
|
10423
|
+
return of([FileEntry.ofFile(itemAsFile)]);
|
|
10424
|
+
}
|
|
10425
|
+
else {
|
|
10426
|
+
if (transferItem.kind == 'file') {
|
|
10427
|
+
return of([
|
|
10428
|
+
FileEntry.ofFile(new File([], '', {
|
|
10429
|
+
type: transferItem.type
|
|
10430
|
+
}))
|
|
10431
|
+
]);
|
|
10432
|
+
}
|
|
10433
|
+
else {
|
|
10434
|
+
this.log.warn('Could not handle DataTransferItem!', transferItem);
|
|
10435
|
+
return of([]);
|
|
10436
|
+
}
|
|
10437
|
+
}
|
|
10438
|
+
}
|
|
10439
|
+
}
|
|
10440
|
+
|
|
10161
10441
|
class ElderFileDropZoneDirective {
|
|
10162
10442
|
/***************************************************************************
|
|
10163
10443
|
* *
|