@sumaris-net/ngx-components 1.19.2 → 1.19.5-rc1

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.
@@ -17888,6 +17888,252 @@
17888
17888
  ]; };
17889
17889
  var templateObject_1$1, templateObject_2$1, templateObject_3$1, templateObject_4$1, templateObject_5$1;
17890
17890
 
17891
+ var UploadFile = /** @class */ (function (_super) {
17892
+ __extends(UploadFile, _super);
17893
+ function UploadFile() {
17894
+ return _super !== null && _super.apply(this, arguments) || this;
17895
+ }
17896
+ return UploadFile;
17897
+ }(File));
17898
+ var FileResponse = /** @class */ (function () {
17899
+ function FileResponse(init) {
17900
+ this.type = i2$1.HttpEventType.Response;
17901
+ this.body = isNotNil(init === null || init === void 0 ? void 0 : init.body) ? init.body : null;
17902
+ this.status = (init === null || init === void 0 ? void 0 : init.status) || 0;
17903
+ this.statusText = (init === null || init === void 0 ? void 0 : init.statusText) || 'OK';
17904
+ }
17905
+ return FileResponse;
17906
+ }());
17907
+ function isProgressEvent(event) {
17908
+ return event && (event.type === i2$1.HttpEventType.UploadProgress
17909
+ || event.type === i2$1.HttpEventType.DownloadProgress);
17910
+ }
17911
+ function isResponseEvent(event) {
17912
+ return event && event.type === i2$1.HttpEventType.Response;
17913
+ }
17914
+
17915
+ var UploadFilePopover = /** @class */ (function () {
17916
+ function UploadFilePopover(popoverController, translate, cd) {
17917
+ this.popoverController = popoverController;
17918
+ this.translate = translate;
17919
+ this.cd = cd;
17920
+ this.uniqueFile = false;
17921
+ this.instantUpload = false;
17922
+ this.importing = false;
17923
+ }
17924
+ Object.defineProperty(UploadFilePopover.prototype, "files", {
17925
+ get: function () {
17926
+ return this.uploader.files;
17927
+ },
17928
+ enumerable: false,
17929
+ configurable: true
17930
+ });
17931
+ Object.defineProperty(UploadFilePopover.prototype, "disabled", {
17932
+ get: function () {
17933
+ return this.importing || isEmptyArray(this.files);
17934
+ },
17935
+ enumerable: false,
17936
+ configurable: true
17937
+ });
17938
+ Object.defineProperty(UploadFilePopover.prototype, "valid", {
17939
+ get: function () {
17940
+ return isNotEmptyArray(this.files);
17941
+ },
17942
+ enumerable: false,
17943
+ configurable: true
17944
+ });
17945
+ UploadFilePopover.prototype.onValidate = function (event) {
17946
+ return __awaiter(this, void 0, void 0, function () {
17947
+ var files, errors, err_1;
17948
+ return __generator(this, function (_a) {
17949
+ switch (_a.label) {
17950
+ case 0:
17951
+ event === null || event === void 0 ? void 0 : event.stopPropagation();
17952
+ // Avoid multiple call
17953
+ if (this.importing)
17954
+ return [2 /*return*/];
17955
+ console.debug('[upload-file-popover] Validate form: will upload all files...');
17956
+ _a.label = 1;
17957
+ case 1:
17958
+ _a.trys.push([1, , 8, 9]);
17959
+ this.importing = true;
17960
+ this.resetError();
17961
+ _a.label = 2;
17962
+ case 2:
17963
+ _a.trys.push([2, 6, , 7]);
17964
+ // Wait processing files finished
17965
+ return [4 /*yield*/, this.uploader.waitIdle()];
17966
+ case 3:
17967
+ // Wait processing files finished
17968
+ _a.sent();
17969
+ // upload all files
17970
+ return [4 /*yield*/, this.uploader.uploadFiles()];
17971
+ case 4:
17972
+ // upload all files
17973
+ _a.sent();
17974
+ files = (this.uploader.processedFiles || [])
17975
+ .filter(function (file) { return !file.deleting; });
17976
+ // Nothing to upload: close popover
17977
+ if (isEmptyArray(files))
17978
+ return [2 /*return*/, this.cancel()];
17979
+ errors = files.map(function (file) { return file.error; }).filter(isNotNilOrBlank);
17980
+ // If error: stop
17981
+ if (errors.length) {
17982
+ errors.forEach(function (error) { return console.error(error); });
17983
+ throw { message: 'FILE.UPLOAD.ERROR' };
17984
+ }
17985
+ // return files
17986
+ return [4 /*yield*/, this.popoverController.dismiss(files)];
17987
+ case 5:
17988
+ // return files
17989
+ _a.sent();
17990
+ return [3 /*break*/, 7];
17991
+ case 6:
17992
+ err_1 = _a.sent();
17993
+ this.error = (err_1 && err_1.message) || err_1;
17994
+ return [3 /*break*/, 7];
17995
+ case 7: return [3 /*break*/, 9];
17996
+ case 8:
17997
+ this.importing = false;
17998
+ this.cd.markForCheck();
17999
+ return [7 /*endfinally*/];
18000
+ case 9: return [2 /*return*/];
18001
+ }
18002
+ });
18003
+ });
18004
+ };
18005
+ UploadFilePopover.prototype.cancel = function () {
18006
+ return this.popoverController.dismiss();
18007
+ };
18008
+ UploadFilePopover.prototype.resetError = function () {
18009
+ if (this.error) {
18010
+ this.error = null;
18011
+ this.cd.markForCheck();
18012
+ }
18013
+ };
18014
+ return UploadFilePopover;
18015
+ }());
18016
+ UploadFilePopover.decorators = [
18017
+ { type: i0.Component, args: [{
18018
+ selector: 'app-upload-file-popover',
18019
+ template: "\n<ion-content class=\"ion-no-padding\" scrollY=\"false\">\n <div class=\"modal-content\">\n <app-upload-file\n #uploader\n [uploadFn]=\"uploadFn\"\n [deleteFn]=\"deleteFn\"\n [fileExtension]=\"fileExtension\"\n [instantUpload]=\"instantUpload\"\n [uniqueFile]=\"uniqueFile\"\n [maxParallelUpload]=\"maxParallelUpload\"\n ></app-upload-file>\n </div>\n</ion-content>\n\n<ion-footer>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col>\n <div *ngIf=\"importing\" class=\"importing-info\">\n <ion-text color=\"medium\"><small translate>FILE.UPLOAD.IMPORTING</small></ion-text>\n &nbsp;<ion-spinner name=\"dots\" color=\"accent\"></ion-spinner>\n </div>\n <ion-item *ngIf=\"error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"ion-text-wrap\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n </ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"cancel()\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n <ion-button\n [fill]=\"disabled ? 'clear' : 'solid'\"\n (click)=\"onValidate($event)\"\n (keyup.enter)=\"onValidate($event)\"\n color=\"tertiary\"\n [disabled]=\"disabled\"\n >\n <ion-label translate>COMMON.BTN_IMPORT</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n",
18020
+ encapsulation: i0.ViewEncapsulation.None,
18021
+ changeDetection: i0.ChangeDetectionStrategy.OnPush,
18022
+ styles: [".importing-info{-webkit-padding-start:var(--ion-padding);padding-inline-start:var(--ion-padding);display:inline-flex;flex-wrap:nowrap;padding-top:8px}"]
18023
+ },] }
18024
+ ];
18025
+ UploadFilePopover.ctorParameters = function () { return [
18026
+ { type: i1$5.PopoverController },
18027
+ { type: i1$2.TranslateService },
18028
+ { type: i0.ChangeDetectorRef }
18029
+ ]; };
18030
+ UploadFilePopover.propDecorators = {
18031
+ uploader: [{ type: i0.ViewChild, args: ['uploader', { static: true },] }],
18032
+ fileExtension: [{ type: i0.Input }],
18033
+ title: [{ type: i0.Input }],
18034
+ uniqueFile: [{ type: i0.Input }],
18035
+ instantUpload: [{ type: i0.Input }],
18036
+ uploadFn: [{ type: i0.Input }],
18037
+ deleteFn: [{ type: i0.Input }],
18038
+ maxParallelUpload: [{ type: i0.Input }]
18039
+ };
18040
+
18041
+ var MimeTypes = Object.freeze({
18042
+ ANDROID_APK: 'application/vnd.android.package-archive'
18043
+ });
18044
+ var FilesUtils = /** @class */ (function () {
18045
+ function FilesUtils() {
18046
+ }
18047
+ FilesUtils.showUploadPopover = function (popoverController, event, opts) {
18048
+ return __awaiter(this, void 0, void 0, function () {
18049
+ var modal, _a, data, role;
18050
+ return __generator(this, function (_b) {
18051
+ switch (_b.label) {
18052
+ case 0: return [4 /*yield*/, popoverController.create({
18053
+ component: UploadFilePopover,
18054
+ componentProps: opts,
18055
+ backdropDismiss: false,
18056
+ keyboardClose: false,
18057
+ event: event,
18058
+ translucent: true,
18059
+ cssClass: 'popover-large',
18060
+ })];
18061
+ case 1:
18062
+ modal = _b.sent();
18063
+ return [4 /*yield*/, modal.present()];
18064
+ case 2:
18065
+ _b.sent();
18066
+ return [4 /*yield*/, modal.onDidDismiss()];
18067
+ case 3:
18068
+ _a = _b.sent(), data = _a.data, role = _a.role;
18069
+ return [2 /*return*/, {
18070
+ role: role,
18071
+ data: data ? data : undefined
18072
+ }];
18073
+ }
18074
+ });
18075
+ });
18076
+ };
18077
+ FilesUtils.readAsText = function (file, encoding) {
18078
+ var $progress = new rxjs.Subject();
18079
+ var reader = new FileReader();
18080
+ encoding = (encoding || 'UTF-8').toLowerCase();
18081
+ // Listen progress
18082
+ reader.onprogress = function (e) {
18083
+ var total = e.total || 1;
18084
+ var loaded = e.total || 0;
18085
+ if (loaded < total)
18086
+ $progress.next({ type: i2$1.HttpEventType.UploadProgress, loaded: loaded, total: total });
18087
+ };
18088
+ // Listen end
18089
+ reader.onloadend = function (e) {
18090
+ var body = reader.result;
18091
+ if (typeof body === 'string') {
18092
+ $progress.next(new FileResponse({ body: body }));
18093
+ $progress.complete();
18094
+ }
18095
+ else {
18096
+ console.error("Cannot read file '" + file.name + "' as text");
18097
+ $progress.error("Cannot read file as text");
18098
+ }
18099
+ };
18100
+ // Listen error
18101
+ reader.onerror = function (event) {
18102
+ console.error('[files] Error while reading file:', event);
18103
+ $progress.error(event);
18104
+ };
18105
+ // Start reading the file
18106
+ reader.readAsText(file, encoding);
18107
+ return $progress.asObservable();
18108
+ };
18109
+ return FilesUtils;
18110
+ }());
18111
+ var UriUtils = /** @class */ (function () {
18112
+ function UriUtils() {
18113
+ }
18114
+ /**
18115
+ * Extract the filename, from an uri (using the last slash)
18116
+ * @param uri
18117
+ */
18118
+ UriUtils.getFilename = function (uri) {
18119
+ if (!uri)
18120
+ return uri;
18121
+ var filename = uri.trim();
18122
+ // Get last part (or all string, if no '/')
18123
+ var lastSlashIndex = filename.lastIndexOf('/');
18124
+ if (lastSlashIndex !== -1 && lastSlashIndex !== uri.length - 1) {
18125
+ filename = filename.substring(lastSlashIndex + 1);
18126
+ }
18127
+ // Remove query params
18128
+ var queryParamIndex = filename.indexOf('?');
18129
+ if (queryParamIndex !== -1) {
18130
+ filename = filename.substring(0, queryParamIndex);
18131
+ }
18132
+ return filename;
18133
+ };
18134
+ return UriUtils;
18135
+ }());
18136
+
17891
18137
  var moment$4 = momentImported__namespace;
17892
18138
  var AndroidOsEnvironment = Object.freeze({
17893
18139
  DIRECTORY_DOWNLOADS: 'Download',
@@ -18083,34 +18329,48 @@
18083
18329
  };
18084
18330
  PlatformService.prototype.download = function (request) {
18085
18331
  var _this = this;
18332
+ var _a, _b;
18086
18333
  if (!request || !request.uri)
18087
18334
  throw new Error('Missing argument \'request\' or \'request.uri\'');
18088
18335
  if (this._android && this.downloader) {
18089
18336
  // Check if not already downloading file
18090
- var location$ = this._downloadingJobs.get(request.uri);
18091
- if (location$)
18092
- return location$;
18093
- request = Object.assign(Object.assign({ visibleInDownloadsUi: true, notificationVisibility: ngx$6.NotificationVisibility.VisibleNotifyCompleted, title: request.title || request.filename }, request), { destinationInExternalPublicDir: Object.assign({ dirType: AndroidOsEnvironment.DIRECTORY_DOWNLOADS, subPath: request.filename || request.title || '' }, request.destinationInExternalPublicDir) });
18094
- console.debug('[platform] Downloading, using request: ' + JSON.stringify(request));
18337
+ var job$ = this._downloadingJobs.get(request.uri);
18338
+ if (job$)
18339
+ return job$;
18340
+ // Compute subPath (final filename)
18341
+ var subPath = ((_a = request.destinationInExternalPublicDir) === null || _a === void 0 ? void 0 : _a.subPath) || ((_b = request.destinationInExternalFilesDir) === null || _b === void 0 ? void 0 : _b.subPath)
18342
+ || request.filename || UriUtils.getFilename(request.uri)
18343
+ || request.title || 'download';
18344
+ subPath = subPath.toLowerCase()
18345
+ // Remove spaces
18346
+ .replace(/\s+/g, '-');
18347
+ // Force APK to finish with '.apk'
18348
+ if (request.mimeType === MimeTypes.ANDROID_APK && !subPath.toLowerCase().endsWith('.apk')) {
18349
+ subPath += '.apk';
18350
+ }
18351
+ var downloadRequest_1 = Object.assign(Object.assign({ visibleInDownloadsUi: true, notificationVisibility: ngx$6.NotificationVisibility.VisibleNotifyCompleted }, request), { filename: undefined, destinationInExternalPublicDir: !request.destinationInExternalFilesDir
18352
+ ? Object.assign(Object.assign({ dirType: AndroidOsEnvironment.DIRECTORY_DOWNLOADS }, request.destinationInExternalPublicDir), { subPath: subPath }) : undefined, destinationInExternalFilesDir: request.destinationInExternalFilesDir
18353
+ ? Object.assign(Object.assign({ dirType: AndroidOsEnvironment.DIRECTORY_DOWNLOADS }, request.destinationInExternalFilesDir), { subPath: subPath }) : undefined });
18095
18354
  // Start download
18096
- location$ = rxjs.from(this.downloader.download(request));
18355
+ console.debug('[platform] Downloading, using request: ' + JSON.stringify(downloadRequest_1));
18356
+ job$ = rxjs.from(this.downloader.download(downloadRequest_1));
18097
18357
  // Remember this request uri
18098
- this._downloadingJobs.set(request.uri, location$);
18099
- return location$
18358
+ this._downloadingJobs.set(downloadRequest_1.uri, job$);
18359
+ return job$
18100
18360
  .pipe(operators.tap(function (location) {
18101
18361
  console.info('[platform] File successfully downloaded at:' + location);
18102
- // Forget the request
18103
- _this._downloadingJobs.delete(request.uri);
18362
+ // Forget job
18363
+ _this._downloadingJobs.delete(downloadRequest_1.uri);
18104
18364
  return location;
18105
18365
  }), operators.catchError(function (err) {
18106
18366
  var _a;
18107
18367
  console.error('[platform] Unable to download: ' + (err && err.message || err));
18108
18368
  // Forget the request
18109
- _this._downloadingJobs.delete(request.uri);
18369
+ _this._downloadingJobs.delete(downloadRequest_1.uri);
18110
18370
  // Retry with another location - issue in older Android version
18111
- if (((_a = request.destinationInExternalPublicDir) === null || _a === void 0 ? void 0 : _a.dirType) === AndroidOsEnvironment.DIRECTORY_DOWNLOADS) {
18112
- request.destinationInExternalPublicDir.dirType = AndroidOsEnvironment.DIRECTORY_DOWNLOADS_OLD;
18113
- return _this.download(request);
18371
+ if (((_a = downloadRequest_1.destinationInExternalPublicDir) === null || _a === void 0 ? void 0 : _a.dirType) === AndroidOsEnvironment.DIRECTORY_DOWNLOADS) {
18372
+ downloadRequest_1.destinationInExternalPublicDir.dirType = AndroidOsEnvironment.DIRECTORY_DOWNLOADS_OLD;
18373
+ return _this.download(downloadRequest_1);
18114
18374
  }
18115
18375
  throw err;
18116
18376
  }));
@@ -18526,30 +18786,6 @@
18526
18786
  { type: i1$2.TranslateService }
18527
18787
  ]; };
18528
18788
 
18529
- var UploadFile = /** @class */ (function (_super) {
18530
- __extends(UploadFile, _super);
18531
- function UploadFile() {
18532
- return _super !== null && _super.apply(this, arguments) || this;
18533
- }
18534
- return UploadFile;
18535
- }(File));
18536
- var FileResponse = /** @class */ (function () {
18537
- function FileResponse(init) {
18538
- this.type = i2$1.HttpEventType.Response;
18539
- this.body = isNotNil(init === null || init === void 0 ? void 0 : init.body) ? init.body : null;
18540
- this.status = (init === null || init === void 0 ? void 0 : init.status) || 0;
18541
- this.statusText = (init === null || init === void 0 ? void 0 : init.statusText) || 'OK';
18542
- }
18543
- return FileResponse;
18544
- }());
18545
- function isProgressEvent(event) {
18546
- return event && (event.type === i2$1.HttpEventType.UploadProgress
18547
- || event.type === i2$1.HttpEventType.DownloadProgress);
18548
- }
18549
- function isResponseEvent(event) {
18550
- return event && event.type === i2$1.HttpEventType.Response;
18551
- }
18552
-
18553
18789
  var UploadFileComponent = /** @class */ (function () {
18554
18790
  function UploadFileComponent(cd, translate) {
18555
18791
  this.cd = cd;
@@ -18668,235 +18904,109 @@
18668
18904
  // Start upload
18669
18905
  if (this.instantUpload) {
18670
18906
  this.uploadFiles(items);
18671
- }
18672
- };
18673
- /**
18674
- * Execute upload
18675
- */
18676
- UploadFileComponent.prototype.uploadFiles = function (files) {
18677
- return __awaiter(this, void 0, void 0, function () {
18678
- var $processingFileCountChange, $changes, jobs;
18679
- var _this = this;
18680
- return __generator(this, function (_b) {
18681
- if (!files) {
18682
- console.info('[upload-file] Uploading all files...');
18683
- files = this.files;
18684
- }
18685
- $processingFileCountChange = new rxjs.BehaviorSubject(this.processingFilesCount);
18686
- $changes = new rxjs.Subject();
18687
- $changes.subscribe(function (_) {
18688
- _this.cd.markForCheck();
18689
- $processingFileCountChange.next(_this.processingFilesCount);
18690
- });
18691
- jobs = files
18692
- .map(function (f) { return f; })
18693
- .filter(function (file) { return file && isNil(file.progress) && !file.error; }) // Skip if already started or in error
18694
- .map(function (file, i) {
18695
- // Wait if need
18696
- if (isNotNil(_this.maxParallelUpload)) {
18697
- return $processingFileCountChange.pipe(operators.debounceTime(i), // call of filter() must be different for waiting job
18698
- operators.filter(function (count) { return count < _this.maxParallelUpload; }), operators.map(function (_) {
18699
- // Start uploading
18700
- if (isNil(file.progress))
18701
- file.progress = 0;
18702
- $changes.next();
18703
- return file;
18704
- }), operators.first());
18705
- }
18706
- // Start uploading
18707
- if (isNil(file.progress))
18708
- file.progress = 0;
18709
- return rxjs.of(file);
18710
- })
18711
- .map(function (file$) { return file$.pipe(operators.filter(isNotNil),
18712
- // Log
18713
- operators.tap(function (file) { return console.debug("[upload-file] " + file.name + ": uploading..."); }),
18714
- // Start uploading
18715
- operators.switchMap(function (file) { return _this.uploadFn(file)
18716
- .pipe(operators.filter(isNotNil), operators.map(function (event) {
18717
- // Progress event
18718
- if (isProgressEvent(event)) {
18719
- if (isNotNil(event.loaded) && event.loaded >= 0) {
18720
- file.progress = Math.min(1, event.loaded / (event.total || 1));
18721
- }
18722
- else {
18723
- file.progress = -1; // Indeterminate progression
18724
- }
18725
- }
18726
- // Response event
18727
- else if (isResponseEvent(event)) {
18728
- file.progress = 1;
18729
- file.response = event;
18730
- console.debug("[upload-file] " + file.name + ": " + (event.statusText || 'OK'), file.response);
18731
- }
18732
- // Other events: ignore
18733
- else {
18734
- // DEBUG
18735
- console.debug('[upload-file] Ignoring a file event: ', event);
18736
- }
18737
- return file;
18738
- }), operators.catchError(function (err) {
18739
- var _a;
18740
- console.error(err);
18741
- file.error = err && (err.message || ((_a = err.error) === null || _a === void 0 ? void 0 : _a.message)) || 'FILE.UPLOAD.ERROR';
18742
- return rxjs.of(file);
18743
- }), operators.tap(function () {
18744
- $changes.next();
18745
- _this.cd.markForCheck();
18746
- })); })); });
18747
- // Wait jobs to finish
18748
- return [2 /*return*/, rxjs.forkJoin(jobs)
18749
- .pipe(operators.tap(function (_) { return $changes.complete(); }))
18750
- .toPromise()];
18751
- });
18752
- });
18753
- };
18754
- UploadFileComponent.prototype.waitIdle = function (opts) {
18755
- var _this = this;
18756
- return waitFor(function () { return _this.processingFilesCount === 0; }, opts);
18757
- };
18758
- return UploadFileComponent;
18759
- }());
18760
- UploadFileComponent.decorators = [
18761
- { type: i0.Component, args: [{
18762
- selector: 'app-upload-file',
18763
- template: "<div *ngIf=\"!uniqueFile || files.length === 0\"\n class=\"container\"\n appDragAndDrop (fileDropped)=\"onFileDropped($event)\">\n <input\n type=\"file\"\n #fileDropRef\n id=\"fileDropRef\"\n multiple\n [accept]=\"fileExtension\"\n (change)=\"fileBrowseHandler($any($event.target).files)\"\n />\n <mat-icon style=\"font-size: xx-large\" color=\"accent\">file_upload</mat-icon>\n <ion-label\n [innerHTML]=\"\n 'FILE.UPLOAD.DRAG_AND_DROP' | translate: { extension: !fileExtension ? '' : ' (' + fileExtension + ')' }\n \"\n ></ion-label>\n <ion-button>{{ 'FILE.UPLOAD.BROWSE' | translate }}</ion-button>\n</div>\n<div class=\"files-list\">\n <div *ngFor=\"let file of files; index as i\"\n class=\"single-file\"\n [class.deleting]=\"file.deleting\">\n <mat-icon>description</mat-icon>\n <div class=\"info\">\n <h4 class=\"name\">\n {{ file.name }}\n </h4>\n <p class=\"size\">\n {{ file.size | fileSize }}\n </p>\n <ion-progress-bar *ngIf=\"!file.error\"\n [value]=\"file.progress||0\"\n [type]=\"(file.deleting || file.progress === -1) ? 'indeterminate' : 'determinate'\"></ion-progress-bar>\n <ion-label *ngIf=\"file.error\" color=\"danger\" [innerHTML]=\"file.error | translate\"></ion-label>\n </div>\n <!-- remote from list (before importation) -->\n <button *ngIf=\"!(file.progress) || file.error\" mat-icon-button (click)=\"deleteFile(i)\">\n <mat-icon>clear</mat-icon>\n </button>\n <!-- delete file (remotely - after importation) -->\n <button *ngIf=\"file.progress === 1 && !!deleteFn && !file.deleting\" mat-icon-button (click)=\"deleteFile(i)\">\n <mat-icon>clear</mat-icon>\n </button>\n </div>\n</div>\n",
18764
- styles: [".container{width:calc(100% - 3rem);min-height:200px;padding:2rem;text-align:center;border:1px dashed #979797;position:relative;margin:1.5rem}.container input{opacity:0;position:absolute;z-index:2;width:100%;height:100%;top:0;left:0;cursor:pointer}.container ion-label{display:block;font-size:20px;font-weight:600;color:#38424c}.container label{display:inline-block;color:#fff;width:183px;height:44px;border-radius:21.5px;background-color:#db202f;padding:8px 16px}.fileover{-webkit-animation:shake 1s;animation:shake 1s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.files-list{max-height:300px;overflow:auto}.files-list .single-file{display:flex;flex-grow:1;padding:.5rem;justify-content:space-between;align-items:center;border:1px dashed #979797;margin-bottom:1rem}.files-list .single-file.deleting .name{color:grey!important;font-style:italic!important}.files-list .single-file .name{font-size:14px;font-weight:500;color:#353f4a;margin:0}.files-list .single-file .size{font-size:12px;font-weight:500;color:#a4a4a4;margin:0 0 .25rem}.files-list .single-file .info{width:100%}@-webkit-keyframes shake{0%{transform:translate(1px,1px) rotate(0deg)}10%{transform:translate(-1px,-2px) rotate(-1deg)}20%{transform:translate(-3px) rotate(1deg)}30%{transform:translate(3px,2px) rotate(0deg)}40%{transform:translate(1px,-1px) rotate(1deg)}50%{transform:translate(-1px,2px) rotate(-1deg)}60%{transform:translate(-3px,1px) rotate(0deg)}70%{transform:translate(3px,1px) rotate(-1deg)}80%{transform:translate(-1px,-1px) rotate(1deg)}90%{transform:translate(1px,2px) rotate(0deg)}to{transform:translate(1px,-2px) rotate(-1deg)}}@keyframes shake{0%{transform:translate(1px,1px) rotate(0deg)}10%{transform:translate(-1px,-2px) rotate(-1deg)}20%{transform:translate(-3px) rotate(1deg)}30%{transform:translate(3px,2px) rotate(0deg)}40%{transform:translate(1px,-1px) rotate(1deg)}50%{transform:translate(-1px,2px) rotate(-1deg)}60%{transform:translate(-3px,1px) rotate(0deg)}70%{transform:translate(3px,1px) rotate(-1deg)}80%{transform:translate(-1px,-1px) rotate(1deg)}90%{transform:translate(1px,2px) rotate(0deg)}to{transform:translate(1px,-2px) rotate(-1deg)}}"]
18765
- },] }
18766
- ];
18767
- UploadFileComponent.ctorParameters = function () { return [
18768
- { type: i0.ChangeDetectorRef },
18769
- { type: i1$2.TranslateService }
18770
- ]; };
18771
- UploadFileComponent.propDecorators = {
18772
- fileDropEl: [{ type: i0.ViewChild, args: ['fileDropRef', { static: false },] }],
18773
- fileExtension: [{ type: i0.Input }],
18774
- uniqueFile: [{ type: i0.Input }],
18775
- instantUpload: [{ type: i0.Input }],
18776
- uploadFn: [{ type: i0.Input }],
18777
- deleteFn: [{ type: i0.Input }],
18778
- maxParallelUpload: [{ type: i0.Input }]
18779
- };
18780
-
18781
- var UploadFilePopover = /** @class */ (function () {
18782
- function UploadFilePopover(popoverController, translate, cd) {
18783
- this.popoverController = popoverController;
18784
- this.translate = translate;
18785
- this.cd = cd;
18786
- this.uniqueFile = false;
18787
- this.instantUpload = false;
18788
- this.importing = false;
18789
- }
18790
- Object.defineProperty(UploadFilePopover.prototype, "files", {
18791
- get: function () {
18792
- return this.uploader.files;
18793
- },
18794
- enumerable: false,
18795
- configurable: true
18796
- });
18797
- Object.defineProperty(UploadFilePopover.prototype, "disabled", {
18798
- get: function () {
18799
- return this.importing || isEmptyArray(this.files);
18800
- },
18801
- enumerable: false,
18802
- configurable: true
18803
- });
18804
- Object.defineProperty(UploadFilePopover.prototype, "valid", {
18805
- get: function () {
18806
- return isNotEmptyArray(this.files);
18807
- },
18808
- enumerable: false,
18809
- configurable: true
18810
- });
18811
- UploadFilePopover.prototype.onValidate = function (event) {
18812
- return __awaiter(this, void 0, void 0, function () {
18813
- var files, errors, err_1;
18814
- return __generator(this, function (_a) {
18815
- switch (_a.label) {
18816
- case 0:
18817
- event === null || event === void 0 ? void 0 : event.stopPropagation();
18818
- // Avoid multiple call
18819
- if (this.importing)
18820
- return [2 /*return*/];
18821
- console.debug('[upload-file-popover] Validate form: will upload all files...');
18822
- _a.label = 1;
18823
- case 1:
18824
- _a.trys.push([1, , 8, 9]);
18825
- this.importing = true;
18826
- this.resetError();
18827
- _a.label = 2;
18828
- case 2:
18829
- _a.trys.push([2, 6, , 7]);
18830
- // Wait processing files finished
18831
- return [4 /*yield*/, this.uploader.waitIdle()];
18832
- case 3:
18833
- // Wait processing files finished
18834
- _a.sent();
18835
- // upload all files
18836
- return [4 /*yield*/, this.uploader.uploadFiles()];
18837
- case 4:
18838
- // upload all files
18839
- _a.sent();
18840
- files = (this.uploader.processedFiles || [])
18841
- .filter(function (file) { return !file.deleting; });
18842
- // Nothing to upload: close popover
18843
- if (isEmptyArray(files))
18844
- return [2 /*return*/, this.cancel()];
18845
- errors = files.map(function (file) { return file.error; }).filter(isNotNilOrBlank);
18846
- // If error: stop
18847
- if (errors.length) {
18848
- errors.forEach(function (error) { return console.error(error); });
18849
- throw { message: 'FILE.UPLOAD.ERROR' };
18850
- }
18851
- // return files
18852
- return [4 /*yield*/, this.popoverController.dismiss(files)];
18853
- case 5:
18854
- // return files
18855
- _a.sent();
18856
- return [3 /*break*/, 7];
18857
- case 6:
18858
- err_1 = _a.sent();
18859
- this.error = (err_1 && err_1.message) || err_1;
18860
- return [3 /*break*/, 7];
18861
- case 7: return [3 /*break*/, 9];
18862
- case 8:
18863
- this.importing = false;
18864
- this.cd.markForCheck();
18865
- return [7 /*endfinally*/];
18866
- case 9: return [2 /*return*/];
18907
+ }
18908
+ };
18909
+ /**
18910
+ * Execute upload
18911
+ */
18912
+ UploadFileComponent.prototype.uploadFiles = function (files) {
18913
+ return __awaiter(this, void 0, void 0, function () {
18914
+ var $processingFileCountChange, $changes, jobs;
18915
+ var _this = this;
18916
+ return __generator(this, function (_b) {
18917
+ if (!files) {
18918
+ console.info('[upload-file] Uploading all files...');
18919
+ files = this.files;
18867
18920
  }
18921
+ $processingFileCountChange = new rxjs.BehaviorSubject(this.processingFilesCount);
18922
+ $changes = new rxjs.Subject();
18923
+ $changes.subscribe(function (_) {
18924
+ _this.cd.markForCheck();
18925
+ $processingFileCountChange.next(_this.processingFilesCount);
18926
+ });
18927
+ jobs = files
18928
+ .map(function (f) { return f; })
18929
+ .filter(function (file) { return file && isNil(file.progress) && !file.error; }) // Skip if already started or in error
18930
+ .map(function (file, i) {
18931
+ // Wait if need
18932
+ if (isNotNil(_this.maxParallelUpload)) {
18933
+ return $processingFileCountChange.pipe(operators.debounceTime(i), // call of filter() must be different for waiting job
18934
+ operators.filter(function (count) { return count < _this.maxParallelUpload; }), operators.map(function (_) {
18935
+ // Start uploading
18936
+ if (isNil(file.progress))
18937
+ file.progress = 0;
18938
+ $changes.next();
18939
+ return file;
18940
+ }), operators.first());
18941
+ }
18942
+ // Start uploading
18943
+ if (isNil(file.progress))
18944
+ file.progress = 0;
18945
+ return rxjs.of(file);
18946
+ })
18947
+ .map(function (file$) { return file$.pipe(operators.filter(isNotNil),
18948
+ // Log
18949
+ operators.tap(function (file) { return console.debug("[upload-file] " + file.name + ": uploading..."); }),
18950
+ // Start uploading
18951
+ operators.switchMap(function (file) { return _this.uploadFn(file)
18952
+ .pipe(operators.filter(isNotNil), operators.map(function (event) {
18953
+ // Progress event
18954
+ if (isProgressEvent(event)) {
18955
+ if (isNotNil(event.loaded) && event.loaded >= 0) {
18956
+ file.progress = Math.min(1, event.loaded / (event.total || 1));
18957
+ }
18958
+ else {
18959
+ file.progress = -1; // Indeterminate progression
18960
+ }
18961
+ }
18962
+ // Response event
18963
+ else if (isResponseEvent(event)) {
18964
+ file.progress = 1;
18965
+ file.response = event;
18966
+ console.debug("[upload-file] " + file.name + ": " + (event.statusText || 'OK'), file.response);
18967
+ }
18968
+ // Other events: ignore
18969
+ else {
18970
+ // DEBUG
18971
+ console.debug('[upload-file] Ignoring a file event: ', event);
18972
+ }
18973
+ return file;
18974
+ }), operators.catchError(function (err) {
18975
+ var _a;
18976
+ console.error(err);
18977
+ file.error = err && (err.message || ((_a = err.error) === null || _a === void 0 ? void 0 : _a.message)) || 'FILE.UPLOAD.ERROR';
18978
+ return rxjs.of(file);
18979
+ }), operators.tap(function () {
18980
+ $changes.next();
18981
+ _this.cd.markForCheck();
18982
+ })); })); });
18983
+ // Wait jobs to finish
18984
+ return [2 /*return*/, rxjs.forkJoin(jobs)
18985
+ .pipe(operators.tap(function (_) { return $changes.complete(); }))
18986
+ .toPromise()];
18868
18987
  });
18869
18988
  });
18870
18989
  };
18871
- UploadFilePopover.prototype.cancel = function () {
18872
- return this.popoverController.dismiss();
18873
- };
18874
- UploadFilePopover.prototype.resetError = function () {
18875
- if (this.error) {
18876
- this.error = null;
18877
- this.cd.markForCheck();
18878
- }
18990
+ UploadFileComponent.prototype.waitIdle = function (opts) {
18991
+ var _this = this;
18992
+ return waitFor(function () { return _this.processingFilesCount === 0; }, opts);
18879
18993
  };
18880
- return UploadFilePopover;
18994
+ return UploadFileComponent;
18881
18995
  }());
18882
- UploadFilePopover.decorators = [
18996
+ UploadFileComponent.decorators = [
18883
18997
  { type: i0.Component, args: [{
18884
- selector: 'app-upload-file-popover',
18885
- template: "\n<ion-content class=\"ion-no-padding\" scrollY=\"false\">\n <div class=\"modal-content\">\n <app-upload-file\n #uploader\n [uploadFn]=\"uploadFn\"\n [deleteFn]=\"deleteFn\"\n [fileExtension]=\"fileExtension\"\n [instantUpload]=\"instantUpload\"\n [uniqueFile]=\"uniqueFile\"\n [maxParallelUpload]=\"maxParallelUpload\"\n ></app-upload-file>\n </div>\n</ion-content>\n\n<ion-footer>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col>\n <div *ngIf=\"importing\" class=\"importing-info\">\n <ion-text color=\"medium\"><small translate>FILE.UPLOAD.IMPORTING</small></ion-text>\n &nbsp;<ion-spinner name=\"dots\" color=\"accent\"></ion-spinner>\n </div>\n <ion-item *ngIf=\"error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"ion-text-wrap\" [innerHTML]=\"error | translate\"></ion-label>\n </ion-item>\n </ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"cancel()\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n <ion-button\n [fill]=\"disabled ? 'clear' : 'solid'\"\n (click)=\"onValidate($event)\"\n (keyup.enter)=\"onValidate($event)\"\n color=\"tertiary\"\n [disabled]=\"disabled\"\n >\n <ion-label translate>COMMON.BTN_IMPORT</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n",
18886
- encapsulation: i0.ViewEncapsulation.None,
18887
- changeDetection: i0.ChangeDetectionStrategy.OnPush,
18888
- styles: [".importing-info{-webkit-padding-start:var(--ion-padding);padding-inline-start:var(--ion-padding);display:inline-flex;flex-wrap:nowrap;padding-top:8px}"]
18998
+ selector: 'app-upload-file',
18999
+ template: "<div *ngIf=\"!uniqueFile || files.length === 0\"\n class=\"container\"\n appDragAndDrop (fileDropped)=\"onFileDropped($event)\">\n <input\n type=\"file\"\n #fileDropRef\n id=\"fileDropRef\"\n multiple\n [accept]=\"fileExtension\"\n (change)=\"fileBrowseHandler($any($event.target).files)\"\n />\n <mat-icon style=\"font-size: xx-large\" color=\"accent\">file_upload</mat-icon>\n <ion-label\n [innerHTML]=\"\n 'FILE.UPLOAD.DRAG_AND_DROP' | translate: { extension: !fileExtension ? '' : ' (' + fileExtension + ')' }\n \"\n ></ion-label>\n <ion-button>{{ 'FILE.UPLOAD.BROWSE' | translate }}</ion-button>\n</div>\n<div class=\"files-list\">\n <div *ngFor=\"let file of files; index as i\"\n class=\"single-file\"\n [class.deleting]=\"file.deleting\">\n <mat-icon>description</mat-icon>\n <div class=\"info\">\n <h4 class=\"name\">\n {{ file.name }}\n </h4>\n <p class=\"size\">\n {{ file.size | fileSize }}\n </p>\n <ion-progress-bar *ngIf=\"!file.error\"\n [value]=\"file.progress||0\"\n [type]=\"(file.deleting || file.progress === -1) ? 'indeterminate' : 'determinate'\"></ion-progress-bar>\n <ion-label *ngIf=\"file.error\" color=\"danger\" [innerHTML]=\"file.error | translate\"></ion-label>\n </div>\n <!-- remote from list (before importation) -->\n <button *ngIf=\"!(file.progress) || file.error\" mat-icon-button (click)=\"deleteFile(i)\">\n <mat-icon>clear</mat-icon>\n </button>\n <!-- delete file (remotely - after importation) -->\n <button *ngIf=\"file.progress === 1 && !!deleteFn && !file.deleting\" mat-icon-button (click)=\"deleteFile(i)\">\n <mat-icon>clear</mat-icon>\n </button>\n </div>\n</div>\n",
19000
+ styles: [".container{width:calc(100% - 3rem);min-height:200px;padding:2rem;text-align:center;border:1px dashed #979797;position:relative;margin:1.5rem}.container input{opacity:0;position:absolute;z-index:2;width:100%;height:100%;top:0;left:0;cursor:pointer}.container ion-label{display:block;font-size:20px;font-weight:600;color:#38424c}.container label{display:inline-block;color:#fff;width:183px;height:44px;border-radius:21.5px;background-color:#db202f;padding:8px 16px}.fileover{-webkit-animation:shake 1s;animation:shake 1s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.files-list{max-height:300px;overflow:auto}.files-list .single-file{display:flex;flex-grow:1;padding:.5rem;justify-content:space-between;align-items:center;border:1px dashed #979797;margin-bottom:1rem}.files-list .single-file.deleting .name{color:grey!important;font-style:italic!important}.files-list .single-file .name{font-size:14px;font-weight:500;color:#353f4a;margin:0}.files-list .single-file .size{font-size:12px;font-weight:500;color:#a4a4a4;margin:0 0 .25rem}.files-list .single-file .info{width:100%}@-webkit-keyframes shake{0%{transform:translate(1px,1px) rotate(0deg)}10%{transform:translate(-1px,-2px) rotate(-1deg)}20%{transform:translate(-3px) rotate(1deg)}30%{transform:translate(3px,2px) rotate(0deg)}40%{transform:translate(1px,-1px) rotate(1deg)}50%{transform:translate(-1px,2px) rotate(-1deg)}60%{transform:translate(-3px,1px) rotate(0deg)}70%{transform:translate(3px,1px) rotate(-1deg)}80%{transform:translate(-1px,-1px) rotate(1deg)}90%{transform:translate(1px,2px) rotate(0deg)}to{transform:translate(1px,-2px) rotate(-1deg)}}@keyframes shake{0%{transform:translate(1px,1px) rotate(0deg)}10%{transform:translate(-1px,-2px) rotate(-1deg)}20%{transform:translate(-3px) rotate(1deg)}30%{transform:translate(3px,2px) rotate(0deg)}40%{transform:translate(1px,-1px) rotate(1deg)}50%{transform:translate(-1px,2px) rotate(-1deg)}60%{transform:translate(-3px,1px) rotate(0deg)}70%{transform:translate(3px,1px) rotate(-1deg)}80%{transform:translate(-1px,-1px) rotate(1deg)}90%{transform:translate(1px,2px) rotate(0deg)}to{transform:translate(1px,-2px) rotate(-1deg)}}"]
18889
19001
  },] }
18890
19002
  ];
18891
- UploadFilePopover.ctorParameters = function () { return [
18892
- { type: i1$5.PopoverController },
18893
- { type: i1$2.TranslateService },
18894
- { type: i0.ChangeDetectorRef }
19003
+ UploadFileComponent.ctorParameters = function () { return [
19004
+ { type: i0.ChangeDetectorRef },
19005
+ { type: i1$2.TranslateService }
18895
19006
  ]; };
18896
- UploadFilePopover.propDecorators = {
18897
- uploader: [{ type: i0.ViewChild, args: ['uploader', { static: true },] }],
19007
+ UploadFileComponent.propDecorators = {
19008
+ fileDropEl: [{ type: i0.ViewChild, args: ['fileDropRef', { static: false },] }],
18898
19009
  fileExtension: [{ type: i0.Input }],
18899
- title: [{ type: i0.Input }],
18900
19010
  uniqueFile: [{ type: i0.Input }],
18901
19011
  instantUpload: [{ type: i0.Input }],
18902
19012
  uploadFn: [{ type: i0.Input }],
@@ -19663,12 +19773,41 @@
19663
19773
  AppForm.prototype.cancel = function () {
19664
19774
  this.onCancel.emit();
19665
19775
  };
19666
- AppForm.prototype.doSubmit = function (event) {
19667
- if (!this.form || !this.form.valid) {
19668
- this.markAllAsTouched({ emitEvent: true });
19669
- return;
19670
- }
19671
- this.onSubmit.emit(event);
19776
+ /**
19777
+ *
19778
+ * @param event
19779
+ * @param opts allow to skip validation check, using {checkValid: false}
19780
+ */
19781
+ AppForm.prototype.doSubmit = function (event, opts) {
19782
+ return __awaiter(this, void 0, void 0, function () {
19783
+ return __generator(this, function (_b) {
19784
+ switch (_b.label) {
19785
+ case 0:
19786
+ if (!this._form) {
19787
+ this.markAllAsTouched({ emitEvent: true });
19788
+ return [2 /*return*/];
19789
+ }
19790
+ if (!((!opts || opts.checkValid !== false) && !this._form.valid)) return [3 /*break*/, 2];
19791
+ // Wait validation end
19792
+ return [4 /*yield*/, AppFormUtils.waitWhilePending(this._form)];
19793
+ case 1:
19794
+ // Wait validation end
19795
+ _b.sent();
19796
+ // Form is invalid: exit (+ log if debug)
19797
+ if (this._form.invalid) {
19798
+ this.markAllAsTouched({ emitEvent: true });
19799
+ if (this.debug)
19800
+ AppFormUtils.logFormErrors(this._form);
19801
+ return [2 /*return*/];
19802
+ }
19803
+ _b.label = 2;
19804
+ case 2:
19805
+ // Emit event
19806
+ this.onSubmit.emit(event);
19807
+ return [2 /*return*/];
19808
+ }
19809
+ });
19810
+ });
19672
19811
  };
19673
19812
  AppForm.prototype.setForm = function (form) {
19674
19813
  var _this = this;
@@ -20469,74 +20608,6 @@
20469
20608
  return JobUtils;
20470
20609
  }());
20471
20610
 
20472
- var FilesUtils = /** @class */ (function () {
20473
- function FilesUtils() {
20474
- }
20475
- FilesUtils.showUploadPopover = function (popoverController, event, opts) {
20476
- return __awaiter(this, void 0, void 0, function () {
20477
- var modal, _a, data, role;
20478
- return __generator(this, function (_b) {
20479
- switch (_b.label) {
20480
- case 0: return [4 /*yield*/, popoverController.create({
20481
- component: UploadFilePopover,
20482
- componentProps: opts,
20483
- backdropDismiss: false,
20484
- keyboardClose: false,
20485
- event: event,
20486
- translucent: true,
20487
- cssClass: 'popover-large',
20488
- })];
20489
- case 1:
20490
- modal = _b.sent();
20491
- return [4 /*yield*/, modal.present()];
20492
- case 2:
20493
- _b.sent();
20494
- return [4 /*yield*/, modal.onDidDismiss()];
20495
- case 3:
20496
- _a = _b.sent(), data = _a.data, role = _a.role;
20497
- return [2 /*return*/, {
20498
- role: role,
20499
- data: data ? data : undefined
20500
- }];
20501
- }
20502
- });
20503
- });
20504
- };
20505
- FilesUtils.readAsText = function (file, encoding) {
20506
- var $progress = new rxjs.Subject();
20507
- var reader = new FileReader();
20508
- encoding = (encoding || 'UTF-8').toLowerCase();
20509
- // Listen progress
20510
- reader.onprogress = function (e) {
20511
- var total = e.total || 1;
20512
- var loaded = e.total || 0;
20513
- if (loaded < total)
20514
- $progress.next({ type: i2$1.HttpEventType.UploadProgress, loaded: loaded, total: total });
20515
- };
20516
- // Listen end
20517
- reader.onloadend = function (e) {
20518
- var body = reader.result;
20519
- if (typeof body === 'string') {
20520
- $progress.next(new FileResponse({ body: body }));
20521
- $progress.complete();
20522
- }
20523
- else {
20524
- console.error("Cannot read file '" + file.name + "' as text");
20525
- $progress.error("Cannot read file as text");
20526
- }
20527
- };
20528
- // Listen error
20529
- reader.onerror = function (event) {
20530
- console.error('[files] Error while reading file:', event);
20531
- $progress.error(event);
20532
- };
20533
- // Start reading the file
20534
- reader.readAsText(file, encoding);
20535
- return $progress.asObservable();
20536
- };
20537
- return FilesUtils;
20538
- }());
20539
-
20540
20611
  /**
20541
20612
  * Define here theme colors
20542
20613
  */
@@ -20953,25 +21024,41 @@
20953
21024
  this.onCancel.emit();
20954
21025
  };
20955
21026
  AuthForm.prototype.doSubmit = function (event) {
20956
- var _this = this;
20957
- if (event) {
20958
- event.preventDefault();
20959
- event.stopPropagation();
20960
- }
20961
- if (this.form.invalid || this.loading)
20962
- return;
20963
- this.markAsLoading();
20964
- var data = this.form.value;
20965
- this.showPwd = false; // Hide password
20966
- this.error = null; // Reset error
20967
- this.registerSubscription(this.onSubmit
20968
- .pipe(operators.debounceTime(500))
20969
- .subscribe(function (res) { return _this.markAsLoaded(); }));
20970
- setTimeout(function () { return _this.onSubmit.emit({
20971
- username: data.username,
20972
- password: data.password,
20973
- offline: data.offline
20974
- }); });
21027
+ return __awaiter(this, void 0, void 0, function () {
21028
+ var data;
21029
+ var _this = this;
21030
+ return __generator(this, function (_a) {
21031
+ switch (_a.label) {
21032
+ case 0:
21033
+ if (event) {
21034
+ event.preventDefault();
21035
+ event.stopPropagation();
21036
+ }
21037
+ if (this.loading)
21038
+ return [2 /*return*/];
21039
+ if (!!this.form.valid) return [3 /*break*/, 2];
21040
+ return [4 /*yield*/, AppFormUtils.waitWhilePending(this.form)];
21041
+ case 1:
21042
+ _a.sent();
21043
+ if (this.form.invalid) {
21044
+ AppFormUtils.logFormErrors(this.form);
21045
+ return [2 /*return*/]; // Skip if invalid
21046
+ }
21047
+ _a.label = 2;
21048
+ case 2:
21049
+ this.markAsLoading();
21050
+ data = this.form.value;
21051
+ this.showPwd = false; // Hide password
21052
+ this.error = null; // Reset error
21053
+ setTimeout(function () { return _this.onSubmit.emit({
21054
+ username: data.username,
21055
+ password: data.password,
21056
+ offline: data.offline
21057
+ }); });
21058
+ return [2 /*return*/];
21059
+ }
21060
+ });
21061
+ });
20975
21062
  };
20976
21063
  AuthForm.prototype.register = function () {
20977
21064
  var _this = this;
@@ -23170,9 +23257,6 @@
23170
23257
  { type: i0.Injectable, args: [{ providedIn: 'root' },] }
23171
23258
  ];
23172
23259
 
23173
- var MimeTypes = Object.freeze({
23174
- ANDROID_APK: 'application/vnd.android.package-archive'
23175
- });
23176
23260
  var AppInstallUpgradeCard = /** @class */ (function () {
23177
23261
  function AppInstallUpgradeCard(modalCtrl, configService, toastController, alertController, translate, cd, platform, network, environment) {
23178
23262
  this.modalCtrl = modalCtrl;
@@ -23419,10 +23503,10 @@
23419
23503
  var title = void 0;
23420
23504
  var mimeType = void 0;
23421
23505
  // Get file name
23422
- var filename = this.getFilename(url);
23506
+ var filename = UriUtils.getFilename(url);
23423
23507
  var version = minVersion || 'latest';
23424
23508
  // OK, this is a downloadable APK file (e.g. NOT a link to a playstore)
23425
- if (filename === null || filename === void 0 ? void 0 : filename.endsWith('.apk')) {
23509
+ if (filename === null || filename === void 0 ? void 0 : filename.toLowerCase().endsWith('.apk')) {
23426
23510
  // Define mime type
23427
23511
  mimeType = MimeTypes.ANDROID_APK;
23428
23512
  // Get the file version (if any)
@@ -23433,11 +23517,9 @@
23433
23517
  filename = (name + "-v" + version + ".apk").toLowerCase();
23434
23518
  title = name + " v" + version;
23435
23519
  }
23436
- else {
23437
- // Replace 'latest' with the app version, if present
23438
- if (filename.indexOf('latest')) {
23439
- filename = filename.replace('latest', version);
23440
- }
23520
+ // Replace 'latest' with the app version, if present in the filename
23521
+ else if (filename.indexOf('latest') && version !== 'latest') {
23522
+ filename = filename.replace('latest', version);
23441
23523
  }
23442
23524
  }
23443
23525
  result.push({ name: name, url: url, platform: 'android', version: version, filename: filename, title: title, mimeType: mimeType });
@@ -23449,18 +23531,6 @@
23449
23531
  //}
23450
23532
  return result;
23451
23533
  };
23452
- AppInstallUpgradeCard.prototype.getFilename = function (url) {
23453
- if (!url)
23454
- return;
23455
- // Get last part (or all string, if no '/')
23456
- var filename = url.substring(url.lastIndexOf('/') + 1);
23457
- var queryParamIndex = filename.indexOf('?');
23458
- if (queryParamIndex !== -1) {
23459
- // Remove query params
23460
- return filename.substring(0, queryParamIndex);
23461
- }
23462
- return filename;
23463
- };
23464
23534
  AppInstallUpgradeCard.prototype.listenDownloadJobs = function () {
23465
23535
  var _this = this;
23466
23536
  // Listening downloading promise, if exists
@@ -25221,7 +25291,7 @@
25221
25291
  updateDatasource: { get: function () { return _super_1.prototype.updateDatasource; } }
25222
25292
  });
25223
25293
  return __awaiter(this, void 0, void 0, function () {
25224
- var res, existingData;
25294
+ var fetchMoreFn, res, existingData;
25225
25295
  return __generator(this, function (_a) {
25226
25296
  switch (_a.label) {
25227
25297
  case 0:
@@ -25231,9 +25301,9 @@
25231
25301
  console.warn('Cannot fetch more because still editing row');
25232
25302
  return [2 /*return*/];
25233
25303
  }
25234
- // Fetch next page
25235
- this._fetchMoreFn = null; // Avoid multiple call
25236
- return [4 /*yield*/, this._fetchMoreFn()];
25304
+ fetchMoreFn = this._fetchMoreFn;
25305
+ this._fetchMoreFn = null;
25306
+ return [4 /*yield*/, fetchMoreFn()];
25237
25307
  case 1:
25238
25308
  res = _a.sent();
25239
25309
  // Skip if empty (no more data)
@@ -30276,6 +30346,7 @@
30276
30346
  missingEntity: [null, SharedValidators.entity],
30277
30347
  disableEntity: [null, SharedValidators.entity],
30278
30348
  entities: [null, SharedFormArrayValidators.requiredArrayMinLength(1)],
30349
+ farEntity: [null, SharedValidators.entity]
30279
30350
  });
30280
30351
  this.form.get('disableEntity').disable();
30281
30352
  }
@@ -30314,6 +30385,13 @@
30314
30385
  attributes: ['label', 'name', 'description', 'comments'],
30315
30386
  displayWith: this.entityToString
30316
30387
  });
30388
+ // Far entity, in the list
30389
+ this.autocompleteFields.add('entity-far', {
30390
+ suggestFn: function (value, filter) { return _this.suggest(value, filter); },
30391
+ attributes: ['label', 'name'],
30392
+ displayWith: this.entityToString,
30393
+ showAllOnFocus: true
30394
+ });
30317
30395
  // For mutliple value
30318
30396
  this.autocompleteFields.add('entities-items-filter', {
30319
30397
  service: {
@@ -30333,6 +30411,7 @@
30333
30411
  return __generator(this, function (_a) {
30334
30412
  data = {
30335
30413
  entity: deepCopy(FAKE_ENTITIES)[0],
30414
+ farEntity: deepCopy(FAKE_ENTITIES)[2],
30336
30415
  // This item is NOT in the items list => i should be displayed anyway
30337
30416
  missingEntity: {
30338
30417
  id: -1, label: '??', name: 'Missing item'
@@ -30428,7 +30507,7 @@
30428
30507
  AutocompleteTestPage.decorators = [
30429
30508
  { type: i0.Component, args: [{
30430
30509
  selector: 'app-autocomplete-test',
30431
- template: "<ion-header>\n <ion-toolbar color=\"primary\">\n\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Autocomplete field test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n\n <!-- Tab nav - mobile/desktop mode -->\n <nav mat-tab-nav-bar>\n <a mat-tab-link\n [active]=\"mode==='mobile'\"\n (click)=\"toggleMode('mobile')\">\n <mat-label>Mobile</mat-label>\n </a>\n <a mat-tab-link [active]=\"mode==='desktop'\"\n (click)=\"toggleMode('desktop')\">\n <mat-label>Desktop</mat-label>\n </a>\n <a mat-tab-link [active]=\"mode==='memory'\"\n (click)=\"toggleMode('memory')\">\n <mat-label>Memory leak debug</mat-label>\n </a>\n <a mat-tab-link\n [active]=\"mode==='temp'\"\n (click)=\"toggleMode('temp')\">\n <mat-label>Temporary</mat-label>\n </a>\n </nav>\n\n <form class=\"form-container\" [formGroup]=\"form\" (ngSubmit)=\"doSubmit($event)\">\n\n <!--<ion-grid *ngIf=\"mode === 'temp'\">\n <ion-row>\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an Observable\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>items: Observable&lt;any[]&gt;</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [logPrefix]=\"'[combo-desktop-2]'\"\n [compareWith]=\"compareWithFn\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n\n </ion-card>\n </ion-col>\n </ion-row>\n\n\n </ion-grid>-->\n\n\n <ion-grid *ngIf=\"mode === 'memory'\">\n\n <!-- debugging memory leak -->\n <ion-row>\n <ion-col>\n <ion-text><h4>Debug memory leak</h4></ion-text>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"2\">\n <mat-form-field floatLabel=\"always\">\n <input matInput type=\"text\" hidden placeholder=\"Items type\">\n <mat-select (selectionChange)=\"memoryAutocompleteFieldName=$event.value\" [value]=\"memoryAutocompleteFieldName\">\n <mat-option value=\"entity-$items\">Observable</mat-option>\n <mat-option value=\"entity-suggestFn\">Suggest function</mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"1\" class=\"ion-no-padding\">\n <mat-form-field floatLabel=\"always\">\n <input matInput type=\"text\" hidden placeholder=\"Mobile ?\">\n <mat-checkbox (change)=\"memoryMobile=$event.checked\" [checked]=\"memoryMobile\">\n </mat-checkbox>\n </mat-form-field>\n </ion-col>\n\n <ion-col size=\"2\">\n <ion-button *ngIf=\"!memoryTimer\" (click)=\"startMemoryTimer()\" color=\"tertiary\">Start</ion-button>\n <ion-button *ngIf=\"memoryTimer\" (click)=\"stopMemoryTimer()\" color=\"tertiary\">Stop</ion-button>\n </ion-col>\n </ion-row>\n\n <ion-row>\n <ion-col>\n <mat-autocomplete-field formControlName=\"entity\"\n *ngIf=\"!memoryHide && memoryAutocompleteFieldName\"\n [config]=\"autocompleteFields.get(memoryAutocompleteFieldName)\"\n [mobile]=\"memoryMobile\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-memory-leak]'\">\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n <!-- Mobile mode -->\n <ion-grid *ngIf=\"mode === 'mobile'\">\n\n <ion-row>\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Suggest() function\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, suggestFn: (searchText, filter) =&gt; any[]</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-suggestFn')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-1]'\"\n [required]=\"true\"\n placeholder=\"Suggest field\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Items is an Observable\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-observable]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Control value if missing in items\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"missingEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [clearable]=\"true\"\n [logPrefix]=\"'[combo-mobile-2]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n search with a suggestLengthThreshold\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;\nsuggestLengthThreshold: '3'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n suggestLengthThreshold=\"3\"\n [logPrefix]=\"'[combo-mobile-suggestLengthThreshold]'\"\n required\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Multiple value\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>multiple: true</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entities\"\n [config]=\"autocompleteFields.get('entities-items-filter')\"\n [placeholder]=\"'Multiple'\"\n mobile=\"true\"\n multiple=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-multiple]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disabled control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"disableEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-2]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n </ion-row>\n\n <ion-row>\n <ion-col size=\"9\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Change filter\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre></pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <ion-button (click)=\"updateFilter(filterChangeField, 'entity-items-filter')\">Filter on: {{autocompleteFields.get('entity-items-filter').filter?.searchAttribute }}</ion-button>\n </ion-col>\n <ion-col>\n <mat-autocomplete-field formControlName=\"entity\" #filterChangeField\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-filter]'\"\n ></mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"3\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Large combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class=\"min-width-large\"</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"true\"\n class=\"min-width-large\"\n panelWidth=\"400px\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-large]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Readonly toggle\n </ion-text>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid>\n <ion-row>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n Readonly ?\n </mat-checkbox>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <mat-autocomplete-field #readonlyField formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-readonly]'\"\n [readonly]=\"true\"\n [clearable]=\"!readonlyField.readonly\">\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Fullscreen\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class: 'mat-autocomplete-panel-full-size'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [class]=\"'mat-autocomplete-panel-full-size'\"\n [matAutocompletePosition]=\"'above'\"\n [logPrefix]=\"'[combo-mobile-full-size]'\"\n [debug]=\"true\"\n [showAllOnFocus]=\"true\"\n [showPanelOnFocus]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Desktop mode -->\n <ion-grid *ngIf=\"mode === 'desktop'\">\n <ion-row>\n <ion-col>\n <ion-text><h4>Desktop mode</h4></ion-text>\n </ion-col>\n </ion-row>\n <ion-row>\n\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n items from suggest function\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-suggest]'\">\n </mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n search with a suggestLengthThreshold\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;\nsuggestLengthThreshold: '3'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n suggestLengthThreshold=\"3\"\n [logPrefix]=\"'[combo-desktop-suggestLengthThreshold]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an array\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: [], value: {{stringify(form.controls.entity.value)}}</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items')\"\n [logPrefix]=\"'[combo-desktop-array-1]'\"\n [equals]=\"equals\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an Observable\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [logPrefix]=\"'[combo-desktop-2]'\"\n [equals]=\"equals\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Multiple value\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>multiple: true</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entities\"\n [config]=\"autocompleteFields.get('entities-items-filter')\"\n [placeholder]=\"'Multiple'\"\n mobile=\"false\"\n multiple=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-multiple]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Control value if missing in items\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"missingEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-missing]'\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Full size panel\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class: 'mat-autocomplete-panel-full-size'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [class]=\"'mat-autocomplete-panel-full-size'\"\n [matAutocompletePosition]=\"'above'\"\n [logPrefix]=\"'[combo-desktop-full-size]'\"\n [debug]=\"true\"\n [showAllOnFocus]=\"true\"\n [showPanelOnFocus]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disabled control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"disableEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-disable]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Readonly control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-readonly]'\"\n [readonly]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n\n <ion-row>\n <ion-col size=\"9\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Full size combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class=\"mat-autocomplete-panel-full-size\"</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"false\"\n class=\"mat-autocomplete-panel-full-size\"\n panelWidth=\"100vw\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-full-size]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"3\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Large combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>panelWidth: string</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"false\"\n panelWidth=\"400px\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-large]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Readonly toggle\n </ion-text>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid>\n <ion-row>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n Readonly ?\n </mat-checkbox>\n </ion-col>\n <ion-col size=\"3\">\n <mat-checkbox #showIcons [checked]=\"false\">\n Icons ?\n </mat-checkbox>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <mat-autocomplete-field #readonlyField formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-readonly]'\"\n [readonly]=\"true\"\n [clearable]=\"!readonlyField.readonly\">\n <mat-icon matPrefix *ngIf=\"showIcons.checked\">keyboard_arrow_right</mat-icon>\n <mat-icon matSuffix *ngIf=\"showIcons.checked\">keyboard_arrow_left</mat-icon>\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </form>\n\n</ion-content>\n"
30510
+ template: "<ion-header>\n <ion-toolbar color=\"primary\">\n\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Autocomplete field test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n\n <!-- Tab nav - mobile/desktop mode -->\n <nav mat-tab-nav-bar>\n <a mat-tab-link\n [active]=\"mode==='mobile'\"\n (click)=\"toggleMode('mobile')\">\n <mat-label>Mobile</mat-label>\n </a>\n <a mat-tab-link [active]=\"mode==='desktop'\"\n (click)=\"toggleMode('desktop')\">\n <mat-label>Desktop</mat-label>\n </a>\n <a mat-tab-link [active]=\"mode==='memory'\"\n (click)=\"toggleMode('memory')\">\n <mat-label>Memory leak debug</mat-label>\n </a>\n <a mat-tab-link\n [active]=\"mode==='temp'\"\n (click)=\"toggleMode('temp')\">\n <mat-label>Temporary</mat-label>\n </a>\n </nav>\n\n <form class=\"form-container\" [formGroup]=\"form\" (ngSubmit)=\"doSubmit($event)\">\n\n <!--<ion-grid *ngIf=\"mode === 'temp'\">\n <ion-row>\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an Observable\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>items: Observable&lt;any[]&gt;</pre></small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [logPrefix]=\"'[combo-desktop-2]'\"\n [compareWith]=\"compareWithFn\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n\n </ion-card>\n </ion-col>\n </ion-row>\n\n\n </ion-grid>-->\n\n\n <ion-grid *ngIf=\"mode === 'memory'\">\n\n <!-- debugging memory leak -->\n <ion-row>\n <ion-col>\n <ion-text><h4>Debug memory leak</h4></ion-text>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"2\">\n <mat-form-field floatLabel=\"always\">\n <input matInput type=\"text\" hidden placeholder=\"Items type\">\n <mat-select (selectionChange)=\"memoryAutocompleteFieldName=$event.value\" [value]=\"memoryAutocompleteFieldName\">\n <mat-option value=\"entity-$items\">Observable</mat-option>\n <mat-option value=\"entity-suggestFn\">Suggest function</mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"1\" class=\"ion-no-padding\">\n <mat-form-field floatLabel=\"always\">\n <input matInput type=\"text\" hidden placeholder=\"Mobile ?\">\n <mat-checkbox (change)=\"memoryMobile=$event.checked\" [checked]=\"memoryMobile\">\n </mat-checkbox>\n </mat-form-field>\n </ion-col>\n\n <ion-col size=\"2\">\n <ion-button *ngIf=\"!memoryTimer\" (click)=\"startMemoryTimer()\" color=\"tertiary\">Start</ion-button>\n <ion-button *ngIf=\"memoryTimer\" (click)=\"stopMemoryTimer()\" color=\"tertiary\">Stop</ion-button>\n </ion-col>\n </ion-row>\n\n <ion-row>\n <ion-col>\n <mat-autocomplete-field formControlName=\"entity\"\n *ngIf=\"!memoryHide && memoryAutocompleteFieldName\"\n [config]=\"autocompleteFields.get(memoryAutocompleteFieldName)\"\n [mobile]=\"memoryMobile\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-memory-leak]'\">\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n <!-- Mobile mode -->\n <ion-grid *ngIf=\"mode === 'mobile'\">\n\n <ion-row>\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Suggest() function\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, suggestFn: (searchText, filter) =&gt; any[]</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"farEntity\"\n [config]=\"autocompleteFields.get('entity-far')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-far]'\"\n [required]=\"true\"\n placeholder=\"Far entity (in the list)\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Suggest() function\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, suggestFn: (searchText, filter) =&gt; any[]</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-suggestFn')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-1]'\"\n [required]=\"true\"\n placeholder=\"Suggest field\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Items is an Observable\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-observable]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Control value if missing in items\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>mobile: true, items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"missingEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [clearable]=\"true\"\n [logPrefix]=\"'[combo-mobile-2]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n search with a suggestLengthThreshold\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;\nsuggestLengthThreshold: '3'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n suggestLengthThreshold=\"3\"\n [logPrefix]=\"'[combo-mobile-suggestLengthThreshold]'\"\n required\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Multiple value\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>multiple: true</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entities\"\n [config]=\"autocompleteFields.get('entities-items-filter')\"\n [placeholder]=\"'Multiple'\"\n mobile=\"true\"\n multiple=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-multiple]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disabled control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"disableEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-2]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n </ion-row>\n\n <ion-row>\n <ion-col size=\"9\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Change filter\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre></pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col>\n <ion-button (click)=\"updateFilter(filterChangeField, 'entity-items-filter')\">Filter on: {{autocompleteFields.get('entity-items-filter').filter?.searchAttribute }}</ion-button>\n </ion-col>\n <ion-col>\n <mat-autocomplete-field formControlName=\"entity\" #filterChangeField\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-filter]'\"\n ></mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"3\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Large combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class=\"min-width-large\"</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"true\"\n class=\"min-width-large\"\n panelWidth=\"400px\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-large]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Readonly toggle\n </ion-text>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid>\n <ion-row>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n Readonly ?\n </mat-checkbox>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <mat-autocomplete-field #readonlyField formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-mobile-readonly]'\"\n [readonly]=\"true\"\n [clearable]=\"!readonlyField.readonly\">\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Fullscreen\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class: 'mat-autocomplete-panel-full-size'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"true\"\n [class]=\"'mat-autocomplete-panel-full-size'\"\n [matAutocompletePosition]=\"'above'\"\n [logPrefix]=\"'[combo-mobile-full-size]'\"\n [debug]=\"true\"\n [showAllOnFocus]=\"true\"\n [showPanelOnFocus]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Desktop mode -->\n <ion-grid *ngIf=\"mode === 'desktop'\">\n <ion-row>\n <ion-col>\n <ion-text><h4>Desktop mode</h4></ion-text>\n </ion-col>\n </ion-row>\n <ion-row>\n\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n items from suggest function\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-suggest]'\">\n </mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n search with a suggestLengthThreshold\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>suggest: (value, filter) => LoadResult&lt;any&gt;\nsuggestLengthThreshold: '3'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n suggestLengthThreshold=\"3\"\n [logPrefix]=\"'[combo-desktop-suggestLengthThreshold]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an array\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: [], value: {{stringify(form.controls.entity.value)}}</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items')\"\n [logPrefix]=\"'[combo-desktop-array-1]'\"\n [equals]=\"equals\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Items is an Observable\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [logPrefix]=\"'[combo-desktop-2]'\"\n [equals]=\"equals\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Multiple value\n </ion-text>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small><pre>multiple: true</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entities\"\n [config]=\"autocompleteFields.get('entities-items-filter')\"\n [placeholder]=\"'Multiple'\"\n mobile=\"false\"\n multiple=\"true\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-multiple]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Control value if missing in items\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>items: Observable&lt;any[]&gt;</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"missingEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-missing]'\"\n [mobile]=\"false\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Full size panel\n </ion-label>\n </ion-card-title>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class: 'mat-autocomplete-panel-full-size'</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [class]=\"'mat-autocomplete-panel-full-size'\"\n [matAutocompletePosition]=\"'above'\"\n [logPrefix]=\"'[combo-desktop-full-size]'\"\n [debug]=\"true\"\n [showAllOnFocus]=\"true\"\n [showPanelOnFocus]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Disabled control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"disableEntity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-disable]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col>\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Readonly control\n </ion-label>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-$items')\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-readonly]'\"\n [readonly]=\"true\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n\n <ion-row>\n <ion-col size=\"9\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Full size combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>class=\"mat-autocomplete-panel-full-size\"</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"false\"\n class=\"mat-autocomplete-panel-full-size\"\n panelWidth=\"100vw\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-full-size]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"3\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-label color=\"primary\">\n Large combo\n </ion-label>\n <ion-card-subtitle>\n <ion-text color=\"medium\">\n <small>\n <pre>panelWidth: string</pre>\n </small>\n </ion-text>\n </ion-card-subtitle>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <mat-autocomplete-field formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-large')\"\n [mobile]=\"false\"\n panelWidth=\"400px\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-large]'\"\n ></mat-autocomplete-field>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-col size=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-title>\n <ion-text color=\"primary\">\n Readonly toggle\n </ion-text>\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <ion-grid>\n <ion-row>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"readonlyField.readonly=$event.checked\" [checked]=\"readonlyField.readonly\">\n Readonly ?\n </mat-checkbox>\n </ion-col>\n <ion-col size=\"3\">\n <mat-checkbox #showIcons [checked]=\"false\">\n Icons ?\n </mat-checkbox>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col>\n <mat-autocomplete-field #readonlyField formControlName=\"entity\"\n [config]=\"autocompleteFields.get('entity-items-filter')\"\n [placeholder]=\"'Entity'\"\n [mobile]=\"false\"\n [equals]=\"equals\"\n [logPrefix]=\"'[combo-desktop-readonly]'\"\n [readonly]=\"true\"\n [clearable]=\"!readonlyField.readonly\">\n <mat-icon matPrefix *ngIf=\"showIcons.checked\">keyboard_arrow_right</mat-icon>\n <mat-icon matSuffix *ngIf=\"showIcons.checked\">keyboard_arrow_left</mat-icon>\n </mat-autocomplete-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </form>\n\n</ion-content>\n"
30432
30511
  },] }
30433
30512
  ];
30434
30513
  AutocompleteTestPage.ctorParameters = function () { return [
@@ -31920,6 +31999,7 @@
31920
31999
  exports.MessageService = MessageService;
31921
32000
  exports.MessageTypeList = MessageTypeList;
31922
32001
  exports.MessageTypes = MessageTypes;
32002
+ exports.MimeTypes = MimeTypes;
31923
32003
  exports.ModalToolbarComponent = ModalToolbarComponent;
31924
32004
  exports.NetworkService = NetworkService;
31925
32005
  exports.NgInitDirective = NgInitDirective;
@@ -32004,6 +32084,7 @@
32004
32084
  exports.UploadFilePopover = UploadFilePopover;
32005
32085
  exports.UploadFileTestingModule = UploadFileTestingModule;
32006
32086
  exports.UploadFileTestingPage = UploadFileTestingPage;
32087
+ exports.UriUtils = UriUtils;
32007
32088
  exports.UserEventFragments = UserEventFragments;
32008
32089
  exports.UserEventService = UserEventService;
32009
32090
  exports.UserEventTypes = UserEventTypes;