@sumaris-net/ngx-components 2.6.4-rc9 → 2.6.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.
@@ -23,13 +23,13 @@ import * as i6$2 from '@angular/material/checkbox';
23
23
  import { MatCheckboxModule } from '@angular/material/checkbox';
24
24
  import * as i9$1 from '@angular/material/expansion';
25
25
  import { MatExpansionModule, MatExpansionPanel } from '@angular/material/expansion';
26
- import * as i8$1 from '@angular/material/toolbar';
26
+ import * as i8$2 from '@angular/material/toolbar';
27
27
  import { MatToolbarModule } from '@angular/material/toolbar';
28
28
  import * as i8 from '@angular/material/icon';
29
29
  import { MatIconModule } from '@angular/material/icon';
30
30
  import * as i9 from '@angular/material/button';
31
31
  import { MatButtonModule } from '@angular/material/button';
32
- import * as i11$2 from '@angular/material/menu';
32
+ import * as i8$1 from '@angular/material/menu';
33
33
  import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
34
34
  import * as i12 from '@angular/material/select';
35
35
  import { MatSelectModule, MAT_SELECT_SCROLL_STRATEGY } from '@angular/material/select';
@@ -4324,7 +4324,7 @@ function addValueInArray(arrayControl, createControl, equals, isEmpty, value, op
4324
4324
  control.disable({ emitEvent: false });
4325
4325
  else if (!disabled && control.disabled)
4326
4326
  control.enable({ emitEvent: false });
4327
- if (options?.insertAt) {
4327
+ if (isNotNilOrNaN(options?.insertAt)) {
4328
4328
  arrayControl.insert(options.insertAt, control, options);
4329
4329
  }
4330
4330
  else {
@@ -20110,6 +20110,819 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImpor
20110
20110
  }]
20111
20111
  }] });
20112
20112
 
20113
+ class EntityFilter extends Entity {
20114
+ /**
20115
+ * Compose some filter functions: all should return true
20116
+ * @param filterFns
20117
+ */
20118
+ static composeFilters(filterFns) {
20119
+ if (isEmptyArray(filterFns))
20120
+ return undefined;
20121
+ return (entity) => (filterFns || []).every(fn => fn(entity));
20122
+ }
20123
+ static compose(filters) {
20124
+ const filterFns = (filters || []).reduce((res, f) => {
20125
+ const fns = f.buildFilter();
20126
+ if (!fns)
20127
+ return res;
20128
+ return res.concat(fns);
20129
+ }, []);
20130
+ return this.composeFilters(filterFns);
20131
+ }
20132
+ /**
20133
+ * Compose some filter functions: all should return true
20134
+ * @param filterFns
20135
+ */
20136
+ static composeFns(filterFns) {
20137
+ if (isEmptyArray(filterFns))
20138
+ return undefined;
20139
+ return (entity) => (filterFns || []).every(fn => fn(entity));
20140
+ }
20141
+ constructor(__typename) {
20142
+ super(__typename);
20143
+ }
20144
+ /**
20145
+ * Clean a filter, before sending to the pod (e.g convert dates, remove internal properties, etc.)
20146
+ */
20147
+ asPodObject() {
20148
+ return this.asObject(MINIFY_ENTITY_FOR_POD);
20149
+ }
20150
+ isEmpty() {
20151
+ return this.countNotEmptyCriteria() === 0;
20152
+ }
20153
+ countNotEmptyCriteria() {
20154
+ const json = this.asPodObject();
20155
+ return Object.keys(json)
20156
+ .filter(key => this.isCriteriaNotEmpty(key, json[key]))
20157
+ .length;
20158
+ }
20159
+ asFilterFn() {
20160
+ const filterFns = this.buildFilter();
20161
+ return EntityFilter.composeFns(filterFns);
20162
+ }
20163
+ buildFilter() {
20164
+ // Can be completed by subclasses
20165
+ return [];
20166
+ }
20167
+ isCriteriaNotEmpty(key, value) {
20168
+ // Can be overridden by subclasses
20169
+ return value !== undefined
20170
+ && value !== null
20171
+ && (
20172
+ // not empty string
20173
+ (typeof value === 'string' && value.trim() !== '')
20174
+ // valid number
20175
+ || (typeof value === 'number' && !isNaN(value))
20176
+ // valid boolean
20177
+ || (typeof value === 'boolean')
20178
+ // not empty array
20179
+ || (Array.isArray(value) && value.length > 0)
20180
+ // entity with an id
20181
+ || ReferentialUtils.isNotEmpty(value));
20182
+ }
20183
+ }
20184
+ class EntityFilterUtils {
20185
+ static isEntityFilter(obj) {
20186
+ return obj && obj.asPodObject && obj.asFilterFn && true || false;
20187
+ }
20188
+ static fromObject(source, filterType) {
20189
+ if (!source)
20190
+ return source;
20191
+ if (isInstanceOf(source, filterType))
20192
+ return source;
20193
+ const target = new filterType();
20194
+ if (EntityFilterUtils.isEntityFilter(target)) {
20195
+ target.fromObject(source);
20196
+ }
20197
+ else {
20198
+ Object.assign(target, source);
20199
+ }
20200
+ return target;
20201
+ }
20202
+ }
20203
+
20204
+ // @dynamic
20205
+ // eslint-disable-next-line @angular-eslint/directive-class-suffix
20206
+ class BaseEntityService extends BaseGraphqlService {
20207
+ constructor(graphql, platform, dataType, filterType, options) {
20208
+ super(graphql, environment);
20209
+ this.graphql = graphql;
20210
+ this.platform = platform;
20211
+ this.dataType = dataType;
20212
+ this.filterType = filterType;
20213
+ this.queries = options.queries;
20214
+ this.mutations = options.mutations || {};
20215
+ this.subscriptions = options.subscriptions || {};
20216
+ this.equalsFn = options.equalsFn || ((e1, e2) => EntityUtils.equals(e1, e2, 'id'));
20217
+ this.isNewFn = options.isNewFn || ((e) => EntityUtils.isNotEmpty(e, 'id'));
20218
+ this.defaultSortBy = options.defaultSortBy || 'id';
20219
+ this.defaultSortDirection = options.defaultSortDirection || 'asc';
20220
+ this.watchQueriesUpdatePolicy = options.watchQueriesUpdatePolicy || 'update-cache';
20221
+ const obj = new dataType();
20222
+ this._typename = obj.__typename || 'UnknownVO';
20223
+ this._logTypeName = removeEnd(this._typename, 'VO');
20224
+ // For DEV only
20225
+ this._debug = !environment.production;
20226
+ }
20227
+ watch(id, opts) {
20228
+ if (this._debug)
20229
+ console.debug(this._logPrefix + `Watching ${this._logTypeName} {${id}}...`);
20230
+ const variables = opts?.variables || { id };
20231
+ const query = opts && opts.query || this.queries.load;
20232
+ return this.graphql.watchQuery({
20233
+ query,
20234
+ variables,
20235
+ fetchPolicy: opts && opts.fetchPolicy || undefined,
20236
+ error: { code: ErrorCodes.LOAD_DATA_ERROR, message: 'REFERENTIAL.ERROR.LOAD_DATA_ERROR' }
20237
+ })
20238
+ .pipe(map(({ data }) => (!opts || opts.toEntity !== false)
20239
+ ? (data && this.fromObject(data))
20240
+ : data));
20241
+ }
20242
+ async load(id, opts) {
20243
+ if (this._debug)
20244
+ console.debug(this._logPrefix + `Loading ${this._logTypeName} {${id}}...`);
20245
+ const variables = opts?.variables || { id };
20246
+ const query = opts?.query || this.queries.load;
20247
+ const { data } = await this.graphql.query({
20248
+ query,
20249
+ variables,
20250
+ fetchPolicy: opts && opts.fetchPolicy || undefined,
20251
+ error: { code: ErrorCodes.LOAD_DATA_ERROR, message: 'REFERENTIAL.ERROR.LOAD_DATA_ERROR' }
20252
+ });
20253
+ // Convert to entity
20254
+ return (!opts || opts.toEntity !== false)
20255
+ ? (data && this.fromObject(data))
20256
+ : data;
20257
+ }
20258
+ watchAll(offset, size, sortBy, sortDirection, filter, opts) {
20259
+ filter = this.asFilter(filter);
20260
+ const variables = {
20261
+ ...(opts?.variables || {}),
20262
+ offset: offset || 0,
20263
+ size: size || 100,
20264
+ sortBy: sortBy || this.defaultSortBy,
20265
+ sortDirection: sortDirection || this.defaultSortDirection,
20266
+ filter: filter && filter.asPodObject()
20267
+ };
20268
+ let now = this._debug && Date.now();
20269
+ if (this._debug)
20270
+ console.debug(this._logPrefix + `Watching ${this._logTypeName}...`, variables);
20271
+ const withTotal = (!opts || opts.withTotal !== false) && this.queries.loadAllWithTotal && true;
20272
+ const query = (opts && opts.query) // use given query
20273
+ // Or get loadAll or loadAllWithTotal query
20274
+ || (withTotal ? this.queries.loadAllWithTotal : this.queries.loadAll);
20275
+ return this.mutableWatchQuery({
20276
+ query,
20277
+ variables,
20278
+ // Force cached mutable query
20279
+ queryName: withTotal ? 'LoadAllWithTotal' : 'LoadAll',
20280
+ arrayFieldName: 'data',
20281
+ totalFieldName: withTotal ? 'total' : undefined,
20282
+ insertFilterFn: filter && filter.asFilterFn(),
20283
+ error: { code: ErrorCodes.LOAD_DATA_ERROR, message: 'REFERENTIAL.ERROR.LOAD_DATA_ERROR' },
20284
+ fetchPolicy: opts && opts.fetchPolicy || undefined
20285
+ })
20286
+ .pipe(map(({ data, total }) => {
20287
+ // Convert to entity (if need)
20288
+ const entities = (!opts || opts.toEntity !== false)
20289
+ ? (data || []).map(json => this.fromObject(json))
20290
+ : (data || []);
20291
+ if (now) {
20292
+ console.debug(this._logPrefix + `${this._logTypeName} loaded in ${Date.now() - now}ms`, entities);
20293
+ now = null;
20294
+ }
20295
+ return { data: entities, total };
20296
+ }));
20297
+ }
20298
+ async loadAll(offset, size, sortBy, sortDirection, filter, opts) {
20299
+ const debug = this._debug && (!opts || opts.debug !== false);
20300
+ filter = this.asFilter(filter);
20301
+ const variables = {
20302
+ ...(opts?.variables || {}),
20303
+ offset: offset || 0,
20304
+ size: size || 100,
20305
+ sortBy: sortBy || 'id',
20306
+ sortDirection: sortDirection || 'asc',
20307
+ filter: filter && filter.asPodObject()
20308
+ };
20309
+ const now = Date.now();
20310
+ if (debug)
20311
+ console.debug(this._logPrefix + `Loading ${this._logTypeName}...`, variables);
20312
+ const withTotal = (!opts || opts.withTotal !== false) && this.queries.loadAllWithTotal && true;
20313
+ // use given query
20314
+ const query = opts?.query
20315
+ // Or get loadAll or loadAllWithTotal query
20316
+ || (withTotal ? this.queries.loadAllWithTotal : this.queries.loadAll);
20317
+ const { data, total } = await this.graphql.query({
20318
+ query,
20319
+ variables,
20320
+ error: { code: ErrorCodes.LOAD_DATA_ERROR, message: 'ERROR.LOAD_DATA_ERROR' },
20321
+ fetchPolicy: opts && opts.fetchPolicy || 'network-only'
20322
+ });
20323
+ const entities = (!opts || opts.toEntity !== false) ?
20324
+ (data || []).map(json => this.fromObject(json)) :
20325
+ (data || []);
20326
+ if (debug)
20327
+ console.debug(this._logPrefix + `${this._logTypeName} items loaded in ${Date.now() - now}ms`);
20328
+ const result = {
20329
+ data: entities,
20330
+ total: total || entities.length
20331
+ };
20332
+ // Add fetch more capability, if total was fetched
20333
+ if (withTotal) {
20334
+ const nextOffset = (offset || 0) + entities.length;
20335
+ if (nextOffset < result.total) {
20336
+ result.fetchMore = () => this.loadAll(nextOffset, size, sortBy, sortDirection, filter, opts);
20337
+ }
20338
+ }
20339
+ return result;
20340
+ }
20341
+ async countAll(filter, opts) {
20342
+ // If no countAll query
20343
+ if (!this.queries.countAll && !opts?.query) {
20344
+ // Fail if no loadAll (cannot count)
20345
+ if (!this.queries.loadAllWithTotal)
20346
+ throw new Error('Not implemented');
20347
+ // Use loadAll query, with size = 0;
20348
+ return (await this.loadAll(0, 1, null, null, filter, { withTotal: true }))?.total || 0;
20349
+ }
20350
+ filter = this.asFilter(filter);
20351
+ const variables = {
20352
+ ...(opts?.variables || {}),
20353
+ filter: filter && filter.asPodObject()
20354
+ };
20355
+ const query = opts?.query || this.queries.countAll;
20356
+ const res = await this.graphql.query({
20357
+ query,
20358
+ variables,
20359
+ error: { code: ErrorCodes.LOAD_DATA_ERROR, message: 'ERROR.COUNT_DATA_ERROR' },
20360
+ fetchPolicy: opts && opts.fetchPolicy || 'network-only'
20361
+ });
20362
+ return res.total;
20363
+ }
20364
+ canUserWrite(data, opts) {
20365
+ return !!data;
20366
+ }
20367
+ async saveAll(entities, opts) {
20368
+ if (isEmptyArray(entities))
20369
+ return entities; // Nothing to save: skip
20370
+ if (!this.mutations.saveAll) {
20371
+ if (!this.mutations.save)
20372
+ throw new Error('Not implemented');
20373
+ // Save one by one
20374
+ return chainPromises((entities || [])
20375
+ .map(entity => (() => this.save(entity, opts))));
20376
+ }
20377
+ let hasNewEntities = false;
20378
+ const json = entities.map(entity => {
20379
+ hasNewEntities = hasNewEntities || this.isNewFn(entity);
20380
+ this.fillDefaultProperties(entity);
20381
+ return this.asObject(entity);
20382
+ });
20383
+ const now = Date.now();
20384
+ if (this._debug)
20385
+ console.debug(this._logPrefix + `Saving all ${this._logTypeName}...`, json);
20386
+ await this.graphql.mutate({
20387
+ mutation: this.mutations.saveAll,
20388
+ refetchQueries: this.getRefetchQueriesForMutation(opts),
20389
+ awaitRefetchQueries: opts && opts.awaitRefetchQueries,
20390
+ variables: {
20391
+ data: json
20392
+ },
20393
+ error: { code: ErrorCodes.SAVE_DATA_ERROR, message: 'ERROR.SAVE_DATA_ERROR' },
20394
+ update: (proxy, { data }) => {
20395
+ const savedEntities = data && data.data;
20396
+ if (savedEntities) {
20397
+ // Update entities (id and update date)
20398
+ entities.forEach(entity => {
20399
+ const savedEntity = savedEntities.find(e => this.equalsFn(e, entity));
20400
+ this.copyIdAndUpdateDate(savedEntity, entity);
20401
+ });
20402
+ if (hasNewEntities && this.watchQueriesUpdatePolicy === 'update-cache') {
20403
+ // Update the cache
20404
+ this.insertIntoMutableCachedQueries(proxy, {
20405
+ queries: this.getLoadQueries(),
20406
+ data: savedEntities
20407
+ });
20408
+ }
20409
+ }
20410
+ if (opts && opts.update) {
20411
+ opts.update(proxy, { data });
20412
+ }
20413
+ if (this._debug)
20414
+ console.debug(`${this._logTypeName} saved in ${Date.now() - now}ms`, entities);
20415
+ }
20416
+ });
20417
+ return entities;
20418
+ }
20419
+ /**
20420
+ * Save a referential entity
20421
+ *
20422
+ * @param entity
20423
+ * @param opts
20424
+ */
20425
+ async save(entity, opts) {
20426
+ if (!this.mutations.save) {
20427
+ if (!this.mutations.saveAll)
20428
+ throw new Error('Not implemented');
20429
+ const data = await this.saveAll([entity], opts);
20430
+ return data && data[0];
20431
+ }
20432
+ // Fill default properties
20433
+ this.fillDefaultProperties(entity);
20434
+ // Transform into json
20435
+ const json = this.asObject(entity);
20436
+ const isNew = this.isNewFn(json);
20437
+ const now = Date.now();
20438
+ if (this._debug)
20439
+ console.debug(this._logPrefix + `Saving ${this._logTypeName}...`, json);
20440
+ await this.graphql.mutate({
20441
+ mutation: this.mutations.save,
20442
+ refetchQueries: this.getRefetchQueriesForMutation(opts),
20443
+ awaitRefetchQueries: opts && opts.awaitRefetchQueries,
20444
+ variables: {
20445
+ data: json
20446
+ },
20447
+ error: { code: ErrorCodes.SAVE_DATA_ERROR, message: 'ERROR.SAVE_DATA_ERROR' },
20448
+ update: (cache, { data }) => {
20449
+ // Update entity
20450
+ const savedEntity = data && data.data;
20451
+ this.copyIdAndUpdateDate(savedEntity, entity);
20452
+ // Insert into the cache
20453
+ if (isNew && this.watchQueriesUpdatePolicy === 'update-cache') {
20454
+ this.insertIntoMutableCachedQueries(cache, {
20455
+ queries: this.getLoadQueries(),
20456
+ data: savedEntity
20457
+ });
20458
+ }
20459
+ if (opts && opts.update) {
20460
+ opts.update(cache, { data });
20461
+ }
20462
+ if (this._debug)
20463
+ console.debug(this._logPrefix + `${entity.__typename} saved in ${Date.now() - now}ms`, entity);
20464
+ }
20465
+ });
20466
+ return entity;
20467
+ }
20468
+ /**
20469
+ * Delete referential entities
20470
+ */
20471
+ async deleteAll(entities, opts) {
20472
+ if (!this.mutations.deleteAll) {
20473
+ if (!this.mutations.delete)
20474
+ throw new Error('Not implemented');
20475
+ // Delete one by one
20476
+ return chainPromises((entities || [])
20477
+ .map(entity => (() => this.delete(entity, opts))));
20478
+ }
20479
+ // Filter saved entities
20480
+ entities = entities && entities.filter(e => isNotNil(e.id));
20481
+ // Nothing to save: skip
20482
+ if (isEmptyArray(entities))
20483
+ return;
20484
+ const ids = entities.map(t => t.id);
20485
+ const now = Date.now();
20486
+ if (this._debug)
20487
+ console.debug(this._logPrefix + `Deleting ${this._logTypeName}...`, ids);
20488
+ await this.graphql.mutate({
20489
+ mutation: this.mutations.deleteAll,
20490
+ refetchQueries: this.getRefetchQueriesForMutation(opts),
20491
+ awaitRefetchQueries: opts && opts.awaitRefetchQueries,
20492
+ variables: { ids },
20493
+ error: { code: ErrorCodes.DELETE_DATA_ERROR, message: 'ERROR.DELETE_DATA_ERROR' },
20494
+ update: (cache, res) => {
20495
+ // Remove from cache
20496
+ if (this.watchQueriesUpdatePolicy === 'update-cache') {
20497
+ this.removeFromMutableCachedQueriesByIds(cache, {
20498
+ queries: this.getLoadQueries(),
20499
+ ids
20500
+ });
20501
+ }
20502
+ if (opts && opts.update) {
20503
+ opts.update(cache, res);
20504
+ }
20505
+ if (this._debug)
20506
+ console.debug(this._logPrefix + `${this._logTypeName} deleted in ${Date.now() - now}ms`);
20507
+ }
20508
+ });
20509
+ }
20510
+ /**
20511
+ * Delete a referential entity
20512
+ */
20513
+ async delete(entity, opts) {
20514
+ if (!this.mutations.delete) {
20515
+ if (!this.mutations.deleteAll)
20516
+ throw new Error('Not implemented');
20517
+ const data = await this.deleteAll([entity], opts);
20518
+ return data && data[0];
20519
+ }
20520
+ // Nothing to save: skip
20521
+ if (!entity || isNil(entity.id))
20522
+ return;
20523
+ const id = entity.id;
20524
+ const now = new Date();
20525
+ if (this._debug)
20526
+ console.debug(this._logPrefix + `Deleting ${this._logTypeName} {${id}}...`);
20527
+ await this.graphql.mutate({
20528
+ mutation: this.mutations.delete,
20529
+ refetchQueries: this.getRefetchQueriesForMutation(opts),
20530
+ awaitRefetchQueries: opts && opts.awaitRefetchQueries,
20531
+ variables: { id },
20532
+ error: { code: ErrorCodes.DELETE_DATA_ERROR, message: 'ERROR.DELETE_DATA_ERROR' },
20533
+ update: (proxy, res) => {
20534
+ // Remove from cache
20535
+ if (this.watchQueriesUpdatePolicy === 'update-cache') {
20536
+ this.removeFromMutableCachedQueriesByIds(proxy, {
20537
+ queries: this.getLoadQueries(),
20538
+ ids: [id]
20539
+ });
20540
+ }
20541
+ if (opts && opts.update) {
20542
+ opts.update(proxy, res);
20543
+ }
20544
+ if (this._debug)
20545
+ console.debug(this._logPrefix + `${this._logTypeName} deleted in ${new Date().getTime() - now.getTime()}ms`);
20546
+ }
20547
+ });
20548
+ }
20549
+ listenChanges(id, opts) {
20550
+ if (isNil(id))
20551
+ throw Error('Missing argument \'id\' ');
20552
+ if (!this.subscriptions.listenChanges) {
20553
+ console.warn(`${this.constructor.name}.listenChanges() not implemented yet. Will empty observable`);
20554
+ return of();
20555
+ }
20556
+ const variables = opts?.variables || {
20557
+ id,
20558
+ interval: toNumber(opts && opts.interval, 0) // no timer by default
20559
+ };
20560
+ if (this._debug)
20561
+ console.debug(this._logPrefix + `[WS] Listening for changes on ${this._logTypeName} {${id}}...`);
20562
+ return this.graphql.subscribe({
20563
+ query: opts?.query || this.subscriptions.listenChanges,
20564
+ fetchPolicy: opts?.fetchPolicy || 'no-cache',
20565
+ variables,
20566
+ error: {
20567
+ code: ErrorCodes.SUBSCRIBE_DATA_ERROR,
20568
+ message: 'ERROR.SUBSCRIBE_DATA_ERROR'
20569
+ }
20570
+ })
20571
+ .pipe(map(({ data }) => {
20572
+ const entity = (!opts || opts.toEntity !== false) ? data && this.fromObject(data) : data;
20573
+ //if (entity && this._debug) console.debug(this._logPrefix + `[WS] Received changes on ${this._logTypeName} {${id}}`, entity);
20574
+ // TODO: missing = deleted ?
20575
+ if (!entity)
20576
+ console.warn(this._logPrefix + `[WS] Received deletion on ${this._logTypeName} {${id}} - TODO check implementation`);
20577
+ return entity;
20578
+ }));
20579
+ }
20580
+ copyIdAndUpdateDate(source, target) {
20581
+ if (!source)
20582
+ return;
20583
+ // Update (id and updateDate)
20584
+ EntityUtils.copyIdAndUpdateDate(source, target);
20585
+ }
20586
+ fromObject(source) {
20587
+ if (!source)
20588
+ return source;
20589
+ const target = new this.dataType();
20590
+ target.fromObject(source);
20591
+ return target;
20592
+ }
20593
+ asFilter(source) {
20594
+ return EntityFilterUtils.fromObject(source, this.filterType);
20595
+ }
20596
+ /* -- protected functions -- */
20597
+ async ngOnStart() {
20598
+ // No limit for updatable watch queries, if desktop
20599
+ if (!this.platform.mobile) {
20600
+ this._mutableWatchQueriesMaxCount = -1;
20601
+ }
20602
+ }
20603
+ fillDefaultProperties(source) {
20604
+ // Can be override by subclasses
20605
+ }
20606
+ asObject(entity, opts) {
20607
+ // Can be override by subclasses
20608
+ return entity.asObject(opts);
20609
+ }
20610
+ getRefetchQueriesForMutation(opts) {
20611
+ if (opts && opts.refetchQueries)
20612
+ return opts.refetchQueries;
20613
+ // Skip if update policy not used refecth queries
20614
+ if (this.watchQueriesUpdatePolicy !== 'refetch-queries')
20615
+ return undefined;
20616
+ const queries = this.getLoadQueries();
20617
+ if (!queries.length)
20618
+ return undefined; // Skip if empty
20619
+ // Find the refetch queries definition
20620
+ return this.findRefetchQueries({ queries });
20621
+ }
20622
+ getLoadQueries() {
20623
+ return [this.queries.loadAll, this.queries.loadAllWithTotal].filter(isNotNil);
20624
+ }
20625
+ getLoadQueryNames() {
20626
+ return ['LoadAll', 'LoadAllWithTotal'];
20627
+ }
20628
+ }
20629
+ BaseEntityService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseEntityService, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
20630
+ BaseEntityService.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.3.0", type: BaseEntityService, usesInheritance: true, ngImport: i0 });
20631
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseEntityService, decorators: [{
20632
+ type: Directive
20633
+ }], ctorParameters: function () { return [{ type: GraphqlService }, { type: PlatformService }, { type: undefined }, { type: undefined }, { type: undefined }]; } });
20634
+
20635
+ const APP_NAMED_FILTER_SERVICE = new InjectionToken('namedFilterService');
20636
+ // @dynamic
20637
+ class AbstractNamedFilterService extends BaseEntityService {
20638
+ constructor(graphql, platform, account, dataType, filterType, options) {
20639
+ super(graphql, platform, dataType, filterType, {
20640
+ defaultSortBy: 'name',
20641
+ defaultSortDirection: 'asc',
20642
+ ...options
20643
+ });
20644
+ this.graphql = graphql;
20645
+ this.platform = platform;
20646
+ this.account = account;
20647
+ this.dataType = dataType;
20648
+ this.filterType = filterType;
20649
+ this.options = options;
20650
+ this._logPrefix = '[named-filter-service] ';
20651
+ }
20652
+ async suggest(value, filter) {
20653
+ if (EntityUtils.isNotEmpty(value, 'id'))
20654
+ return { data: [value] };
20655
+ value = (typeof value === 'string' && value !== '*' && value) || undefined;
20656
+ return await this.loadAll(0, 1000, 'name', 'asc', this.asSuggestFilter(value, filter), {
20657
+ withTotal: true,
20658
+ });
20659
+ }
20660
+ asSuggestFilter(searchText, filter) {
20661
+ return {
20662
+ ...filter,
20663
+ searchText
20664
+ };
20665
+ }
20666
+ asFilter(source) {
20667
+ const filter = super.asFilter(source);
20668
+ // Default filter on current user
20669
+ if (isNil(filter.recorderPersonId) && isNil(filter.recorderDepartmentId))
20670
+ filter.recorderPersonId = this.account.person.id;
20671
+ return filter;
20672
+ }
20673
+ fillDefaultProperties(source) {
20674
+ super.fillDefaultProperties(source);
20675
+ // Default recorder
20676
+ if (isNil(source.recorderPersonId) && isNil(source.recorderDepartmentId))
20677
+ source.recorderPersonId = this.account.person.id;
20678
+ // Stringify content
20679
+ if (typeof source.content === 'object') {
20680
+ source.content = JSON.stringify(source.content);
20681
+ }
20682
+ }
20683
+ }
20684
+ AbstractNamedFilterService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AbstractNamedFilterService, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
20685
+ AbstractNamedFilterService.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.3.0", type: AbstractNamedFilterService, usesInheritance: true, ngImport: i0 });
20686
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AbstractNamedFilterService, decorators: [{
20687
+ type: Directive
20688
+ }], ctorParameters: function () { return [{ type: GraphqlService }, { type: PlatformService }, { type: AccountService }, { type: undefined }, { type: undefined }, { type: undefined }]; } });
20689
+
20690
+ let NamedFilter = class NamedFilter extends Entity {
20691
+ fromObject(source, opts) {
20692
+ super.fromObject(source, opts);
20693
+ this.name = source.name;
20694
+ this.entityName = source.entityName;
20695
+ this.recorderPersonId = source.recorderPersonId;
20696
+ this.recorderDepartmentId = source.recorderDepartmentId;
20697
+ try {
20698
+ this.content = typeof source?.content === 'string' ? JSON.parse(source?.content) : source?.content || undefined;
20699
+ }
20700
+ catch (e) {
20701
+ console.error(`[named-filter] can't parse content json`, source);
20702
+ throw e;
20703
+ }
20704
+ }
20705
+ };
20706
+ NamedFilter = __decorate([
20707
+ EntityClass({ typename: 'NamedFilterVO' })
20708
+ ], NamedFilter);
20709
+ let NamedFilterFilter = class NamedFilterFilter extends EntityFilter {
20710
+ constructor() {
20711
+ super(...arguments);
20712
+ this.searchText = null;
20713
+ this.entityName = null;
20714
+ this.recorderPersonId = null;
20715
+ this.recorderDepartmentId = null;
20716
+ }
20717
+ fromObject(source, opts) {
20718
+ super.fromObject(source, opts);
20719
+ this.searchText = source.searchText;
20720
+ this.entityName = source.entityName;
20721
+ this.recorderPersonId = source.recorderPersonId;
20722
+ this.recorderDepartmentId = source.recorderDepartmentId;
20723
+ }
20724
+ };
20725
+ NamedFilterFilter = __decorate([
20726
+ EntityClass({ typename: 'NamedFilterFilterVO' })
20727
+ ], NamedFilterFilter);
20728
+
20729
+ class NamedFilterSelector extends AppForm {
20730
+ constructor(injector, formBuilder, cd, toastController, popoverController, dataService) {
20731
+ super(injector, formBuilder.group({ namedFilter: [null] }));
20732
+ this.injector = injector;
20733
+ this.formBuilder = formBuilder;
20734
+ this.cd = cd;
20735
+ this.toastController = toastController;
20736
+ this.popoverController = popoverController;
20737
+ this.dataService = dataService;
20738
+ this.logPrefix = '[named-filter-selector]';
20739
+ this._deleteDisabled = false;
20740
+ this.showButtons = true;
20741
+ this.exportFileNamePrefix = 'filter';
20742
+ this.filterSelected = new EventEmitter();
20743
+ this.filterDeleted = new EventEmitter();
20744
+ }
20745
+ get isNew() {
20746
+ return isNilOrNaN(this.value?.namedFilter?.id);
20747
+ }
20748
+ get saveDisabled() {
20749
+ return !this.dirty || isNilOrBlank(this.value.namedFilter);
20750
+ }
20751
+ get deleteDisabled() {
20752
+ return this._deleteDisabled || this.isNew;
20753
+ }
20754
+ get dirty() {
20755
+ return super.dirty || this.filterFormDirty;
20756
+ }
20757
+ ngOnInit() {
20758
+ this.registerAutocompleteField('namedFilter', {
20759
+ suggestFn: (value, filter) => this.dataService.suggest(value, filter),
20760
+ attributes: ['name'],
20761
+ filter: {
20762
+ entityName: this.entityName,
20763
+ },
20764
+ });
20765
+ this.registerSubscription(this.form.controls.namedFilter.valueChanges.pipe(debounceTime(100), distinctUntilChanged()).subscribe((value) => this.onFilterChanges(value)));
20766
+ }
20767
+ markForCheck() {
20768
+ this.cd.markForCheck();
20769
+ }
20770
+ async save() {
20771
+ const filterContent = this.filterContentProvider();
20772
+ if (this.debug)
20773
+ console.debug(`${this.logPrefix} save named filter`, filterContent);
20774
+ if (isNilOrBlank(filterContent))
20775
+ return;
20776
+ const currentSelected = this.value.namedFilter;
20777
+ let result;
20778
+ if (typeof currentSelected === 'string') {
20779
+ result = NamedFilter.fromObject({
20780
+ name: currentSelected,
20781
+ entityName: this.entityName,
20782
+ content: filterContent,
20783
+ });
20784
+ }
20785
+ else {
20786
+ result = {
20787
+ ...currentSelected,
20788
+ content: filterContent,
20789
+ };
20790
+ }
20791
+ const savedEntity = await this.dataService.save(result);
20792
+ this.setControlValue(savedEntity);
20793
+ this.markAsPristine();
20794
+ }
20795
+ async delete() {
20796
+ const data = this.value.namedFilter;
20797
+ if (this.debug)
20798
+ console.debug(`${this.logPrefix} delete named filter`, data);
20799
+ if (typeof data === 'object') {
20800
+ await this.dataService.delete(data);
20801
+ this.reset();
20802
+ this.autocompleteField.blurred.emit();
20803
+ this.filterDeleted.emit(data);
20804
+ Toasts.show(this.toastController, this.translate, {
20805
+ type: 'info',
20806
+ message: 'COMMON.NAMED_FILTER.DELETED',
20807
+ });
20808
+ }
20809
+ }
20810
+ async export() {
20811
+ const id = this.autocompleteField.value?.id;
20812
+ if (this.debug)
20813
+ console.debug(`${this.logPrefix} export named filter`, id);
20814
+ if (!id)
20815
+ return;
20816
+ const data = await this.dataService.load(id, { withContent: true });
20817
+ if (data) {
20818
+ FilesUtils.writeTextToFile(JSON.stringify(data, null, 2), {
20819
+ type: 'application/json',
20820
+ filename: `${this.exportFileNamePrefix}-${data.entityName}-${data.name}.json`,
20821
+ });
20822
+ }
20823
+ else {
20824
+ Toasts.show(this.toastController, this.translate, {
20825
+ type: 'error',
20826
+ message: 'COMMON.NAMED_FILTER.NOT_FOUND',
20827
+ });
20828
+ }
20829
+ }
20830
+ async import(event) {
20831
+ const result = await FilesUtils.showUploadPopover(this.popoverController, event, {
20832
+ uploadFn: (file) => FilesUtils.readAsText(file),
20833
+ title: 'COMMON.NAMED_FILTER.IMPORT',
20834
+ fileExtension: '.json',
20835
+ uniqueFile: true,
20836
+ instantUpload: true,
20837
+ });
20838
+ if (!result)
20839
+ return;
20840
+ try {
20841
+ const data = JSON.parse(result.data[0].response.body);
20842
+ if (this.debug)
20843
+ console.debug(`${this.logPrefix} import named filter`, data);
20844
+ this.setControlValue(data);
20845
+ this.filterSelected.emit(data.content);
20846
+ }
20847
+ catch (e) {
20848
+ console.error(e);
20849
+ Toasts.show(this.toastController, this.translate, {
20850
+ type: 'error',
20851
+ message: 'COMMON.NAMED_FILTER.IMPORT_FAILED',
20852
+ });
20853
+ }
20854
+ }
20855
+ async onFilterChanges(value) {
20856
+ if (this.debug)
20857
+ console.debug(`${this.logPrefix} filter change`, value);
20858
+ if (typeof value === 'object' && value?.id && isNotNil(value.id)) {
20859
+ try {
20860
+ const data = await this.dataService.load(value.id, { withContent: true });
20861
+ this._deleteDisabled = !this.dataService.canUserWrite(data);
20862
+ const filter = isNotNilOrBlank(data?.content) ? data.content : null;
20863
+ if (filter) {
20864
+ this.filterSelected.emit(filter);
20865
+ this.markAsPristine();
20866
+ }
20867
+ }
20868
+ catch (e) {
20869
+ Toasts.show(this.toastController, this.translate, {
20870
+ type: 'error',
20871
+ message: 'COMMON.NAMED_FILTER.LOAD_FAILED',
20872
+ });
20873
+ this.autocompleteField.applyImplicitValue = false;
20874
+ }
20875
+ }
20876
+ else {
20877
+ this.autocompleteField.applyImplicitValue = false;
20878
+ }
20879
+ }
20880
+ setControlValue(value) {
20881
+ this.setValue({ namedFilter: value });
20882
+ this.autocompleteField.blurred.emit();
20883
+ }
20884
+ }
20885
+ NamedFilterSelector.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NamedFilterSelector, deps: [{ token: i0.Injector }, { token: i1$3.UntypedFormBuilder }, { token: i0.ChangeDetectorRef }, { token: i1.ToastController }, { token: i1.PopoverController }, { token: APP_NAMED_FILTER_SERVICE }], target: i0.ɵɵFactoryTarget.Component });
20886
+ NamedFilterSelector.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NamedFilterSelector, selector: "app-named-filter-selector[entityName]", inputs: { entityName: "entityName", filterContentProvider: "filterContentProvider", filterFormDirty: "filterFormDirty", showButtons: "showButtons", exportFileNamePrefix: "exportFileNamePrefix" }, outputs: { filterSelected: "filterSelected", filterDeleted: "filterDeleted" }, viewQueries: [{ propertyName: "autocompleteField", first: true, predicate: MatAutocompleteField, descendants: true }], usesInheritance: true, ngImport: i0, template: "<form class=\"form-container\" [formGroup]=\"form\">\n <mat-autocomplete-field\n formControlName=\"namedFilter\"\n [config]=\"autocompleteFields.namedFilter\"\n [placeholder]=\"'COMMON.NAMED_FILTER.TITLE' | translate\"\n [clearable]=\"true\"\n >\n <ng-container matSuffix *ngIf=\"showButtons\">\n <button\n mat-icon-button\n [color]=\"'primary'\"\n [disabled]=\"saveDisabled\"\n [title]=\"'COMMON.NAMED_FILTER.SAVE' | translate\"\n (click)=\"save()\"\n >\n <mat-icon>save</mat-icon>\n <mat-icon *ngIf=\"isNew\" class=\"icon-secondary\" style=\"left: 12px; top: 0px\">add</mat-icon>\n </button>\n <button\n mat-icon-button\n [disabled]=\"deleteDisabled\"\n [title]=\"'COMMON.NAMED_FILTER.DELETE' | translate\"\n (click)=\"delete()\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n <button mat-icon-button [matMenuTriggerFor]=\"moreMenu\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #moreMenu=\"matMenu\">\n <button mat-menu-item (click)=\"export()\" [disabled]=\"isNew\">\n <ion-label>\n <mat-icon>file_download</mat-icon>\n <span>{{ 'COMMON.NAMED_FILTER.EXPORT' | translate }}</span>\n </ion-label>\n </button>\n <button mat-menu-item (click)=\"import($event)\">\n <ion-label>\n <mat-icon>file_upload</mat-icon>\n <span>{{ 'COMMON.NAMED_FILTER.IMPORT' | translate }}</span>\n </ion-label>\n </button>\n </mat-menu>\n </ng-container>\n </mat-autocomplete-field>\n</form>\n", styles: [".filter-name-container{background:transparent!important;overflow:hidden}\n"], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: MatAutocompleteField, selector: "mat-autocomplete-field", inputs: ["equals", "logPrefix", "formControl", "formControlName", "floatLabel", "appearance", "placeholder", "suggestFn", "required", "mobile", "clearable", "debounceTime", "displayWith", "displayAttributes", "displayColumnSizes", "displayColumnNames", "highlightAccent", "showAllOnFocus", "showPanelOnFocus", "autofocus", "config", "i18nPrefix", "noResultMessage", "class", "panelWidth", "matAutocompletePosition", "multiple", "fetchMoreThreshold", "suggestLengthThreshold", "showLoadingSpinner", "debug", "showSearchBar", "stickySearchBar", "applyImplicitValue", "filter", "readonly", "tabindex", "items"], outputs: ["click", "blur", "focus", "dropButtonClick", "keydown.escape", "keyup.enter"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i6$1.MatSuffix, selector: "[matSuffix]" }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i8$1.MatMenu, selector: "mat-menu", exportAs: ["matMenu"] }, { kind: "component", type: i8$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["disabled", "disableRipple", "role"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i8$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", exportAs: ["matMenuTrigger"] }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
20887
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NamedFilterSelector, decorators: [{
20888
+ type: Component,
20889
+ args: [{ selector: 'app-named-filter-selector[entityName]', changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"form-container\" [formGroup]=\"form\">\n <mat-autocomplete-field\n formControlName=\"namedFilter\"\n [config]=\"autocompleteFields.namedFilter\"\n [placeholder]=\"'COMMON.NAMED_FILTER.TITLE' | translate\"\n [clearable]=\"true\"\n >\n <ng-container matSuffix *ngIf=\"showButtons\">\n <button\n mat-icon-button\n [color]=\"'primary'\"\n [disabled]=\"saveDisabled\"\n [title]=\"'COMMON.NAMED_FILTER.SAVE' | translate\"\n (click)=\"save()\"\n >\n <mat-icon>save</mat-icon>\n <mat-icon *ngIf=\"isNew\" class=\"icon-secondary\" style=\"left: 12px; top: 0px\">add</mat-icon>\n </button>\n <button\n mat-icon-button\n [disabled]=\"deleteDisabled\"\n [title]=\"'COMMON.NAMED_FILTER.DELETE' | translate\"\n (click)=\"delete()\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n <button mat-icon-button [matMenuTriggerFor]=\"moreMenu\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #moreMenu=\"matMenu\">\n <button mat-menu-item (click)=\"export()\" [disabled]=\"isNew\">\n <ion-label>\n <mat-icon>file_download</mat-icon>\n <span>{{ 'COMMON.NAMED_FILTER.EXPORT' | translate }}</span>\n </ion-label>\n </button>\n <button mat-menu-item (click)=\"import($event)\">\n <ion-label>\n <mat-icon>file_upload</mat-icon>\n <span>{{ 'COMMON.NAMED_FILTER.IMPORT' | translate }}</span>\n </ion-label>\n </button>\n </mat-menu>\n </ng-container>\n </mat-autocomplete-field>\n</form>\n", styles: [".filter-name-container{background:transparent!important;overflow:hidden}\n"] }]
20890
+ }], ctorParameters: function () { return [{ type: i0.Injector }, { type: i1$3.UntypedFormBuilder }, { type: i0.ChangeDetectorRef }, { type: i1.ToastController }, { type: i1.PopoverController }, { type: undefined, decorators: [{
20891
+ type: Inject,
20892
+ args: [APP_NAMED_FILTER_SERVICE]
20893
+ }] }]; }, propDecorators: { entityName: [{
20894
+ type: Input
20895
+ }], filterContentProvider: [{
20896
+ type: Input
20897
+ }], filterFormDirty: [{
20898
+ type: Input
20899
+ }], showButtons: [{
20900
+ type: Input
20901
+ }], exportFileNamePrefix: [{
20902
+ type: Input
20903
+ }], filterSelected: [{
20904
+ type: Output
20905
+ }], filterDeleted: [{
20906
+ type: Output
20907
+ }], autocompleteField: [{
20908
+ type: ViewChild,
20909
+ args: [MatAutocompleteField]
20910
+ }] } });
20911
+
20912
+ class SharedNamedFilterModule {
20913
+ }
20914
+ SharedNamedFilterModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: SharedNamedFilterModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
20915
+ SharedNamedFilterModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: SharedNamedFilterModule, declarations: [NamedFilterSelector], imports: [CommonModule, IonicModule, i1$2.TranslateModule, SharedMatAutocompleteModule, ReactiveFormsModule, SharedMaterialModule], exports: [NamedFilterSelector] });
20916
+ SharedNamedFilterModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: SharedNamedFilterModule, imports: [CommonModule, IonicModule, TranslateModule.forChild(), SharedMatAutocompleteModule, ReactiveFormsModule, SharedMaterialModule] });
20917
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: SharedNamedFilterModule, decorators: [{
20918
+ type: NgModule,
20919
+ args: [{
20920
+ imports: [CommonModule, IonicModule, TranslateModule.forChild(), SharedMatAutocompleteModule, ReactiveFormsModule, SharedMaterialModule],
20921
+ declarations: [NamedFilterSelector],
20922
+ exports: [NamedFilterSelector],
20923
+ }]
20924
+ }] });
20925
+
20113
20926
  function scrollFactory(overlay) {
20114
20927
  return () => overlay.scrollStrategies.close();
20115
20928
  }
@@ -20172,7 +20985,8 @@ SharedModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "
20172
20985
  SharedDirectivesModule,
20173
20986
  SharedPipesModule,
20174
20987
  SharedHotkeysModule,
20175
- SharedToolbarModule], exports: [CommonModule,
20988
+ SharedToolbarModule,
20989
+ SharedNamedFilterModule], exports: [CommonModule,
20176
20990
  IonicModule,
20177
20991
  TranslateModule,
20178
20992
  ReactiveFormsModule,
@@ -20187,6 +21001,7 @@ SharedModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "
20187
21001
  SharedPipesModule,
20188
21002
  SharedHotkeysModule,
20189
21003
  SharedToolbarModule,
21004
+ SharedNamedFilterModule,
20190
21005
  // Components
20191
21006
  AppFormField,
20192
21007
  AppLoadingSpinner,
@@ -20207,7 +21022,8 @@ SharedModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "
20207
21022
  SharedDirectivesModule,
20208
21023
  SharedPipesModule,
20209
21024
  SharedHotkeysModule,
20210
- SharedToolbarModule, CommonModule,
21025
+ SharedToolbarModule,
21026
+ SharedNamedFilterModule, CommonModule,
20211
21027
  IonicModule,
20212
21028
  TranslateModule,
20213
21029
  ReactiveFormsModule,
@@ -20221,7 +21037,8 @@ SharedModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "
20221
21037
  SharedDirectivesModule,
20222
21038
  SharedPipesModule,
20223
21039
  SharedHotkeysModule,
20224
- SharedToolbarModule] });
21040
+ SharedToolbarModule,
21041
+ SharedNamedFilterModule] });
20225
21042
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: SharedModule, decorators: [{
20226
21043
  type: NgModule,
20227
21044
  args: [{
@@ -20239,7 +21056,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImpor
20239
21056
  SharedDirectivesModule,
20240
21057
  SharedPipesModule,
20241
21058
  SharedHotkeysModule,
20242
- SharedToolbarModule
21059
+ SharedToolbarModule,
21060
+ SharedNamedFilterModule
20243
21061
  ],
20244
21062
  declarations: [
20245
21063
  // Components
@@ -20266,6 +21084,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImpor
20266
21084
  SharedPipesModule,
20267
21085
  SharedHotkeysModule,
20268
21086
  SharedToolbarModule,
21087
+ SharedNamedFilterModule,
20269
21088
  // Components
20270
21089
  AppFormField,
20271
21090
  AppLoadingSpinner,
@@ -21579,157 +22398,66 @@ class ResizableComponent {
21579
22398
  return;
21580
22399
  if (this.debug)
21581
22400
  console.info(`resize:${width}`);
21582
- this.minWidth = width + 'px';
21583
- this.maxWidth = width + 'px';
21584
- if (!opts || opts.emitEvent) {
21585
- this.sizeChanged.emit(width);
21586
- }
21587
- }
21588
- onFit(opts) {
21589
- if (!this.resizable)
21590
- return;
21591
- if (this.debug)
21592
- console.info('fit');
21593
- this.width = 'auto';
21594
- this.minWidth = null;
21595
- this.maxWidth = null;
21596
- if (!opts || opts.emitEvent) {
21597
- this.sizeChanged.emit(undefined);
21598
- }
21599
- }
21600
- }
21601
- ResizableComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ResizableComponent, deps: [{ token: i6$5.MatColumnDef, optional: true }], target: i0.ɵɵFactoryTarget.Component });
21602
- ResizableComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: ResizableComponent, selector: "th[resizable]", inputs: { resizable: "resizable" }, outputs: { sizeChanged: "sizeChanged" }, host: { properties: { "style.width": "this.width", "style.min-width": "this.minWidth", "style.max-width": "this.maxWidth" } }, ngImport: i0, template: "<div class=\"wrapper\">\n <div class=\"content\">\n <ng-content></ng-content>\n </div>\n <div class=\"bar\" [class.cdk-visually-hidden]=\"!resizable\" (resizable)=\"onResize($event)\" (fit)=\"onFit()\"></div>\n</div>\n", styles: [":host:last-child .bar{display:none}.wrapper{display:flex;justify-content:flex-end;align-items:center}.content{flex:1}.bar{height:var(--app-table-header-height, 55px);width:10px;justify-self:flex-end;border-left:4px solid transparent;border-right:4px solid transparent;background:var(--ion-color-secondary);background-clip:content-box;cursor:ew-resize;opacity:0;transition:opacity .3s}.bar:hover,.bar:active{opacity:1}:host-context(.mat-table-sticky) .bar{width:12px!important}\n"], dependencies: [{ kind: "directive", type: ResizableDirective, selector: "[resizable]", outputs: ["resizable", "fit"] }] });
21603
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ResizableComponent, decorators: [{
21604
- type: Component,
21605
- args: [{ selector: 'th[resizable]', template: "<div class=\"wrapper\">\n <div class=\"content\">\n <ng-content></ng-content>\n </div>\n <div class=\"bar\" [class.cdk-visually-hidden]=\"!resizable\" (resizable)=\"onResize($event)\" (fit)=\"onFit()\"></div>\n</div>\n", styles: [":host:last-child .bar{display:none}.wrapper{display:flex;justify-content:flex-end;align-items:center}.content{flex:1}.bar{height:var(--app-table-header-height, 55px);width:10px;justify-self:flex-end;border-left:4px solid transparent;border-right:4px solid transparent;background:var(--ion-color-secondary);background-clip:content-box;cursor:ew-resize;opacity:0;transition:opacity .3s}.bar:hover,.bar:active{opacity:1}:host-context(.mat-table-sticky) .bar{width:12px!important}\n"] }]
21606
- }], ctorParameters: function () { return [{ type: i6$5.MatColumnDef, decorators: [{
21607
- type: Optional
21608
- }] }]; }, propDecorators: { resizable: [{
21609
- type: Input
21610
- }], sizeChanged: [{
21611
- type: Output
21612
- }], width: [{
21613
- type: HostBinding,
21614
- args: ['style.width']
21615
- }], minWidth: [{
21616
- type: HostBinding,
21617
- args: ['style.min-width']
21618
- }], maxWidth: [{
21619
- type: HostBinding,
21620
- args: ['style.max-width']
21621
- }] } });
21622
-
21623
- class ResizableModule {
21624
- }
21625
- ResizableModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ResizableModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
21626
- ResizableModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: ResizableModule, declarations: [ResizableComponent, ResizableDirective], exports: [ResizableComponent, ResizableDirective] });
21627
- ResizableModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ResizableModule });
21628
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ResizableModule, decorators: [{
21629
- type: NgModule,
21630
- args: [{
21631
- declarations: [ResizableComponent, ResizableDirective],
21632
- exports: [ResizableComponent, ResizableDirective],
21633
- }]
21634
- }] });
21635
-
21636
- function mergeLoadResult(res1, res2) {
21637
- return {
21638
- data: (res1.data || []).concat(...res2.data),
21639
- total: ((res1.total || res1.data?.length || 0) + (res2.total || res2.data?.length || 0))
21640
- };
21641
- }
21642
-
21643
- class EntityFilter extends Entity {
21644
- /**
21645
- * Compose some filter functions: all should return true
21646
- * @param filterFns
21647
- */
21648
- static composeFilters(filterFns) {
21649
- if (isEmptyArray(filterFns))
21650
- return undefined;
21651
- return (entity) => (filterFns || []).every(fn => fn(entity));
21652
- }
21653
- static compose(filters) {
21654
- const filterFns = (filters || []).reduce((res, f) => {
21655
- const fns = f.buildFilter();
21656
- if (!fns)
21657
- return res;
21658
- return res.concat(fns);
21659
- }, []);
21660
- return this.composeFilters(filterFns);
21661
- }
21662
- /**
21663
- * Compose some filter functions: all should return true
21664
- * @param filterFns
21665
- */
21666
- static composeFns(filterFns) {
21667
- if (isEmptyArray(filterFns))
21668
- return undefined;
21669
- return (entity) => (filterFns || []).every(fn => fn(entity));
21670
- }
21671
- constructor(__typename) {
21672
- super(__typename);
21673
- }
21674
- /**
21675
- * Clean a filter, before sending to the pod (e.g convert dates, remove internal properties, etc.)
21676
- */
21677
- asPodObject() {
21678
- return this.asObject(MINIFY_ENTITY_FOR_POD);
21679
- }
21680
- isEmpty() {
21681
- return this.countNotEmptyCriteria() === 0;
21682
- }
21683
- countNotEmptyCriteria() {
21684
- const json = this.asPodObject();
21685
- return Object.keys(json)
21686
- .filter(key => this.isCriteriaNotEmpty(key, json[key]))
21687
- .length;
21688
- }
21689
- asFilterFn() {
21690
- const filterFns = this.buildFilter();
21691
- return EntityFilter.composeFns(filterFns);
21692
- }
21693
- buildFilter() {
21694
- // Can be completed by subclasses
21695
- return [];
21696
- }
21697
- isCriteriaNotEmpty(key, value) {
21698
- // Can be overridden by subclasses
21699
- return value !== undefined
21700
- && value !== null
21701
- && (
21702
- // not empty string
21703
- (typeof value === 'string' && value.trim() !== '')
21704
- // valid number
21705
- || (typeof value === 'number' && !isNaN(value))
21706
- // valid boolean
21707
- || (typeof value === 'boolean')
21708
- // not empty array
21709
- || (Array.isArray(value) && value.length > 0)
21710
- // entity with an id
21711
- || ReferentialUtils.isNotEmpty(value));
21712
- }
21713
- }
21714
- class EntityFilterUtils {
21715
- static isEntityFilter(obj) {
21716
- return obj && obj.asPodObject && obj.asFilterFn && true || false;
21717
- }
21718
- static fromObject(source, filterType) {
21719
- if (!source)
21720
- return source;
21721
- if (isInstanceOf(source, filterType))
21722
- return source;
21723
- const target = new filterType();
21724
- if (EntityFilterUtils.isEntityFilter(target)) {
21725
- target.fromObject(source);
22401
+ this.minWidth = width + 'px';
22402
+ this.maxWidth = width + 'px';
22403
+ if (!opts || opts.emitEvent) {
22404
+ this.sizeChanged.emit(width);
21726
22405
  }
21727
- else {
21728
- Object.assign(target, source);
22406
+ }
22407
+ onFit(opts) {
22408
+ if (!this.resizable)
22409
+ return;
22410
+ if (this.debug)
22411
+ console.info('fit');
22412
+ this.width = 'auto';
22413
+ this.minWidth = null;
22414
+ this.maxWidth = null;
22415
+ if (!opts || opts.emitEvent) {
22416
+ this.sizeChanged.emit(undefined);
21729
22417
  }
21730
- return target;
21731
22418
  }
21732
22419
  }
22420
+ ResizableComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ResizableComponent, deps: [{ token: i6$5.MatColumnDef, optional: true }], target: i0.ɵɵFactoryTarget.Component });
22421
+ ResizableComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: ResizableComponent, selector: "th[resizable]", inputs: { resizable: "resizable" }, outputs: { sizeChanged: "sizeChanged" }, host: { properties: { "style.width": "this.width", "style.min-width": "this.minWidth", "style.max-width": "this.maxWidth" } }, ngImport: i0, template: "<div class=\"wrapper\">\n <div class=\"content\">\n <ng-content></ng-content>\n </div>\n <div class=\"bar\" [class.cdk-visually-hidden]=\"!resizable\" (resizable)=\"onResize($event)\" (fit)=\"onFit()\"></div>\n</div>\n", styles: [":host:last-child .bar{display:none}.wrapper{display:flex;justify-content:flex-end;align-items:center}.content{flex:1}.bar{height:var(--app-table-header-height, 55px);width:10px;justify-self:flex-end;border-left:4px solid transparent;border-right:4px solid transparent;background:var(--ion-color-secondary);background-clip:content-box;cursor:ew-resize;opacity:0;transition:opacity .3s}.bar:hover,.bar:active{opacity:1}:host-context(.mat-table-sticky) .bar{width:12px!important}\n"], dependencies: [{ kind: "directive", type: ResizableDirective, selector: "[resizable]", outputs: ["resizable", "fit"] }] });
22422
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ResizableComponent, decorators: [{
22423
+ type: Component,
22424
+ args: [{ selector: 'th[resizable]', template: "<div class=\"wrapper\">\n <div class=\"content\">\n <ng-content></ng-content>\n </div>\n <div class=\"bar\" [class.cdk-visually-hidden]=\"!resizable\" (resizable)=\"onResize($event)\" (fit)=\"onFit()\"></div>\n</div>\n", styles: [":host:last-child .bar{display:none}.wrapper{display:flex;justify-content:flex-end;align-items:center}.content{flex:1}.bar{height:var(--app-table-header-height, 55px);width:10px;justify-self:flex-end;border-left:4px solid transparent;border-right:4px solid transparent;background:var(--ion-color-secondary);background-clip:content-box;cursor:ew-resize;opacity:0;transition:opacity .3s}.bar:hover,.bar:active{opacity:1}:host-context(.mat-table-sticky) .bar{width:12px!important}\n"] }]
22425
+ }], ctorParameters: function () { return [{ type: i6$5.MatColumnDef, decorators: [{
22426
+ type: Optional
22427
+ }] }]; }, propDecorators: { resizable: [{
22428
+ type: Input
22429
+ }], sizeChanged: [{
22430
+ type: Output
22431
+ }], width: [{
22432
+ type: HostBinding,
22433
+ args: ['style.width']
22434
+ }], minWidth: [{
22435
+ type: HostBinding,
22436
+ args: ['style.min-width']
22437
+ }], maxWidth: [{
22438
+ type: HostBinding,
22439
+ args: ['style.max-width']
22440
+ }] } });
22441
+
22442
+ class ResizableModule {
22443
+ }
22444
+ ResizableModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ResizableModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
22445
+ ResizableModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: ResizableModule, declarations: [ResizableComponent, ResizableDirective], exports: [ResizableComponent, ResizableDirective] });
22446
+ ResizableModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ResizableModule });
22447
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ResizableModule, decorators: [{
22448
+ type: NgModule,
22449
+ args: [{
22450
+ declarations: [ResizableComponent, ResizableDirective],
22451
+ exports: [ResizableComponent, ResizableDirective],
22452
+ }]
22453
+ }] });
22454
+
22455
+ function mergeLoadResult(res1, res2) {
22456
+ return {
22457
+ data: (res1.data || []).concat(...res2.data),
22458
+ total: ((res1.total || res1.data?.length || 0) + (res2.total || res2.data?.length || 0))
22459
+ };
22460
+ }
21733
22461
 
21734
22462
  // @dynamic
21735
22463
  // eslint-disable-next-line @angular-eslint/directive-class-suffix
@@ -22047,1075 +22775,532 @@ class InMemoryEntitiesService extends StartableObservableService {
22047
22775
  this.savingSubject.next(true);
22048
22776
  }
22049
22777
  }
22050
- markAsSaved() {
22051
- if (this.savingSubject.value) {
22052
- this.savingSubject.next(false);
22053
- }
22054
- }
22055
- }
22056
- InMemoryEntitiesService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: InMemoryEntitiesService, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
22057
- InMemoryEntitiesService.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.3.0", type: InMemoryEntitiesService, usesInheritance: true, ngImport: i0 });
22058
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: InMemoryEntitiesService, decorators: [{
22059
- type: Directive
22060
- }], ctorParameters: function () { return [{ type: undefined }, { type: undefined }, { type: undefined }]; } });
22061
-
22062
- // @dynamic
22063
- class JobUtils {
22064
- static defers(runnableList, opts) {
22065
- return runnableList.map(runnable => JobUtils.defer(runnable, opts));
22066
- }
22067
- static defer(runnable, opts) {
22068
- return defer(() => JobUtils.run(runnable, opts));
22069
- }
22070
- static run(runnable, opts) {
22071
- const progression = new BehaviorSubject(0);
22072
- runnable({ ...opts, progression })
22073
- .then(() => {
22074
- if (opts?.maxProgression)
22075
- progression.next(opts.maxProgression);
22076
- progression.complete();
22077
- })
22078
- .catch(err => progression.error(err));
22079
- return progression;
22080
- }
22081
- static async fetchAllPages(loadPageFn, opts) {
22082
- const maxProgression = toNumber(opts?.maxProgression, 100);
22083
- const progression = opts?.progression;
22084
- let total;
22085
- let pageCount;
22086
- let progressionStep;
22087
- let currentPageDataCount;
22088
- const fetchSize = toNumber(opts?.fetchSize, 1000);
22089
- if (fetchSize <= 0)
22090
- throw new Error('Unable to fetch pages: invalid fetchSize: ' + fetchSize);
22091
- let offset = 0;
22092
- let data = [];
22093
- do {
22094
- if (opts && opts.logPrefix)
22095
- console.debug(`${opts.logPrefix} Fetching page #${offset / fetchSize}...`);
22096
- // Fetch a page
22097
- const res = await loadPageFn(offset, fetchSize);
22098
- // Concat to previous result
22099
- data = data.concat(res.data);
22100
- // Compute variables, for next loop
22101
- currentPageDataCount = res.data && res.data.length || 0;
22102
- offset += currentPageDataCount;
22103
- // Set total count (only once)
22104
- if (isNil(total) && isNotNil(res.total)) {
22105
- total = res.total;
22106
- // Compute the number of page
22107
- pageCount = Math.round(res.total / fetchSize + 0.5); // Round to HALF_UP integer
22108
- // Compute step, by dividing maxProgression by number of page
22109
- if (progression) {
22110
- progressionStep = maxProgression / pageCount;
22111
- // DEBUG
22112
- if (opts && opts.logPrefix)
22113
- console.debug(`${opts.logPrefix} {pageCount: ${pageCount}, progress: ${progression.value?.toFixed(2)}, pageProgressStep: ${progressionStep?.toFixed(1)}}`);
22114
- }
22115
- }
22116
- // Increment progression on each iteration (if possible)
22117
- if (isNotNil(progressionStep)) {
22118
- progression.next(progression.value + progressionStep);
22119
- }
22120
- if (opts?.onPageLoaded)
22121
- opts.onPageLoaded(res);
22122
- } while (currentPageDataCount > 0 && ((isNil(total) && currentPageDataCount === fetchSize) || (isNotNil(total) && (offset < total))));
22123
- // Complete progression to reach progressionStep value (because of round)
22124
- if (isNotNil(progressionStep)) {
22125
- // If total return by loadAll was bad !
22126
- if (isNotNil(total) && data.length < total) {
22127
- console.warn(`A function loadAll() returned a bad total (less than all page's data)! Expected ${total} but fetch ${data.length}`);
22128
- total = data.length;
22129
- pageCount = Math.round(data.length / fetchSize + 0.5);
22130
- }
22131
- // Compute final increment, using the real page count
22132
- progressionStep = Math.max(0, maxProgression - (progressionStep * pageCount));
22133
- }
22134
- // Or increment once (e.g. when total is not known, in received LoadResult)
22135
- else if (progression) {
22136
- progressionStep = maxProgression;
22137
- }
22138
- // DEBUG
22139
- if (opts && opts.logPrefix)
22140
- console.debug(`${opts.logPrefix} All pages fetched - {progress: ${progression.value?.toFixed(2)}, lastProgressStep: ${progressionStep?.toFixed(1)}`);
22141
- // Final increment
22142
- if (progression && progressionStep > 0) {
22143
- progression.next(progression.value + progressionStep);
22144
- }
22145
- return {
22146
- data,
22147
- total
22148
- };
22149
- }
22150
- }
22151
-
22152
- class IPosition {
22153
- }
22154
- class GeolocationUtils {
22155
- static isNotNilAndValid(position, opts) {
22156
- if (!position || isNil(position.latitude) || isNil(position.longitude))
22157
- return false;
22158
- // Invalid lat/lon
22159
- if (position.latitude < -90 || position.latitude > 90
22160
- || position.longitude < -180 || position.longitude > 180) {
22161
- // /!\ Log in console, because should never occur
22162
- if (opts?.debug)
22163
- console.warn('Invalid lat/long position: ', position);
22164
- return false;
22165
- }
22166
- // OK: valid
22167
- return true;
22168
- }
22169
- static isNilOrInvalid(position, opts) {
22170
- return !GeolocationUtils.isNotNilAndValid(position, opts);
22171
- }
22172
- static computeDistanceInMiles(position1, position2) {
22173
- // Invalid position(s): skip
22174
- if (GeolocationUtils.isNilOrInvalid(position1, { debug: true })
22175
- || GeolocationUtils.isNilOrInvalid(position2, { debug: true }))
22176
- return;
22177
- const latitude1Rad = Math.PI * position1.latitude / 180;
22178
- const longitude1Rad = Math.PI * position1.longitude / 180;
22179
- const latitude2Rad = Math.PI * position2.latitude / 180;
22180
- const longitude2Rad = Math.PI * position2.longitude / 180;
22181
- let distance = 2 * 6371 * Math.asin(Math.sqrt(Math.pow(Math.sin((latitude1Rad - latitude2Rad) / 2), 2)
22182
- + Math.cos(latitude1Rad) * Math.cos(latitude2Rad) * Math.pow(Math.sin((longitude2Rad - longitude1Rad) / 2), 2)));
22183
- distance = Math.round((((distance / 1.852) + Number.EPSILON) * 100)) / 100;
22184
- return distance;
22185
- }
22186
- /**
22187
- * Get the position by geo loc sensor
22188
- */
22189
- static async getCurrentPosition(platform, options) {
22190
- // Use Capacitor plugin
22191
- try {
22192
- console.info(`[geolocation-utils] Get current geo location, using Capacitor...(timeout: ${options?.timeout})`);
22193
- const pos = await Geolocation.getCurrentPosition(options);
22194
- return {
22195
- latitude: pos.coords.latitude,
22196
- longitude: pos.coords.longitude
22197
- };
22198
- }
22199
- catch (err) {
22200
- // If capacitor : throw error (cannot fallback to browser geolocation, because of browser security limitation)
22201
- if (platform.is('capacitor')) {
22202
- console.error(`[geolocation-utils] Cannot get current geo location, using Capacitor: ${err?.message || ''}`);
22203
- // Detect a permission error, to add code
22204
- // FIXME - Capacitor use hardcoded strings! (see https://github.com/ionic-team/capacitor-plugins)
22205
- const message = (err?.message || '').toLowerCase();
22206
- if (message.includes('location permission was denied') || message.includes('location disabled')) {
22207
- throw { ...err, code: GeolocationPositionError.PERMISSION_DENIED };
22208
- }
22209
- throw err;
22210
- }
22211
- }
22212
- // Or fallback to navigator
22213
- console.info('[geolocation-utils] Get current geo location, using browser...');
22214
- return new Promise((resolve, reject) => {
22215
- navigator.geolocation.getCurrentPosition((res) => {
22216
- resolve({
22217
- latitude: res.coords.latitude,
22218
- longitude: res.coords.longitude
22219
- });
22220
- }, (err) => {
22221
- let message = err?.message || err;
22222
- message = typeof message === 'string' ? message : JSON.stringify(message);
22223
- console.error('[geolocation-utils] Cannot get current geo location, using browser:' + message);
22224
- reject(err);
22225
- }, options);
22226
- });
22227
- }
22228
- }
22229
-
22230
- //@dynamic
22231
- class CsvUtils {
22232
- static exportToFile(rows, opts) {
22233
- if (!rows || !rows.length)
22234
- return; // Skip if empty
22235
- // Create text content
22236
- const separator = opts?.separator || ',';
22237
- const protectCellRegexp = new RegExp("/(\"|" + separator + "|\n)/g");
22238
- const keys = Object.keys(rows[0]);
22239
- const headers = opts?.headers || keys;
22240
- const textContent =
22241
- // Header row
22242
- headers.join(separator) + '\n' +
22243
- // Data rows
22244
- rows.map(row => {
22245
- return keys.map(k => {
22246
- const cell = row[k] === null || row[k] === undefined ? '' : row[k];
22247
- let cellStr = cell instanceof Date
22248
- ? cell.toLocaleString()
22249
- : cell.toString().replace(/"/g, '""');
22250
- if (protectCellRegexp.test(cellStr)) {
22251
- cellStr = `"${cellStr}"`;
22252
- }
22253
- return cellStr;
22254
- }).join(separator);
22255
- }).join('\n');
22256
- // Write to file
22257
- FilesUtils.writeTextToFile(textContent, {
22258
- type: 'text/csv',
22259
- filename: 'export.csv',
22260
- ...opts,
22261
- });
22262
- }
22263
- static parseFile(file, opts) {
22264
- return FilesUtils.readAsText(file, opts?.encoding)
22265
- .pipe(map(e => {
22266
- if (e.type === HttpEventType.UploadProgress) {
22267
- const loaded = Math.round(e.loaded * 0.8);
22268
- return { ...e, loaded };
22269
- }
22270
- else if (e instanceof FileResponse) {
22271
- const body = e.body;
22272
- const data = CsvUtils.parseCSV(body, opts);
22273
- return new FileResponse({ body: data });
22274
- }
22275
- }), filter(isNotNil));
22276
- }
22277
- static parseCSV(body, opts) {
22278
- const separator = opts?.separator || ',';
22279
- const skipEmptyLine = !opts || opts.skipEmptyLine !== false;
22280
- // Protect special characters (quote and /n)
22281
- body = body.replace('""', '<quote>') // Protect double quote
22282
- .replace(/("[^\n"]+)\n\r?/gm, '"$1<br>') // Protect \n inside a quoted expression
22283
- .replace(/\n\r/gm, '\n'); // Windows CR
22284
- const headerAndRows = body.split('\n', 2);
22285
- const headers = headerAndRows[0].split(separator);
22286
- return body.split('\n') // split into rows
22287
- .filter(line => !skipEmptyLine || line.trim().length > 0) // Skip empty line
22288
- .map(line => {
22289
- const cells = line.split(separator, headers.length) // split into cells
22290
- .map(cell => cell.replace(/^"([^"]*)"$/, '$1')) // Clean trailing quotes
22291
- .map(cell => cell.replace('<quote>', '"')) // restore protected quote
22292
- .map(cell => cell.replace('<br>', '\n')) // restore protected br
22293
- ;
22294
- return cells;
22295
- });
22296
- }
22297
- }
22298
-
22299
- /**
22300
- * Define here theme colors
22301
- */
22302
- const rgbArrayMap = {
22303
- white: [255, 255, 255],
22304
- primary: [20, 67, 145],
22305
- secondary: [117, 196, 253],
22306
- tertiary: [91, 94, 244],
22307
- danger: [245, 61, 61],
22308
- light: [244, 245, 248],
22309
- medium: [152, 154, 162],
22310
- dark: [34, 36, 40],
22311
- red: [255, 0, 0],
22312
- green: [0, 255, 0],
22313
- blue: [0, 0, 255]
22314
- };
22315
- // Fill a map of Color objects
22316
- const colorsMap = {};
22317
- /**
22318
- * Useful class for color conversion
22319
- */
22320
- // @dynamic
22321
- class Color {
22322
- constructor(_rgbArray, _opacity, _name = 'custom') {
22323
- this._rgbArray = _rgbArray;
22324
- this._opacity = _opacity;
22325
- this._name = _name;
22326
- }
22327
- // Helper method, to retrieve a color
22328
- static get(name) {
22329
- return colorsMap[name];
22330
- }
22331
- static parseRgba(rgba) {
22332
- if (!rgba || (!rgba.startsWith('rgb(') && !rgba.startsWith('rgba(')))
22333
- return null;
22334
- // Parse parts
22335
- const parts = rgba
22336
- .replace('rgb(', '')
22337
- .replace('rgba(', '')
22338
- .replace(')', '')
22339
- .split(',');
22340
- if (parts.length !== 3 && parts.length !== 4)
22341
- return null;
22342
- return new Color([+parts[0], +parts[1], +parts[2]], parts.length === 4 && +parts[3] || 1, 'custom');
22343
- }
22344
- get name() {
22345
- return this._name;
22346
- }
22347
- get opacity() {
22348
- return isNotNil(this._opacity) ? this._opacity : 1;
22349
- }
22350
- get rgb() {
22351
- return this._rgbArray;
22352
- }
22353
- get r() {
22354
- return this._rgbArray[0];
22355
- }
22356
- get g() {
22357
- return this._rgbArray[1];
22358
- }
22359
- get b() {
22360
- return this._rgbArray[2];
22361
- }
22362
- rgba(opacity) {
22363
- opacity = isNotNil(opacity) ? opacity : this._opacity;
22364
- if (isNil(opacity) || opacity < 0 || opacity > 1) {
22365
- return 'rgb(' + this._rgbArray.join(',') + ')';
22366
- }
22367
- return 'rgba(' + this._rgbArray.join(',') + ',' + opacity + ')';
22368
- }
22369
- }
22370
- Color.transparent = function () {
22371
- return new Color([0, 0, 0], 0, 'translucent');
22372
- };
22373
- /**
22374
- * Helper class for colors scale
22375
- */
22376
- // @dynamic
22377
- class ColorScale {
22378
- constructor(colorArray, options) {
22379
- options = options || {};
22380
- // reserved last colors for value > max
22381
- const nbIntervalBeforeUpper = !options.upperMax ? colorArray.length : (colorArray.length - 1);
22382
- this._min = options.min || 0;
22383
- this._max = options.max || nbIntervalBeforeUpper;
22384
- this._rangeSize = Math.round((this._max - this._min) / nbIntervalBeforeUpper);
22385
- this._legendItems = this.computeLegendItems(colorArray);
22386
- }
22387
- static default() {
22388
- return ColorScale.custom(25);
22389
- }
22390
- /**
22391
- * Create a array with the given color
22392
- **/
22393
- static fix(length, colorName) {
22394
- return Array.apply(null, Array(length || 25))
22395
- .map(String.prototype.valueOf, Color.get(colorName || 'primary').rgba(0.5));
22396
- }
22397
- get min() {
22398
- return this._min;
22399
- }
22400
- get max() {
22401
- return this._max;
22402
- }
22403
- get legend() {
22404
- return {
22405
- items: this._legendItems
22406
- };
22407
- }
22408
- getValueColor(value) {
22409
- return this.getLegendForValue(value).color;
22410
- }
22411
- getLegendForValue(value) {
22412
- if (value <= this._min)
22413
- return this._legendItems[0];
22414
- if (value >= this._max)
22415
- return this._legendItems[this._legendItems.length - 1];
22416
- const index = Math.floor(value * (this._legendItems.length - 1) / this._max);
22417
- return this._legendItems[index];
22418
- }
22419
- getLegendAtIndex(index) {
22420
- return this._legendItems[index];
22421
- }
22422
- computeLegendItems(colorArray) {
22423
- return colorArray.map((color, index) => {
22424
- const start = index * this._rangeSize;
22425
- const end = start + this._rangeSize;
22426
- return {
22427
- color: new Color(color),
22428
- label: (end < this._max) ? `${start.toLocaleString()} - ${end.toLocaleString()}` : ` >= ${start}`
22429
- };
22430
- });
22431
- }
22432
- }
22433
- ColorScale.custom = (count, options) => {
22434
- options = options || {};
22435
- return new ColorScale(linearColorGradientWithIntermediate(count, {
22436
- startColor: options.startColor || undefined,
22437
- mainColor: options.mainColor || undefined,
22438
- mainColorIndex: isNotNil(options.mainColorIndex) ? options.mainColorIndex : undefined,
22439
- endColor: options.endColor || undefined
22440
- }), options);
22441
- };
22442
- // Fill colorsMap
22443
- Object.getOwnPropertyNames(rgbArrayMap)
22444
- .forEach((key) => {
22445
- colorsMap[key] = new Color(rgbArrayMap[key], 1, key);
22446
- });
22447
- /**
22448
- * Internal function, that create a colors scale, using iteration
22449
- *
22450
- * @param count
22451
- * @param opacity
22452
- * @param startColor
22453
- * @param startState
22454
- * @returns
22455
- */
22456
- function linearColorGradientWithIntermediate(count, options) {
22457
- options = options || {};
22458
- // From [0,1]
22459
- options.startColor = options.startColor || [255, 255, 190]; // default start = creme
22460
- options.mainColorIndex = options.mainColorIndex && options.mainColorIndex < count - 1 ? options.mainColorIndex : count - 1;
22461
- options.endColor = options.endColor || [255, 0, 0]; // default main = red
22462
- if (!options.mainColor) {
22463
- return linearColorGradient(count, {
22464
- startColor: options.startColor,
22465
- endColor: options.endColor
22466
- });
22467
- }
22468
- else {
22469
- // Step 1: startColor -> mainColor
22470
- const result = linearColorGradient(options.mainColorIndex + 1, {
22471
- startColor: options.startColor,
22472
- endColor: options.mainColor
22473
- });
22474
- // Step 2: mainColor -> endColor
22475
- if (options.mainColorIndex < count - 1) {
22476
- return result.concat(linearColorGradient(count - options.mainColorIndex, {
22477
- startColor: options.mainColor,
22478
- endColor: options.endColor
22479
- }));
22480
- }
22481
- else {
22482
- return result;
22483
- }
22484
- }
22485
- }
22486
- function linearColorGradient(count, options) {
22487
- options = options || {};
22488
- // From [0,1]
22489
- options.startColor = options.startColor || [255, 255, 255]; // default start = white
22490
- options.endColor = options.endColor || [255, 0, 0]; // default end = red
22491
- const result = [];
22492
- const color = options.startColor.slice(); // copy the start color
22493
- const delta = [
22494
- Math.round((options.endColor[0] - options.startColor[0]) / count),
22495
- Math.round((options.endColor[1] - options.startColor[1]) / count),
22496
- Math.round((options.endColor[2] - options.startColor[2]) / count)
22497
- ];
22498
- for (let i = 0; i < count - 1; i++) {
22499
- for (let j = 0; j < 3; j++) {
22500
- color[j] += delta[j];
22501
- }
22502
- result.push(color.slice());
22503
- }
22504
- // Force last color = end color
22505
- result.push(options.endColor.slice());
22506
- // Output as array
22507
- return result;
22508
- }
22509
-
22510
- class DebugComponent {
22511
- constructor(environment) {
22512
- this.title = '';
22513
- this.enable = null;
22514
- this.expanded = false;
22515
- this.enable = !environment || !environment.production;
22516
- }
22517
- toggle() {
22518
- this.expansionPanel.toggle();
22778
+ markAsSaved() {
22779
+ if (this.savingSubject.value) {
22780
+ this.savingSubject.next(false);
22781
+ }
22519
22782
  }
22520
22783
  }
22521
- DebugComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: DebugComponent, deps: [{ token: ENVIRONMENT, optional: true }], target: i0.ɵɵFactoryTarget.Component });
22522
- DebugComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: DebugComponent, selector: "app-debug", inputs: { title: "title", enable: "enable", expanded: "expanded" }, viewQueries: [{ propertyName: "expansionPanel", first: true, predicate: ["expansionPanel"], descendants: true }], ngImport: i0, template: "<!-- debug -->\n<ion-item tappable (click)=\"toggle()\" lines=\"none\" color=\"secondary50\">\n <ion-icon slot=\"start\" name=\"bug\" ></ion-icon>\n <ion-label color=\"primary\">Debug {{title}}</ion-label>\n <mat-icon slot=\"end\">{{expansionPanel.expanded ? 'expand_less' : 'expand_more' }}</mat-icon>\n</ion-item>\n<mat-expansion-panel #expansionPanel [(expanded)]=\"expanded\">\n <ion-text color=\"primary100\" >\n <div class=\"ion-margin-start\">\n <ng-content></ng-content>\n </div>\n </ion-text>\n</mat-expansion-panel>\n", styles: ["ion-item{cursor:pointer!important}ion-item mat-icon,ion-item ion-icon{color:var(--ion-color-primary)}.mat-expansion-panel{--expansion-panel-padding: 4px;background:var(--ion-color-secondary50)!important}.mat-expansion-panel.mat-expanded{margin-bottom:4px!important}.mat-expansion-panel .mat-expansion-panel-body *{color:var(--ion-color-primary100)}\n"], dependencies: [{ kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i9$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["disabled", "expanded", "hideToggle", "togglePosition"], outputs: ["opened", "closed", "expandedChange", "afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }] });
22523
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: DebugComponent, decorators: [{
22524
- type: Component,
22525
- args: [{ selector: 'app-debug', template: "<!-- debug -->\n<ion-item tappable (click)=\"toggle()\" lines=\"none\" color=\"secondary50\">\n <ion-icon slot=\"start\" name=\"bug\" ></ion-icon>\n <ion-label color=\"primary\">Debug {{title}}</ion-label>\n <mat-icon slot=\"end\">{{expansionPanel.expanded ? 'expand_less' : 'expand_more' }}</mat-icon>\n</ion-item>\n<mat-expansion-panel #expansionPanel [(expanded)]=\"expanded\">\n <ion-text color=\"primary100\" >\n <div class=\"ion-margin-start\">\n <ng-content></ng-content>\n </div>\n </ion-text>\n</mat-expansion-panel>\n", styles: ["ion-item{cursor:pointer!important}ion-item mat-icon,ion-item ion-icon{color:var(--ion-color-primary)}.mat-expansion-panel{--expansion-panel-padding: 4px;background:var(--ion-color-secondary50)!important}.mat-expansion-panel.mat-expanded{margin-bottom:4px!important}.mat-expansion-panel .mat-expansion-panel-body *{color:var(--ion-color-primary100)}\n"] }]
22526
- }], ctorParameters: function () { return [{ type: Environment, decorators: [{
22527
- type: Inject,
22528
- args: [ENVIRONMENT]
22529
- }, {
22530
- type: Optional
22531
- }] }]; }, propDecorators: { title: [{
22532
- type: Input
22533
- }], enable: [{
22534
- type: Input
22535
- }], expanded: [{
22536
- type: Input
22537
- }], expansionPanel: [{
22538
- type: ViewChild,
22539
- args: ['expansionPanel']
22540
- }] } });
22541
-
22542
- class SharedDebugModule {
22543
- }
22544
- SharedDebugModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: SharedDebugModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
22545
- SharedDebugModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: SharedDebugModule, declarations: [DebugComponent], imports: [CommonModule,
22546
- IonicModule,
22547
- MatExpansionModule,
22548
- MatIconModule, i1$2.TranslateModule, MatExpansionModule], exports: [DebugComponent,
22549
- TranslateModule] });
22550
- SharedDebugModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: SharedDebugModule, imports: [CommonModule,
22551
- IonicModule,
22552
- MatExpansionModule,
22553
- MatIconModule,
22554
- TranslateModule.forChild(),
22555
- MatExpansionModule, TranslateModule] });
22556
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: SharedDebugModule, decorators: [{
22557
- type: NgModule,
22558
- args: [{
22559
- imports: [
22560
- CommonModule,
22561
- IonicModule,
22562
- MatExpansionModule,
22563
- MatIconModule,
22564
- TranslateModule.forChild(),
22565
- MatExpansionModule
22566
- ],
22567
- declarations: [
22568
- DebugComponent
22569
- ],
22570
- exports: [
22571
- DebugComponent,
22572
- TranslateModule
22573
- ]
22574
- }]
22575
- }] });
22784
+ InMemoryEntitiesService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: InMemoryEntitiesService, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
22785
+ InMemoryEntitiesService.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.3.0", type: InMemoryEntitiesService, usesInheritance: true, ngImport: i0 });
22786
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: InMemoryEntitiesService, decorators: [{
22787
+ type: Directive
22788
+ }], ctorParameters: function () { return [{ type: undefined }, { type: undefined }, { type: undefined }]; } });
22576
22789
 
22577
22790
  // @dynamic
22578
- // eslint-disable-next-line @angular-eslint/directive-class-suffix
22579
- class BaseEntityService extends BaseGraphqlService {
22580
- constructor(graphql, platform, dataType, filterType, options) {
22581
- super(graphql, environment);
22582
- this.graphql = graphql;
22583
- this.platform = platform;
22584
- this.dataType = dataType;
22585
- this.filterType = filterType;
22586
- this.queries = options.queries;
22587
- this.mutations = options.mutations || {};
22588
- this.subscriptions = options.subscriptions || {};
22589
- this.equalsFn = options.equalsFn || ((e1, e2) => EntityUtils.equals(e1, e2, 'id'));
22590
- this.isNewFn = options.isNewFn || ((e) => EntityUtils.isNotEmpty(e, 'id'));
22591
- this.defaultSortBy = options.defaultSortBy || 'id';
22592
- this.defaultSortDirection = options.defaultSortDirection || 'asc';
22593
- this.watchQueriesUpdatePolicy = options.watchQueriesUpdatePolicy || 'update-cache';
22594
- const obj = new dataType();
22595
- this._typename = obj.__typename || 'UnknownVO';
22596
- this._logTypeName = removeEnd(this._typename, 'VO');
22597
- // For DEV only
22598
- this._debug = !environment.production;
22599
- }
22600
- watch(id, opts) {
22601
- if (this._debug)
22602
- console.debug(this._logPrefix + `Watching ${this._logTypeName} {${id}}...`);
22603
- const variables = opts?.variables || { id };
22604
- const query = opts && opts.query || this.queries.load;
22605
- return this.graphql.watchQuery({
22606
- query,
22607
- variables,
22608
- fetchPolicy: opts && opts.fetchPolicy || undefined,
22609
- error: { code: ErrorCodes.LOAD_DATA_ERROR, message: 'REFERENTIAL.ERROR.LOAD_DATA_ERROR' }
22610
- })
22611
- .pipe(map(({ data }) => (!opts || opts.toEntity !== false)
22612
- ? (data && this.fromObject(data))
22613
- : data));
22791
+ class JobUtils {
22792
+ static defers(runnableList, opts) {
22793
+ return runnableList.map(runnable => JobUtils.defer(runnable, opts));
22614
22794
  }
22615
- async load(id, opts) {
22616
- if (this._debug)
22617
- console.debug(this._logPrefix + `Loading ${this._logTypeName} {${id}}...`);
22618
- const variables = opts?.variables || { id };
22619
- const query = opts?.query || this.queries.load;
22620
- const { data } = await this.graphql.query({
22621
- query,
22622
- variables,
22623
- fetchPolicy: opts && opts.fetchPolicy || undefined,
22624
- error: { code: ErrorCodes.LOAD_DATA_ERROR, message: 'REFERENTIAL.ERROR.LOAD_DATA_ERROR' }
22625
- });
22626
- // Convert to entity
22627
- return (!opts || opts.toEntity !== false)
22628
- ? (data && this.fromObject(data))
22629
- : data;
22795
+ static defer(runnable, opts) {
22796
+ return defer(() => JobUtils.run(runnable, opts));
22630
22797
  }
22631
- watchAll(offset, size, sortBy, sortDirection, filter, opts) {
22632
- filter = this.asFilter(filter);
22633
- const variables = {
22634
- ...(opts?.variables || {}),
22635
- offset: offset || 0,
22636
- size: size || 100,
22637
- sortBy: sortBy || this.defaultSortBy,
22638
- sortDirection: sortDirection || this.defaultSortDirection,
22639
- filter: filter && filter.asPodObject()
22640
- };
22641
- let now = this._debug && Date.now();
22642
- if (this._debug)
22643
- console.debug(this._logPrefix + `Watching ${this._logTypeName}...`, variables);
22644
- const withTotal = (!opts || opts.withTotal !== false) && this.queries.loadAllWithTotal && true;
22645
- const query = (opts && opts.query) // use given query
22646
- // Or get loadAll or loadAllWithTotal query
22647
- || (withTotal ? this.queries.loadAllWithTotal : this.queries.loadAll);
22648
- return this.mutableWatchQuery({
22649
- query,
22650
- variables,
22651
- // Force cached mutable query
22652
- queryName: withTotal ? 'LoadAllWithTotal' : 'LoadAll',
22653
- arrayFieldName: 'data',
22654
- totalFieldName: withTotal ? 'total' : undefined,
22655
- insertFilterFn: filter && filter.asFilterFn(),
22656
- error: { code: ErrorCodes.LOAD_DATA_ERROR, message: 'REFERENTIAL.ERROR.LOAD_DATA_ERROR' },
22657
- fetchPolicy: opts && opts.fetchPolicy || undefined
22798
+ static run(runnable, opts) {
22799
+ const progression = new BehaviorSubject(0);
22800
+ runnable({ ...opts, progression })
22801
+ .then(() => {
22802
+ if (opts?.maxProgression)
22803
+ progression.next(opts.maxProgression);
22804
+ progression.complete();
22658
22805
  })
22659
- .pipe(map(({ data, total }) => {
22660
- // Convert to entity (if need)
22661
- const entities = (!opts || opts.toEntity !== false)
22662
- ? (data || []).map(json => this.fromObject(json))
22663
- : (data || []);
22664
- if (now) {
22665
- console.debug(this._logPrefix + `${this._logTypeName} loaded in ${Date.now() - now}ms`, entities);
22666
- now = null;
22667
- }
22668
- return { data: entities, total };
22669
- }));
22806
+ .catch(err => progression.error(err));
22807
+ return progression;
22670
22808
  }
22671
- async loadAll(offset, size, sortBy, sortDirection, filter, opts) {
22672
- const debug = this._debug && (!opts || opts.debug !== false);
22673
- filter = this.asFilter(filter);
22674
- const variables = {
22675
- ...(opts?.variables || {}),
22676
- offset: offset || 0,
22677
- size: size || 100,
22678
- sortBy: sortBy || 'id',
22679
- sortDirection: sortDirection || 'asc',
22680
- filter: filter && filter.asPodObject()
22681
- };
22682
- const now = Date.now();
22683
- if (debug)
22684
- console.debug(this._logPrefix + `Loading ${this._logTypeName}...`, variables);
22685
- const withTotal = (!opts || opts.withTotal !== false) && this.queries.loadAllWithTotal && true;
22686
- // use given query
22687
- const query = opts?.query
22688
- // Or get loadAll or loadAllWithTotal query
22689
- || (withTotal ? this.queries.loadAllWithTotal : this.queries.loadAll);
22690
- const { data, total } = await this.graphql.query({
22691
- query,
22692
- variables,
22693
- error: { code: ErrorCodes.LOAD_DATA_ERROR, message: 'ERROR.LOAD_DATA_ERROR' },
22694
- fetchPolicy: opts && opts.fetchPolicy || 'network-only'
22695
- });
22696
- const entities = (!opts || opts.toEntity !== false) ?
22697
- (data || []).map(json => this.fromObject(json)) :
22698
- (data || []);
22699
- if (debug)
22700
- console.debug(this._logPrefix + `${this._logTypeName} items loaded in ${Date.now() - now}ms`);
22701
- const result = {
22702
- data: entities,
22703
- total: total || entities.length
22704
- };
22705
- // Add fetch more capability, if total was fetched
22706
- if (withTotal) {
22707
- const nextOffset = (offset || 0) + entities.length;
22708
- if (nextOffset < result.total) {
22709
- result.fetchMore = () => this.loadAll(nextOffset, size, sortBy, sortDirection, filter, opts);
22809
+ static async fetchAllPages(loadPageFn, opts) {
22810
+ const maxProgression = toNumber(opts?.maxProgression, 100);
22811
+ const progression = opts?.progression;
22812
+ let total;
22813
+ let pageCount;
22814
+ let progressionStep;
22815
+ let currentPageDataCount;
22816
+ const fetchSize = toNumber(opts?.fetchSize, 1000);
22817
+ if (fetchSize <= 0)
22818
+ throw new Error('Unable to fetch pages: invalid fetchSize: ' + fetchSize);
22819
+ let offset = 0;
22820
+ let data = [];
22821
+ do {
22822
+ if (opts && opts.logPrefix)
22823
+ console.debug(`${opts.logPrefix} Fetching page #${offset / fetchSize}...`);
22824
+ // Fetch a page
22825
+ const res = await loadPageFn(offset, fetchSize);
22826
+ // Concat to previous result
22827
+ data = data.concat(res.data);
22828
+ // Compute variables, for next loop
22829
+ currentPageDataCount = res.data && res.data.length || 0;
22830
+ offset += currentPageDataCount;
22831
+ // Set total count (only once)
22832
+ if (isNil(total) && isNotNil(res.total)) {
22833
+ total = res.total;
22834
+ // Compute the number of page
22835
+ pageCount = Math.round(res.total / fetchSize + 0.5); // Round to HALF_UP integer
22836
+ // Compute step, by dividing maxProgression by number of page
22837
+ if (progression) {
22838
+ progressionStep = maxProgression / pageCount;
22839
+ // DEBUG
22840
+ if (opts && opts.logPrefix)
22841
+ console.debug(`${opts.logPrefix} {pageCount: ${pageCount}, progress: ${progression.value?.toFixed(2)}, pageProgressStep: ${progressionStep?.toFixed(1)}}`);
22842
+ }
22710
22843
  }
22844
+ // Increment progression on each iteration (if possible)
22845
+ if (isNotNil(progressionStep)) {
22846
+ progression.next(progression.value + progressionStep);
22847
+ }
22848
+ if (opts?.onPageLoaded)
22849
+ opts.onPageLoaded(res);
22850
+ } while (currentPageDataCount > 0 && ((isNil(total) && currentPageDataCount === fetchSize) || (isNotNil(total) && (offset < total))));
22851
+ // Complete progression to reach progressionStep value (because of round)
22852
+ if (isNotNil(progressionStep)) {
22853
+ // If total return by loadAll was bad !
22854
+ if (isNotNil(total) && data.length < total) {
22855
+ console.warn(`A function loadAll() returned a bad total (less than all page's data)! Expected ${total} but fetch ${data.length}`);
22856
+ total = data.length;
22857
+ pageCount = Math.round(data.length / fetchSize + 0.5);
22858
+ }
22859
+ // Compute final increment, using the real page count
22860
+ progressionStep = Math.max(0, maxProgression - (progressionStep * pageCount));
22711
22861
  }
22712
- return result;
22713
- }
22714
- async countAll(filter, opts) {
22715
- // If no countAll query
22716
- if (!this.queries.countAll && !opts?.query) {
22717
- // Fail if no loadAll (cannot count)
22718
- if (!this.queries.loadAllWithTotal)
22719
- throw new Error('Not implemented');
22720
- // Use loadAll query, with size = 0;
22721
- return (await this.loadAll(0, 1, null, null, filter, { withTotal: true }))?.total || 0;
22862
+ // Or increment once (e.g. when total is not known, in received LoadResult)
22863
+ else if (progression) {
22864
+ progressionStep = maxProgression;
22722
22865
  }
22723
- filter = this.asFilter(filter);
22724
- const variables = {
22725
- ...(opts?.variables || {}),
22726
- filter: filter && filter.asPodObject()
22866
+ // DEBUG
22867
+ if (opts && opts.logPrefix)
22868
+ console.debug(`${opts.logPrefix} All pages fetched - {progress: ${progression.value?.toFixed(2)}, lastProgressStep: ${progressionStep?.toFixed(1)}`);
22869
+ // Final increment
22870
+ if (progression && progressionStep > 0) {
22871
+ progression.next(progression.value + progressionStep);
22872
+ }
22873
+ return {
22874
+ data,
22875
+ total
22727
22876
  };
22728
- const query = opts?.query || this.queries.countAll;
22729
- const res = await this.graphql.query({
22730
- query,
22731
- variables,
22732
- error: { code: ErrorCodes.LOAD_DATA_ERROR, message: 'ERROR.COUNT_DATA_ERROR' },
22733
- fetchPolicy: opts && opts.fetchPolicy || 'network-only'
22734
- });
22735
- return res.total;
22736
22877
  }
22737
- canUserWrite(data, opts) {
22738
- return !!data;
22739
- }
22740
- async saveAll(entities, opts) {
22741
- if (isEmptyArray(entities))
22742
- return entities; // Nothing to save: skip
22743
- if (!this.mutations.saveAll) {
22744
- if (!this.mutations.save)
22745
- throw new Error('Not implemented');
22746
- // Save one by one
22747
- return chainPromises((entities || [])
22748
- .map(entity => (() => this.save(entity, opts))));
22878
+ }
22879
+
22880
+ class IPosition {
22881
+ }
22882
+ class GeolocationUtils {
22883
+ static isNotNilAndValid(position, opts) {
22884
+ if (!position || isNil(position.latitude) || isNil(position.longitude))
22885
+ return false;
22886
+ // Invalid lat/lon
22887
+ if (position.latitude < -90 || position.latitude > 90
22888
+ || position.longitude < -180 || position.longitude > 180) {
22889
+ // /!\ Log in console, because should never occur
22890
+ if (opts?.debug)
22891
+ console.warn('Invalid lat/long position: ', position);
22892
+ return false;
22749
22893
  }
22750
- let hasNewEntities = false;
22751
- const json = entities.map(entity => {
22752
- hasNewEntities = hasNewEntities || this.isNewFn(entity);
22753
- this.fillDefaultProperties(entity);
22754
- return this.asObject(entity);
22755
- });
22756
- const now = Date.now();
22757
- if (this._debug)
22758
- console.debug(this._logPrefix + `Saving all ${this._logTypeName}...`, json);
22759
- await this.graphql.mutate({
22760
- mutation: this.mutations.saveAll,
22761
- refetchQueries: this.getRefetchQueriesForMutation(opts),
22762
- awaitRefetchQueries: opts && opts.awaitRefetchQueries,
22763
- variables: {
22764
- data: json
22765
- },
22766
- error: { code: ErrorCodes.SAVE_DATA_ERROR, message: 'ERROR.SAVE_DATA_ERROR' },
22767
- update: (proxy, { data }) => {
22768
- const savedEntities = data && data.data;
22769
- if (savedEntities) {
22770
- // Update entities (id and update date)
22771
- entities.forEach(entity => {
22772
- const savedEntity = savedEntities.find(e => this.equalsFn(e, entity));
22773
- this.copyIdAndUpdateDate(savedEntity, entity);
22774
- });
22775
- if (hasNewEntities && this.watchQueriesUpdatePolicy === 'update-cache') {
22776
- // Update the cache
22777
- this.insertIntoMutableCachedQueries(proxy, {
22778
- queries: this.getLoadQueries(),
22779
- data: savedEntities
22780
- });
22781
- }
22782
- }
22783
- if (opts && opts.update) {
22784
- opts.update(proxy, { data });
22785
- }
22786
- if (this._debug)
22787
- console.debug(`${this._logTypeName} saved in ${Date.now() - now}ms`, entities);
22788
- }
22789
- });
22790
- return entities;
22894
+ // OK: valid
22895
+ return true;
22791
22896
  }
22792
- /**
22793
- * Save a referential entity
22794
- *
22795
- * @param entity
22796
- * @param opts
22797
- */
22798
- async save(entity, opts) {
22799
- if (!this.mutations.save) {
22800
- if (!this.mutations.saveAll)
22801
- throw new Error('Not implemented');
22802
- const data = await this.saveAll([entity], opts);
22803
- return data && data[0];
22804
- }
22805
- // Fill default properties
22806
- this.fillDefaultProperties(entity);
22807
- // Transform into json
22808
- const json = this.asObject(entity);
22809
- const isNew = this.isNewFn(json);
22810
- const now = Date.now();
22811
- if (this._debug)
22812
- console.debug(this._logPrefix + `Saving ${this._logTypeName}...`, json);
22813
- await this.graphql.mutate({
22814
- mutation: this.mutations.save,
22815
- refetchQueries: this.getRefetchQueriesForMutation(opts),
22816
- awaitRefetchQueries: opts && opts.awaitRefetchQueries,
22817
- variables: {
22818
- data: json
22819
- },
22820
- error: { code: ErrorCodes.SAVE_DATA_ERROR, message: 'ERROR.SAVE_DATA_ERROR' },
22821
- update: (cache, { data }) => {
22822
- // Update entity
22823
- const savedEntity = data && data.data;
22824
- this.copyIdAndUpdateDate(savedEntity, entity);
22825
- // Insert into the cache
22826
- if (isNew && this.watchQueriesUpdatePolicy === 'update-cache') {
22827
- this.insertIntoMutableCachedQueries(cache, {
22828
- queries: this.getLoadQueries(),
22829
- data: savedEntity
22830
- });
22831
- }
22832
- if (opts && opts.update) {
22833
- opts.update(cache, { data });
22834
- }
22835
- if (this._debug)
22836
- console.debug(this._logPrefix + `${entity.__typename} saved in ${Date.now() - now}ms`, entity);
22837
- }
22838
- });
22839
- return entity;
22897
+ static isNilOrInvalid(position, opts) {
22898
+ return !GeolocationUtils.isNotNilAndValid(position, opts);
22840
22899
  }
22841
- /**
22842
- * Delete referential entities
22843
- */
22844
- async deleteAll(entities, opts) {
22845
- if (!this.mutations.deleteAll) {
22846
- if (!this.mutations.delete)
22847
- throw new Error('Not implemented');
22848
- // Delete one by one
22849
- return chainPromises((entities || [])
22850
- .map(entity => (() => this.delete(entity, opts))));
22851
- }
22852
- // Filter saved entities
22853
- entities = entities && entities.filter(e => isNotNil(e.id));
22854
- // Nothing to save: skip
22855
- if (isEmptyArray(entities))
22900
+ static computeDistanceInMiles(position1, position2) {
22901
+ // Invalid position(s): skip
22902
+ if (GeolocationUtils.isNilOrInvalid(position1, { debug: true })
22903
+ || GeolocationUtils.isNilOrInvalid(position2, { debug: true }))
22856
22904
  return;
22857
- const ids = entities.map(t => t.id);
22858
- const now = Date.now();
22859
- if (this._debug)
22860
- console.debug(this._logPrefix + `Deleting ${this._logTypeName}...`, ids);
22861
- await this.graphql.mutate({
22862
- mutation: this.mutations.deleteAll,
22863
- refetchQueries: this.getRefetchQueriesForMutation(opts),
22864
- awaitRefetchQueries: opts && opts.awaitRefetchQueries,
22865
- variables: { ids },
22866
- error: { code: ErrorCodes.DELETE_DATA_ERROR, message: 'ERROR.DELETE_DATA_ERROR' },
22867
- update: (cache, res) => {
22868
- // Remove from cache
22869
- if (this.watchQueriesUpdatePolicy === 'update-cache') {
22870
- this.removeFromMutableCachedQueriesByIds(cache, {
22871
- queries: this.getLoadQueries(),
22872
- ids
22873
- });
22874
- }
22875
- if (opts && opts.update) {
22876
- opts.update(cache, res);
22877
- }
22878
- if (this._debug)
22879
- console.debug(this._logPrefix + `${this._logTypeName} deleted in ${Date.now() - now}ms`);
22880
- }
22881
- });
22905
+ const latitude1Rad = Math.PI * position1.latitude / 180;
22906
+ const longitude1Rad = Math.PI * position1.longitude / 180;
22907
+ const latitude2Rad = Math.PI * position2.latitude / 180;
22908
+ const longitude2Rad = Math.PI * position2.longitude / 180;
22909
+ let distance = 2 * 6371 * Math.asin(Math.sqrt(Math.pow(Math.sin((latitude1Rad - latitude2Rad) / 2), 2)
22910
+ + Math.cos(latitude1Rad) * Math.cos(latitude2Rad) * Math.pow(Math.sin((longitude2Rad - longitude1Rad) / 2), 2)));
22911
+ distance = Math.round((((distance / 1.852) + Number.EPSILON) * 100)) / 100;
22912
+ return distance;
22882
22913
  }
22883
22914
  /**
22884
- * Delete a referential entity
22885
- */
22886
- async delete(entity, opts) {
22887
- if (!this.mutations.delete) {
22888
- if (!this.mutations.deleteAll)
22889
- throw new Error('Not implemented');
22890
- const data = await this.deleteAll([entity], opts);
22891
- return data && data[0];
22892
- }
22893
- // Nothing to save: skip
22894
- if (!entity || isNil(entity.id))
22895
- return;
22896
- const id = entity.id;
22897
- const now = new Date();
22898
- if (this._debug)
22899
- console.debug(this._logPrefix + `Deleting ${this._logTypeName} {${id}}...`);
22900
- await this.graphql.mutate({
22901
- mutation: this.mutations.delete,
22902
- refetchQueries: this.getRefetchQueriesForMutation(opts),
22903
- awaitRefetchQueries: opts && opts.awaitRefetchQueries,
22904
- variables: { id },
22905
- error: { code: ErrorCodes.DELETE_DATA_ERROR, message: 'ERROR.DELETE_DATA_ERROR' },
22906
- update: (proxy, res) => {
22907
- // Remove from cache
22908
- if (this.watchQueriesUpdatePolicy === 'update-cache') {
22909
- this.removeFromMutableCachedQueriesByIds(proxy, {
22910
- queries: this.getLoadQueries(),
22911
- ids: [id]
22912
- });
22913
- }
22914
- if (opts && opts.update) {
22915
- opts.update(proxy, res);
22915
+ * Get the position by geo loc sensor
22916
+ */
22917
+ static async getCurrentPosition(platform, options) {
22918
+ // Use Capacitor plugin
22919
+ try {
22920
+ console.info(`[geolocation-utils] Get current geo location, using Capacitor...(timeout: ${options?.timeout})`);
22921
+ const pos = await Geolocation.getCurrentPosition(options);
22922
+ return {
22923
+ latitude: pos.coords.latitude,
22924
+ longitude: pos.coords.longitude
22925
+ };
22926
+ }
22927
+ catch (err) {
22928
+ // If capacitor : throw error (cannot fallback to browser geolocation, because of browser security limitation)
22929
+ if (platform.is('capacitor')) {
22930
+ console.error(`[geolocation-utils] Cannot get current geo location, using Capacitor: ${err?.message || ''}`);
22931
+ // Detect a permission error, to add code
22932
+ // FIXME - Capacitor use hardcoded strings! (see https://github.com/ionic-team/capacitor-plugins)
22933
+ const message = (err?.message || '').toLowerCase();
22934
+ if (message.includes('location permission was denied') || message.includes('location disabled')) {
22935
+ throw { ...err, code: GeolocationPositionError.PERMISSION_DENIED };
22916
22936
  }
22917
- if (this._debug)
22918
- console.debug(this._logPrefix + `${this._logTypeName} deleted in ${new Date().getTime() - now.getTime()}ms`);
22937
+ throw err;
22919
22938
  }
22939
+ }
22940
+ // Or fallback to navigator
22941
+ console.info('[geolocation-utils] Get current geo location, using browser...');
22942
+ return new Promise((resolve, reject) => {
22943
+ navigator.geolocation.getCurrentPosition((res) => {
22944
+ resolve({
22945
+ latitude: res.coords.latitude,
22946
+ longitude: res.coords.longitude
22947
+ });
22948
+ }, (err) => {
22949
+ let message = err?.message || err;
22950
+ message = typeof message === 'string' ? message : JSON.stringify(message);
22951
+ console.error('[geolocation-utils] Cannot get current geo location, using browser:' + message);
22952
+ reject(err);
22953
+ }, options);
22920
22954
  });
22921
22955
  }
22922
- listenChanges(id, opts) {
22923
- if (isNil(id))
22924
- throw Error('Missing argument \'id\' ');
22925
- if (!this.subscriptions.listenChanges) {
22926
- console.warn(`${this.constructor.name}.listenChanges() not implemented yet. Will empty observable`);
22927
- return of();
22928
- }
22929
- const variables = opts?.variables || {
22930
- id,
22931
- interval: toNumber(opts && opts.interval, 0) // no timer by default
22932
- };
22933
- if (this._debug)
22934
- console.debug(this._logPrefix + `[WS] Listening for changes on ${this._logTypeName} {${id}}...`);
22935
- return this.graphql.subscribe({
22936
- query: opts?.query || this.subscriptions.listenChanges,
22937
- fetchPolicy: opts?.fetchPolicy || 'no-cache',
22938
- variables,
22939
- error: {
22940
- code: ErrorCodes.SUBSCRIBE_DATA_ERROR,
22941
- message: 'ERROR.SUBSCRIBE_DATA_ERROR'
22956
+ }
22957
+
22958
+ //@dynamic
22959
+ class CsvUtils {
22960
+ static exportToFile(rows, opts) {
22961
+ if (!rows || !rows.length)
22962
+ return; // Skip if empty
22963
+ // Create text content
22964
+ const separator = opts?.separator || ',';
22965
+ const protectCellRegexp = new RegExp("/(\"|" + separator + "|\n)/g");
22966
+ const keys = Object.keys(rows[0]);
22967
+ const headers = opts?.headers || keys;
22968
+ const textContent =
22969
+ // Header row
22970
+ headers.join(separator) + '\n' +
22971
+ // Data rows
22972
+ rows.map(row => {
22973
+ return keys.map(k => {
22974
+ const cell = row[k] === null || row[k] === undefined ? '' : row[k];
22975
+ let cellStr = cell instanceof Date
22976
+ ? cell.toLocaleString()
22977
+ : cell.toString().replace(/"/g, '""');
22978
+ if (protectCellRegexp.test(cellStr)) {
22979
+ cellStr = `"${cellStr}"`;
22980
+ }
22981
+ return cellStr;
22982
+ }).join(separator);
22983
+ }).join('\n');
22984
+ // Write to file
22985
+ FilesUtils.writeTextToFile(textContent, {
22986
+ type: 'text/csv',
22987
+ filename: 'export.csv',
22988
+ ...opts,
22989
+ });
22990
+ }
22991
+ static parseFile(file, opts) {
22992
+ return FilesUtils.readAsText(file, opts?.encoding)
22993
+ .pipe(map(e => {
22994
+ if (e.type === HttpEventType.UploadProgress) {
22995
+ const loaded = Math.round(e.loaded * 0.8);
22996
+ return { ...e, loaded };
22942
22997
  }
22943
- })
22944
- .pipe(map(({ data }) => {
22945
- const entity = (!opts || opts.toEntity !== false) ? data && this.fromObject(data) : data;
22946
- //if (entity && this._debug) console.debug(this._logPrefix + `[WS] Received changes on ${this._logTypeName} {${id}}`, entity);
22947
- // TODO: missing = deleted ?
22948
- if (!entity)
22949
- console.warn(this._logPrefix + `[WS] Received deletion on ${this._logTypeName} {${id}} - TODO check implementation`);
22950
- return entity;
22951
- }));
22998
+ else if (e instanceof FileResponse) {
22999
+ const body = e.body;
23000
+ const data = CsvUtils.parseCSV(body, opts);
23001
+ return new FileResponse({ body: data });
23002
+ }
23003
+ }), filter(isNotNil));
22952
23004
  }
22953
- copyIdAndUpdateDate(source, target) {
22954
- if (!source)
22955
- return;
22956
- // Update (id and updateDate)
22957
- EntityUtils.copyIdAndUpdateDate(source, target);
23005
+ static parseCSV(body, opts) {
23006
+ const separator = opts?.separator || ',';
23007
+ const skipEmptyLine = !opts || opts.skipEmptyLine !== false;
23008
+ // Protect special characters (quote and /n)
23009
+ body = body.replace('""', '<quote>') // Protect double quote
23010
+ .replace(/("[^\n"]+)\n\r?/gm, '"$1<br>') // Protect \n inside a quoted expression
23011
+ .replace(/\n\r/gm, '\n'); // Windows CR
23012
+ const headerAndRows = body.split('\n', 2);
23013
+ const headers = headerAndRows[0].split(separator);
23014
+ return body.split('\n') // split into rows
23015
+ .filter(line => !skipEmptyLine || line.trim().length > 0) // Skip empty line
23016
+ .map(line => {
23017
+ const cells = line.split(separator, headers.length) // split into cells
23018
+ .map(cell => cell.replace(/^"([^"]*)"$/, '$1')) // Clean trailing quotes
23019
+ .map(cell => cell.replace('<quote>', '"')) // restore protected quote
23020
+ .map(cell => cell.replace('<br>', '\n')) // restore protected br
23021
+ ;
23022
+ return cells;
23023
+ });
22958
23024
  }
22959
- fromObject(source) {
22960
- if (!source)
22961
- return source;
22962
- const target = new this.dataType();
22963
- target.fromObject(source);
22964
- return target;
23025
+ }
23026
+
23027
+ /**
23028
+ * Define here theme colors
23029
+ */
23030
+ const rgbArrayMap = {
23031
+ white: [255, 255, 255],
23032
+ primary: [20, 67, 145],
23033
+ secondary: [117, 196, 253],
23034
+ tertiary: [91, 94, 244],
23035
+ danger: [245, 61, 61],
23036
+ light: [244, 245, 248],
23037
+ medium: [152, 154, 162],
23038
+ dark: [34, 36, 40],
23039
+ red: [255, 0, 0],
23040
+ green: [0, 255, 0],
23041
+ blue: [0, 0, 255]
23042
+ };
23043
+ // Fill a map of Color objects
23044
+ const colorsMap = {};
23045
+ /**
23046
+ * Useful class for color conversion
23047
+ */
23048
+ // @dynamic
23049
+ class Color {
23050
+ constructor(_rgbArray, _opacity, _name = 'custom') {
23051
+ this._rgbArray = _rgbArray;
23052
+ this._opacity = _opacity;
23053
+ this._name = _name;
22965
23054
  }
22966
- asFilter(source) {
22967
- return EntityFilterUtils.fromObject(source, this.filterType);
23055
+ // Helper method, to retrieve a color
23056
+ static get(name) {
23057
+ return colorsMap[name];
22968
23058
  }
22969
- /* -- protected functions -- */
22970
- async ngOnStart() {
22971
- // No limit for updatable watch queries, if desktop
22972
- if (!this.platform.mobile) {
22973
- this._mutableWatchQueriesMaxCount = -1;
22974
- }
23059
+ static parseRgba(rgba) {
23060
+ if (!rgba || (!rgba.startsWith('rgb(') && !rgba.startsWith('rgba(')))
23061
+ return null;
23062
+ // Parse parts
23063
+ const parts = rgba
23064
+ .replace('rgb(', '')
23065
+ .replace('rgba(', '')
23066
+ .replace(')', '')
23067
+ .split(',');
23068
+ if (parts.length !== 3 && parts.length !== 4)
23069
+ return null;
23070
+ return new Color([+parts[0], +parts[1], +parts[2]], parts.length === 4 && +parts[3] || 1, 'custom');
22975
23071
  }
22976
- fillDefaultProperties(source) {
22977
- // Can be override by subclasses
23072
+ get name() {
23073
+ return this._name;
22978
23074
  }
22979
- asObject(entity, opts) {
22980
- // Can be override by subclasses
22981
- return entity.asObject(opts);
23075
+ get opacity() {
23076
+ return isNotNil(this._opacity) ? this._opacity : 1;
22982
23077
  }
22983
- getRefetchQueriesForMutation(opts) {
22984
- if (opts && opts.refetchQueries)
22985
- return opts.refetchQueries;
22986
- // Skip if update policy not used refecth queries
22987
- if (this.watchQueriesUpdatePolicy !== 'refetch-queries')
22988
- return undefined;
22989
- const queries = this.getLoadQueries();
22990
- if (!queries.length)
22991
- return undefined; // Skip if empty
22992
- // Find the refetch queries definition
22993
- return this.findRefetchQueries({ queries });
23078
+ get rgb() {
23079
+ return this._rgbArray;
22994
23080
  }
22995
- getLoadQueries() {
22996
- return [this.queries.loadAll, this.queries.loadAllWithTotal].filter(isNotNil);
23081
+ get r() {
23082
+ return this._rgbArray[0];
22997
23083
  }
22998
- getLoadQueryNames() {
22999
- return ['LoadAll', 'LoadAllWithTotal'];
23084
+ get g() {
23085
+ return this._rgbArray[1];
23000
23086
  }
23001
- }
23002
- BaseEntityService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseEntityService, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
23003
- BaseEntityService.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.3.0", type: BaseEntityService, usesInheritance: true, ngImport: i0 });
23004
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BaseEntityService, decorators: [{
23005
- type: Directive
23006
- }], ctorParameters: function () { return [{ type: GraphqlService }, { type: PlatformService }, { type: undefined }, { type: undefined }, { type: undefined }]; } });
23007
-
23008
- let NamedFilter = class NamedFilter extends Entity {
23009
- fromObject(source, opts) {
23010
- this.name = source.name;
23011
- this.entityName = source.entityName;
23012
- this.recorderPersonId = source.recorderPersonId;
23013
- this.recorderDepartmentId = source.recorderDepartmentId;
23014
- try {
23015
- this.content = typeof source?.content === 'string' ? JSON.parse(source?.content) : source?.content || undefined;
23016
- }
23017
- catch (e) {
23018
- console.error('[named-filter] can\'t parse content json', source);
23019
- throw e;
23020
- }
23087
+ get b() {
23088
+ return this._rgbArray[2];
23021
23089
  }
23022
- asObject(opts) {
23023
- const target = super.asObject(opts);
23024
- if (opts?.minify) {
23025
- target.content = JSON.stringify(this.content);
23090
+ rgba(opacity) {
23091
+ opacity = isNotNil(opacity) ? opacity : this._opacity;
23092
+ if (isNil(opacity) || opacity < 0 || opacity > 1) {
23093
+ return 'rgb(' + this._rgbArray.join(',') + ')';
23026
23094
  }
23027
- return target;
23095
+ return 'rgba(' + this._rgbArray.join(',') + ',' + opacity + ')';
23028
23096
  }
23097
+ }
23098
+ Color.transparent = function () {
23099
+ return new Color([0, 0, 0], 0, 'translucent');
23029
23100
  };
23030
- NamedFilter = __decorate([
23031
- EntityClass({ typename: 'NamedFilterVO' })
23032
- ], NamedFilter);
23033
- let NamedFilterFilter = class NamedFilterFilter extends EntityFilter {
23034
- constructor() {
23035
- super(...arguments);
23036
- this.searchText = null;
23037
- this.entityName = null;
23038
- this.recorderPersonId = null;
23039
- this.recorderDepartmentId = null;
23101
+ /**
23102
+ * Helper class for colors scale
23103
+ */
23104
+ // @dynamic
23105
+ class ColorScale {
23106
+ constructor(colorArray, options) {
23107
+ options = options || {};
23108
+ // reserved last colors for value > max
23109
+ const nbIntervalBeforeUpper = !options.upperMax ? colorArray.length : (colorArray.length - 1);
23110
+ this._min = options.min || 0;
23111
+ this._max = options.max || nbIntervalBeforeUpper;
23112
+ this._rangeSize = Math.round((this._max - this._min) / nbIntervalBeforeUpper);
23113
+ this._legendItems = this.computeLegendItems(colorArray);
23040
23114
  }
23041
- fromObject(source, opts) {
23042
- super.fromObject(source, opts);
23043
- this.searchText = source.searchText;
23044
- this.entityName = source.entityName;
23045
- this.recorderPersonId = source.recorderPersonId;
23046
- this.recorderDepartmentId = source.recorderDepartmentId;
23115
+ static default() {
23116
+ return ColorScale.custom(25);
23117
+ }
23118
+ /**
23119
+ * Create a array with the given color
23120
+ **/
23121
+ static fix(length, colorName) {
23122
+ return Array.apply(null, Array(length || 25))
23123
+ .map(String.prototype.valueOf, Color.get(colorName || 'primary').rgba(0.5));
23124
+ }
23125
+ get min() {
23126
+ return this._min;
23127
+ }
23128
+ get max() {
23129
+ return this._max;
23130
+ }
23131
+ get legend() {
23132
+ return {
23133
+ items: this._legendItems
23134
+ };
23135
+ }
23136
+ getValueColor(value) {
23137
+ return this.getLegendForValue(value).color;
23138
+ }
23139
+ getLegendForValue(value) {
23140
+ if (value <= this._min)
23141
+ return this._legendItems[0];
23142
+ if (value >= this._max)
23143
+ return this._legendItems[this._legendItems.length - 1];
23144
+ const index = Math.floor(value * (this._legendItems.length - 1) / this._max);
23145
+ return this._legendItems[index];
23047
23146
  }
23147
+ getLegendAtIndex(index) {
23148
+ return this._legendItems[index];
23149
+ }
23150
+ computeLegendItems(colorArray) {
23151
+ return colorArray.map((color, index) => {
23152
+ const start = index * this._rangeSize;
23153
+ const end = start + this._rangeSize;
23154
+ return {
23155
+ color: new Color(color),
23156
+ label: (end < this._max) ? `${start.toLocaleString()} - ${end.toLocaleString()}` : ` >= ${start}`
23157
+ };
23158
+ });
23159
+ }
23160
+ }
23161
+ ColorScale.custom = (count, options) => {
23162
+ options = options || {};
23163
+ return new ColorScale(linearColorGradientWithIntermediate(count, {
23164
+ startColor: options.startColor || undefined,
23165
+ mainColor: options.mainColor || undefined,
23166
+ mainColorIndex: isNotNil(options.mainColorIndex) ? options.mainColorIndex : undefined,
23167
+ endColor: options.endColor || undefined
23168
+ }), options);
23048
23169
  };
23049
- NamedFilterFilter = __decorate([
23050
- EntityClass({ typename: 'NamedFilterFilterVO' })
23051
- ], NamedFilterFilter);
23052
- const APP_NAMED_FILTER_SERVICE = new InjectionToken('namedFilterService');
23053
- // @dynamic
23054
- class AbstractNamedFilterService extends BaseEntityService {
23055
- constructor(graphql, platform, dataType, filterType, options) {
23056
- super(graphql, platform, dataType, filterType, {
23057
- defaultSortBy: 'name',
23058
- defaultSortDirection: 'asc',
23059
- ...options
23170
+ // Fill colorsMap
23171
+ Object.getOwnPropertyNames(rgbArrayMap)
23172
+ .forEach((key) => {
23173
+ colorsMap[key] = new Color(rgbArrayMap[key], 1, key);
23174
+ });
23175
+ /**
23176
+ * Internal function, that create a colors scale, using iteration
23177
+ *
23178
+ * @param count
23179
+ * @param opacity
23180
+ * @param startColor
23181
+ * @param startState
23182
+ * @returns
23183
+ */
23184
+ function linearColorGradientWithIntermediate(count, options) {
23185
+ options = options || {};
23186
+ // From [0,1]
23187
+ options.startColor = options.startColor || [255, 255, 190]; // default start = creme
23188
+ options.mainColorIndex = options.mainColorIndex && options.mainColorIndex < count - 1 ? options.mainColorIndex : count - 1;
23189
+ options.endColor = options.endColor || [255, 0, 0]; // default main = red
23190
+ if (!options.mainColor) {
23191
+ return linearColorGradient(count, {
23192
+ startColor: options.startColor,
23193
+ endColor: options.endColor
23060
23194
  });
23061
- this.graphql = graphql;
23062
- this.platform = platform;
23063
- this.dataType = dataType;
23064
- this.filterType = filterType;
23065
- this.options = options;
23066
- this._logPrefix = '[named-filter-service] ';
23067
23195
  }
23068
- watchAll(offset, size, sortBy, sortDirection, filter, options) {
23069
- if (!filter) {
23070
- filter = this.defaultFilter();
23196
+ else {
23197
+ // Step 1: startColor -> mainColor
23198
+ const result = linearColorGradient(options.mainColorIndex + 1, {
23199
+ startColor: options.startColor,
23200
+ endColor: options.mainColor
23201
+ });
23202
+ // Step 2: mainColor -> endColor
23203
+ if (options.mainColorIndex < count - 1) {
23204
+ return result.concat(linearColorGradient(count - options.mainColorIndex, {
23205
+ startColor: options.mainColor,
23206
+ endColor: options.endColor
23207
+ }));
23071
23208
  }
23072
- // Always ask total
23073
- options = {
23074
- ...options,
23075
- withTotal: true
23076
- };
23077
- // Set query if content is wanted
23078
- if (options?.withContent) {
23079
- options.query = this.queries.loadAllWithContent;
23209
+ else {
23210
+ return result;
23080
23211
  }
23081
- return super.watchAll(offset, size, sortBy, sortDirection, filter, options);
23082
23212
  }
23083
- async loadAll(offset, size, sortBy, sortDirection, filter, options) {
23084
- if (!filter) {
23085
- filter = this.defaultFilter();
23086
- }
23087
- // Always ask total
23088
- options = {
23089
- ...options,
23090
- withTotal: true
23091
- };
23092
- // Set query if content is wanted
23093
- if (options?.withContent) {
23094
- options.query = this.queries.loadAllWithContent;
23213
+ }
23214
+ function linearColorGradient(count, options) {
23215
+ options = options || {};
23216
+ // From [0,1]
23217
+ options.startColor = options.startColor || [255, 255, 255]; // default start = white
23218
+ options.endColor = options.endColor || [255, 0, 0]; // default end = red
23219
+ const result = [];
23220
+ const color = options.startColor.slice(); // copy the start color
23221
+ const delta = [
23222
+ Math.round((options.endColor[0] - options.startColor[0]) / count),
23223
+ Math.round((options.endColor[1] - options.startColor[1]) / count),
23224
+ Math.round((options.endColor[2] - options.startColor[2]) / count)
23225
+ ];
23226
+ for (let i = 0; i < count - 1; i++) {
23227
+ for (let j = 0; j < 3; j++) {
23228
+ color[j] += delta[j];
23095
23229
  }
23096
- return super.loadAll(offset, size, sortBy, sortDirection, filter, options);
23230
+ result.push(color.slice());
23097
23231
  }
23098
- async suggest(value, filter) {
23099
- if (EntityUtils.isNotEmpty(value, 'id'))
23100
- return { data: [value] };
23101
- value = (typeof value === 'string' && value !== '*' && value) || undefined;
23102
- return await this.loadAll(0, 1000, 'name', 'asc', this.suggestFilter(value, filter), {
23103
- withTotal: true,
23104
- withContent: false
23105
- });
23232
+ // Force last color = end color
23233
+ result.push(options.endColor.slice());
23234
+ // Output as array
23235
+ return result;
23236
+ }
23237
+
23238
+ class DebugComponent {
23239
+ constructor(environment) {
23240
+ this.title = '';
23241
+ this.enable = null;
23242
+ this.expanded = false;
23243
+ this.enable = !environment || !environment.production;
23106
23244
  }
23107
- suggestFilter(searchText, filter) {
23108
- return {
23109
- ...filter,
23110
- searchText
23111
- };
23245
+ toggle() {
23246
+ this.expansionPanel.toggle();
23112
23247
  }
23113
23248
  }
23114
- AbstractNamedFilterService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AbstractNamedFilterService, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
23115
- AbstractNamedFilterService.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.3.0", type: AbstractNamedFilterService, usesInheritance: true, ngImport: i0 });
23116
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AbstractNamedFilterService, decorators: [{
23117
- type: Directive
23118
- }], ctorParameters: function () { return [{ type: GraphqlService }, { type: PlatformService }, { type: undefined }, { type: undefined }, { type: undefined }]; } });
23249
+ DebugComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: DebugComponent, deps: [{ token: ENVIRONMENT, optional: true }], target: i0.ɵɵFactoryTarget.Component });
23250
+ DebugComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: DebugComponent, selector: "app-debug", inputs: { title: "title", enable: "enable", expanded: "expanded" }, viewQueries: [{ propertyName: "expansionPanel", first: true, predicate: ["expansionPanel"], descendants: true }], ngImport: i0, template: "<!-- debug -->\n<ion-item tappable (click)=\"toggle()\" lines=\"none\" color=\"secondary50\">\n <ion-icon slot=\"start\" name=\"bug\" ></ion-icon>\n <ion-label color=\"primary\">Debug {{title}}</ion-label>\n <mat-icon slot=\"end\">{{expansionPanel.expanded ? 'expand_less' : 'expand_more' }}</mat-icon>\n</ion-item>\n<mat-expansion-panel #expansionPanel [(expanded)]=\"expanded\">\n <ion-text color=\"primary100\" >\n <div class=\"ion-margin-start\">\n <ng-content></ng-content>\n </div>\n </ion-text>\n</mat-expansion-panel>\n", styles: ["ion-item{cursor:pointer!important}ion-item mat-icon,ion-item ion-icon{color:var(--ion-color-primary)}.mat-expansion-panel{--expansion-panel-padding: 4px;background:var(--ion-color-secondary50)!important}.mat-expansion-panel.mat-expanded{margin-bottom:4px!important}.mat-expansion-panel .mat-expansion-panel-body *{color:var(--ion-color-primary100)}\n"], dependencies: [{ kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i9$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["disabled", "expanded", "hideToggle", "togglePosition"], outputs: ["opened", "closed", "expandedChange", "afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }] });
23251
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: DebugComponent, decorators: [{
23252
+ type: Component,
23253
+ args: [{ selector: 'app-debug', template: "<!-- debug -->\n<ion-item tappable (click)=\"toggle()\" lines=\"none\" color=\"secondary50\">\n <ion-icon slot=\"start\" name=\"bug\" ></ion-icon>\n <ion-label color=\"primary\">Debug {{title}}</ion-label>\n <mat-icon slot=\"end\">{{expansionPanel.expanded ? 'expand_less' : 'expand_more' }}</mat-icon>\n</ion-item>\n<mat-expansion-panel #expansionPanel [(expanded)]=\"expanded\">\n <ion-text color=\"primary100\" >\n <div class=\"ion-margin-start\">\n <ng-content></ng-content>\n </div>\n </ion-text>\n</mat-expansion-panel>\n", styles: ["ion-item{cursor:pointer!important}ion-item mat-icon,ion-item ion-icon{color:var(--ion-color-primary)}.mat-expansion-panel{--expansion-panel-padding: 4px;background:var(--ion-color-secondary50)!important}.mat-expansion-panel.mat-expanded{margin-bottom:4px!important}.mat-expansion-panel .mat-expansion-panel-body *{color:var(--ion-color-primary100)}\n"] }]
23254
+ }], ctorParameters: function () { return [{ type: Environment, decorators: [{
23255
+ type: Inject,
23256
+ args: [ENVIRONMENT]
23257
+ }, {
23258
+ type: Optional
23259
+ }] }]; }, propDecorators: { title: [{
23260
+ type: Input
23261
+ }], enable: [{
23262
+ type: Input
23263
+ }], expanded: [{
23264
+ type: Input
23265
+ }], expansionPanel: [{
23266
+ type: ViewChild,
23267
+ args: ['expansionPanel']
23268
+ }] } });
23269
+
23270
+ class SharedDebugModule {
23271
+ }
23272
+ SharedDebugModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: SharedDebugModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
23273
+ SharedDebugModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: SharedDebugModule, declarations: [DebugComponent], imports: [CommonModule,
23274
+ IonicModule,
23275
+ MatExpansionModule,
23276
+ MatIconModule, i1$2.TranslateModule, MatExpansionModule], exports: [DebugComponent,
23277
+ TranslateModule] });
23278
+ SharedDebugModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: SharedDebugModule, imports: [CommonModule,
23279
+ IonicModule,
23280
+ MatExpansionModule,
23281
+ MatIconModule,
23282
+ TranslateModule.forChild(),
23283
+ MatExpansionModule, TranslateModule] });
23284
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: SharedDebugModule, decorators: [{
23285
+ type: NgModule,
23286
+ args: [{
23287
+ imports: [
23288
+ CommonModule,
23289
+ IonicModule,
23290
+ MatExpansionModule,
23291
+ MatIconModule,
23292
+ TranslateModule.forChild(),
23293
+ MatExpansionModule
23294
+ ],
23295
+ declarations: [
23296
+ DebugComponent
23297
+ ],
23298
+ exports: [
23299
+ DebugComponent,
23300
+ TranslateModule
23301
+ ]
23302
+ }]
23303
+ }] });
23119
23304
 
23120
23305
  class AppGraphQLModule {
23121
23306
  constructor() {
@@ -23659,7 +23844,7 @@ class MenuComponent {
23659
23844
  }
23660
23845
  }
23661
23846
  MenuComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuComponent, deps: [{ token: PlatformService }, { token: AccountService }, { token: i1.NavController }, { token: i1.MenuController }, { token: i1.ModalController }, { token: i1.AlertController }, { token: i1$2.TranslateService }, { token: ConfigService }, { token: i0.ChangeDetectorRef }, { token: MenuService }, { token: ENVIRONMENT }, { token: APP_MENU_OPTIONS, optional: true }, { token: APP_MENU_ITEMS, optional: true }], target: i0.ɵɵFactoryTarget.Component });
23662
- MenuComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: MenuComponent, selector: "app-menu", inputs: { id: "id", menuId: "menuId", side: "side", contentId: "contentId", logo: "logo", appName: "appName", appVersion: "appVersion" }, viewQueries: [{ propertyName: "splitPane", first: true, predicate: ["splitPane"], descendants: true, static: true }], ngImport: i0, template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" [contentId]=\"contentId\">\n <ion-header>\n\n <ion-toolbar @fadeInAnimation *ngIf=\"isLogin; else notLogin\" class=\"ion-toolbar-top\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button type=\"button\" mat-flat-button\n class=\"user-avatar\" [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url('+(accountAvatar||'./assets/img/person.png')+')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"'MENU.BTN_MY_ACCOUNT'|translate\">\n </button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"108px\"/>\n <span *ngIf=\"!logo\" style=\"width: 108px;\">{{appName}}</span>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n <ion-col>\n <button mat-button type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"'MENU.BTN_MY_ACCOUNT'|translate\">\n <ion-label color=\"primary\" class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{accountName}}</b>\n </h3>\n <h4>{{accountEmail}}</h4>\n </ion-label>\n </button>\n </ion-col>\n\n <!-- Insertion headerBottomRight -->\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"headerBottomRight\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar class=\"ion-padding\" @fadeInAnimation\n style=\"height: unset; display: block; margin: auto; text-align: center;\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"150px;\">\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{appName}}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n\n <ion-list lines=\"none\">\n <ion-menu-toggle auto-hide=\"false\"\n [class.flex-spacer]=\"item.cssClass == 'flex-spacer'\"\n *ngFor=\"let item of $items | async\">\n\n <!-- link -->\n <ion-item *ngIf=\"!loading && item.path\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n [routerLink]=\"item.path\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{exact: (item.path === '/')}\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- action -->\n <ion-item *ngIf=\"!loading && item.action\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n (click)=\"doAction(item.action, $event)\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- divider -->\n <ion-item-divider @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n *ngIf=\"!loading && !item.path && !item.action\">\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item-divider>\n\n </ion-menu-toggle>\n </ion-list>\n </ion-content>\n\n <ion-footer class=\"hidden-xs hidden-sm\">\n <ion-toolbar>\n\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"openAboutModal($event)\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"openAboutModal($event)\" color=\"medium\">\n {{'MENU.FOOTER_VERSION_ABOUT'| translate: {version: appVersion} }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button mat-icon-button color=\"accent\" (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') |translate\">\n <mat-icon><span>{{splitPane.when ? '&#xab;' : '&#xbb;'}}</span></mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n\n </ion-menu>\n\n <ng-content></ng-content>\n\n</ion-split-pane>\n\n<ng-template #headerBottomRight>\n <ng-content select=\"[headerBottomRight]\"></ng-content>\n</ng-template>\n", styles: ["ion-menu{--ion-item-background: transparent;--ion-item-divider-background: transparent;--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint);--ion-item-background-selected: var(--ion-color-secondary100);--ion-item-text-color-selected: var(--ion-color-primary);--ion-item-icon-color-selected: var(--ion-color-primary);--ion-item-text-color-disable: var(--ion-color-medium);--ion-item-icon-color-disable: var(--ion-color-medium)}ion-menu ion-header ion-text{color:var(--ion-color-primary)}ion-menu ion-header .user-avatar{background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);overflow:hidden!important;font-size:var(--avatar-size, 60px)!important;line-height:var(--avatar-size, 60px);height:var(--avatar-size, 60px)!important;width:var(--avatar-size, 60px)!important;border-radius:50%;display:inline-block}ion-menu ion-header .user-avatar:hover{background-color:var(--ion-color-secondary-shade);border:solid 2px var(--ion-color-secondary-shade)}ion-menu ion-header .user-logo{text-align:right}ion-menu ion-header .user-logo img{max-width:120px;max-height:var(--avatar-size, 60px);width:auto}ion-menu ion-header .username{padding-top:0;margin-top:0;margin-bottom:0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}ion-menu ion-header button[mat-button]{padding:0;text-align:start!important;width:100%}ion-menu .scroll-content{margin-top:79px!important}ion-menu .has-user-header .scroll-content{margin-top:188px!important}ion-menu ion-content.has-profile-header{--offset-top: 188px}ion-menu ion-content.no-profile-header{--offset-top: 79px}ion-menu ion-content ion-list{min-height:100%;display:flex;flex-direction:column;justify-content:flex-start}ion-menu ion-content ion-list ion-menu-toggle.flex-spacer{flex:1 1 auto;display:flex;flex-direction:column;justify-content:flex-end}ion-menu ion-content ion-list ion-item.primary{--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint)}ion-menu ion-content ion-list ion-item.secondary{--ion-item-icon-color: var(--ion-color-secondary-tint);--ion-item-text-color: var(--ion-color-secondary-tint)}ion-menu ion-content ion-list ion-item.tertiary{--ion-item-icon-color: var(--ion-color-tertiary-tint);--ion-item-text-color: var(--ion-color-tertiary-tint)}ion-menu ion-content ion-list ion-item.danger{--ion-item-icon-color: var(--ion-color-danger-tint);--ion-item-text-color: var(--ion-color-danger-tint)}ion-menu ion-content ion-list ion-item.medium{--ion-item-icon-color: var(--ion-color-medium-shade);--ion-item-text-color: var(--ion-color-medium-shade)}ion-menu ion-content ion-list ion-item.dark{--ion-item-icon-color: var(--ion-color-dark-tint);--ion-item-text-color: var(--ion-color-dark-tint)}ion-menu ion-content ion-list ion-item mat-icon,ion-menu ion-content ion-list ion-item ion-icon{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ion-text,ion-menu ion-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-menu ion-content ion-list ion-item.selected{background-color:var(--ion-item-background-selected)!important;--color-hover: var(--ion-item-background-selected) !important}ion-menu ion-content ion-list ion-item.selected mat-icon,ion-menu ion-content ion-list ion-item.selected ion-icon{color:var(--ion-item-icon-color-selected)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item.selected ion-text,ion-menu ion-content ion-list ion-item.selected ion-label{color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list ion-item.selected:hover{--color-hover: var(--ion-item-background-selected) !important;--ion-item-icon-color-selected: var(--ion-item-icon-color) !important;--ion-item-text-color-selected: var(--ion-item-text-color) !important}ion-menu ion-footer{display:block!important}ion-menu ion-footer ion-toolbar ion-title{cursor:pointer;font-size:12pt;font-weight:400;text-align:center;padding:0 8px}ion-menu ion-footer ion-toolbar ion-button{--width: 40px}@media screen and (max-width: 767px){ion-menu ion-footer{display:none!important;visibility:hidden!important}}@media screen and (min-width: 768px){ion-menu ion-scroll{overflow-y:auto!important}ion-menu .user-avatar{font-size:var(--avatar-size, 80px)!important;line-height:var(--avatar-size, 80px);height:var(--avatar-size, 80px)!important;width:var(--avatar-size, 80px)!important}ion-menu .user-logo img{max-height:var(--avatar-size, 80px)}}\n"], dependencies: [{ kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i1.IonItemDivider, selector: "ion-item-divider", inputs: ["color", "mode", "sticky"] }, { kind: "component", type: i1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i1.IonMenu, selector: "ion-menu", inputs: ["contentId", "disabled", "maxEdgeStart", "menuId", "side", "swipeGesture", "type"] }, { kind: "component", type: i1.IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { kind: "component", type: i1.IonRow, selector: "ion-row" }, { kind: "component", type: i1.IonSplitPane, selector: "ion-split-pane", inputs: ["contentId", "disabled", "when"] }, { kind: "component", type: i1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i1.RouterLinkDelegate, selector: ":not(a):not(area)[routerLink]", inputs: ["routerDirection", "routerAnimation"] }, { kind: "directive", type: i1$2.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i8$1.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "directive", type: i1$6.RouterLink, selector: ":not(a):not(area)[routerLink]", inputs: ["queryParams", "fragment", "queryParamsHandling", "state", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1$6.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }], animations: [fadeInAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
23847
+ MenuComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: MenuComponent, selector: "app-menu", inputs: { id: "id", menuId: "menuId", side: "side", contentId: "contentId", logo: "logo", appName: "appName", appVersion: "appVersion" }, viewQueries: [{ propertyName: "splitPane", first: true, predicate: ["splitPane"], descendants: true, static: true }], ngImport: i0, template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" [contentId]=\"contentId\">\n <ion-header>\n\n <ion-toolbar @fadeInAnimation *ngIf=\"isLogin; else notLogin\" class=\"ion-toolbar-top\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button type=\"button\" mat-flat-button\n class=\"user-avatar\" [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url('+(accountAvatar||'./assets/img/person.png')+')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"'MENU.BTN_MY_ACCOUNT'|translate\">\n </button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"108px\"/>\n <span *ngIf=\"!logo\" style=\"width: 108px;\">{{appName}}</span>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n <ion-col>\n <button mat-button type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"'MENU.BTN_MY_ACCOUNT'|translate\">\n <ion-label color=\"primary\" class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{accountName}}</b>\n </h3>\n <h4>{{accountEmail}}</h4>\n </ion-label>\n </button>\n </ion-col>\n\n <!-- Insertion headerBottomRight -->\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"headerBottomRight\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar class=\"ion-padding\" @fadeInAnimation\n style=\"height: unset; display: block; margin: auto; text-align: center;\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"150px;\">\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{appName}}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n\n <ion-list lines=\"none\">\n <ion-menu-toggle auto-hide=\"false\"\n [class.flex-spacer]=\"item.cssClass == 'flex-spacer'\"\n *ngFor=\"let item of $items | async\">\n\n <!-- link -->\n <ion-item *ngIf=\"!loading && item.path\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n [routerLink]=\"item.path\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{exact: (item.path === '/')}\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- action -->\n <ion-item *ngIf=\"!loading && item.action\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n (click)=\"doAction(item.action, $event)\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- divider -->\n <ion-item-divider @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n *ngIf=\"!loading && !item.path && !item.action\">\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item-divider>\n\n </ion-menu-toggle>\n </ion-list>\n </ion-content>\n\n <ion-footer class=\"hidden-xs hidden-sm\">\n <ion-toolbar>\n\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"openAboutModal($event)\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"openAboutModal($event)\" color=\"medium\">\n {{'MENU.FOOTER_VERSION_ABOUT'| translate: {version: appVersion} }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button mat-icon-button color=\"accent\" (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') |translate\">\n <mat-icon><span>{{splitPane.when ? '&#xab;' : '&#xbb;'}}</span></mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n\n </ion-menu>\n\n <ng-content></ng-content>\n\n</ion-split-pane>\n\n<ng-template #headerBottomRight>\n <ng-content select=\"[headerBottomRight]\"></ng-content>\n</ng-template>\n", styles: ["ion-menu{--ion-item-background: transparent;--ion-item-divider-background: transparent;--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint);--ion-item-background-selected: var(--ion-color-secondary100);--ion-item-text-color-selected: var(--ion-color-primary);--ion-item-icon-color-selected: var(--ion-color-primary);--ion-item-text-color-disable: var(--ion-color-medium);--ion-item-icon-color-disable: var(--ion-color-medium)}ion-menu ion-header ion-text{color:var(--ion-color-primary)}ion-menu ion-header .user-avatar{background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);overflow:hidden!important;font-size:var(--avatar-size, 60px)!important;line-height:var(--avatar-size, 60px);height:var(--avatar-size, 60px)!important;width:var(--avatar-size, 60px)!important;border-radius:50%;display:inline-block}ion-menu ion-header .user-avatar:hover{background-color:var(--ion-color-secondary-shade);border:solid 2px var(--ion-color-secondary-shade)}ion-menu ion-header .user-logo{text-align:right}ion-menu ion-header .user-logo img{max-width:120px;max-height:var(--avatar-size, 60px);width:auto}ion-menu ion-header .username{padding-top:0;margin-top:0;margin-bottom:0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}ion-menu ion-header button[mat-button]{padding:0;text-align:start!important;width:100%}ion-menu .scroll-content{margin-top:79px!important}ion-menu .has-user-header .scroll-content{margin-top:188px!important}ion-menu ion-content.has-profile-header{--offset-top: 188px}ion-menu ion-content.no-profile-header{--offset-top: 79px}ion-menu ion-content ion-list{min-height:100%;display:flex;flex-direction:column;justify-content:flex-start}ion-menu ion-content ion-list ion-menu-toggle.flex-spacer{flex:1 1 auto;display:flex;flex-direction:column;justify-content:flex-end}ion-menu ion-content ion-list ion-item.primary{--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint)}ion-menu ion-content ion-list ion-item.secondary{--ion-item-icon-color: var(--ion-color-secondary-tint);--ion-item-text-color: var(--ion-color-secondary-tint)}ion-menu ion-content ion-list ion-item.tertiary{--ion-item-icon-color: var(--ion-color-tertiary-tint);--ion-item-text-color: var(--ion-color-tertiary-tint)}ion-menu ion-content ion-list ion-item.danger{--ion-item-icon-color: var(--ion-color-danger-tint);--ion-item-text-color: var(--ion-color-danger-tint)}ion-menu ion-content ion-list ion-item.medium{--ion-item-icon-color: var(--ion-color-medium-shade);--ion-item-text-color: var(--ion-color-medium-shade)}ion-menu ion-content ion-list ion-item.dark{--ion-item-icon-color: var(--ion-color-dark-tint);--ion-item-text-color: var(--ion-color-dark-tint)}ion-menu ion-content ion-list ion-item mat-icon,ion-menu ion-content ion-list ion-item ion-icon{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ion-text,ion-menu ion-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-menu ion-content ion-list ion-item.selected{background-color:var(--ion-item-background-selected)!important;--color-hover: var(--ion-item-background-selected) !important}ion-menu ion-content ion-list ion-item.selected mat-icon,ion-menu ion-content ion-list ion-item.selected ion-icon{color:var(--ion-item-icon-color-selected)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item.selected ion-text,ion-menu ion-content ion-list ion-item.selected ion-label{color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list ion-item.selected:hover{--color-hover: var(--ion-item-background-selected) !important;--ion-item-icon-color-selected: var(--ion-item-icon-color) !important;--ion-item-text-color-selected: var(--ion-item-text-color) !important}ion-menu ion-footer{display:block!important}ion-menu ion-footer ion-toolbar ion-title{cursor:pointer;font-size:12pt;font-weight:400;text-align:center;padding:0 8px}ion-menu ion-footer ion-toolbar ion-button{--width: 40px}@media screen and (max-width: 767px){ion-menu ion-footer{display:none!important;visibility:hidden!important}}@media screen and (min-width: 768px){ion-menu ion-scroll{overflow-y:auto!important}ion-menu .user-avatar{font-size:var(--avatar-size, 80px)!important;line-height:var(--avatar-size, 80px);height:var(--avatar-size, 80px)!important;width:var(--avatar-size, 80px)!important}ion-menu .user-logo img{max-height:var(--avatar-size, 80px)}}\n"], dependencies: [{ kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i1.IonItemDivider, selector: "ion-item-divider", inputs: ["color", "mode", "sticky"] }, { kind: "component", type: i1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i1.IonMenu, selector: "ion-menu", inputs: ["contentId", "disabled", "maxEdgeStart", "menuId", "side", "swipeGesture", "type"] }, { kind: "component", type: i1.IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { kind: "component", type: i1.IonRow, selector: "ion-row" }, { kind: "component", type: i1.IonSplitPane, selector: "ion-split-pane", inputs: ["contentId", "disabled", "when"] }, { kind: "component", type: i1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i1.RouterLinkDelegate, selector: ":not(a):not(area)[routerLink]", inputs: ["routerDirection", "routerAnimation"] }, { kind: "directive", type: i1$2.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i8$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "directive", type: i1$6.RouterLink, selector: ":not(a):not(area)[routerLink]", inputs: ["queryParams", "fragment", "queryParamsHandling", "state", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1$6.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }], animations: [fadeInAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
23663
23848
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MenuComponent, decorators: [{
23664
23849
  type: Component,
23665
23850
  args: [{ selector: 'app-menu', animations: [fadeInAnimation], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-split-pane #splitPane [contentId]=\"contentId\" (swiperight)=\"onSwipeRight($event)\">\n\n <ion-menu [id]=\"id\" [menuId]=\"menuId\" [contentId]=\"contentId\">\n <ion-header>\n\n <ion-toolbar @fadeInAnimation *ngIf=\"isLogin; else notLogin\" class=\"ion-toolbar-top\">\n <ion-grid>\n <ion-row>\n <ion-col size=\"4\">\n <button type=\"button\" mat-flat-button\n class=\"user-avatar\" [class.primary]=\"!accountAvatar\"\n [style.background-image]=\"'url('+(accountAvatar||'./assets/img/person.png')+')'\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"'MENU.BTN_MY_ACCOUNT'|translate\">\n </button>\n </ion-col>\n <ion-col size=\"8\" class=\"user-logo\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"108px\"/>\n <span *ngIf=\"!logo\" style=\"width: 108px;\">{{appName}}</span>\n </ion-col>\n </ion-row>\n <ion-row class=\"ion-no-padding\">\n <ion-col>\n <button mat-button type=\"button\"\n [routerLink]=\"['/account']\"\n routerDirection=\"root\"\n routerLinkActive=\"ion-color-primary\"\n (click)=\"close()\"\n [title]=\"'MENU.BTN_MY_ACCOUNT'|translate\">\n <ion-label color=\"primary\" class=\"ion-text-wrap ion-text-start\">\n <h3 class=\"no-margin username\">\n <b>{{accountName}}</b>\n </h3>\n <h4>{{accountEmail}}</h4>\n </ion-label>\n </button>\n </ion-col>\n\n <!-- Insertion headerBottomRight -->\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"headerBottomRight\"></ng-container>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-toolbar>\n\n <!-- User not logged -->\n <ng-template #notLogin>\n <mat-toolbar class=\"ion-padding\" @fadeInAnimation\n style=\"height: unset; display: block; margin: auto; text-align: center;\">\n <img *ngIf=\"logo\" src=\"{{logo}}\" [title]=\"'APP_NAME'|translate: {appName: appName}\" width=\"150px;\">\n <span *ngIf=\"!logo\" style=\"width: 150px\">{{appName}}</span>\n </mat-toolbar>\n </ng-template>\n </ion-header>\n\n <ion-content [class.has-user-header]=\"isLogin\">\n\n <ion-list lines=\"none\">\n <ion-menu-toggle auto-hide=\"false\"\n [class.flex-spacer]=\"item.cssClass == 'flex-spacer'\"\n *ngFor=\"let item of $items | async\">\n\n <!-- link -->\n <ion-item *ngIf=\"!loading && item.path\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n [routerLink]=\"item.path\"\n routerDirection=\"root\"\n routerLinkActive=\"selected\"\n [routerLinkActiveOptions]=\"{exact: (item.path === '/')}\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- action -->\n <ion-item *ngIf=\"!loading && item.action\"\n @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}} text-1x\"\n tappable\n (click)=\"doAction(item.action, $event)\">\n <ion-icon slot=\"start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item>\n\n <!-- divider -->\n <ion-item-divider @fadeInAnimation\n class=\"{{item.cssClass}} {{item.color}}\"\n *ngIf=\"!loading && !item.path && !item.action\">\n <ion-label translate>{{item.title}}</ion-label>\n </ion-item-divider>\n\n </ion-menu-toggle>\n </ion-list>\n </ion-content>\n\n <ion-footer class=\"hidden-xs hidden-sm\">\n <ion-toolbar>\n\n <ion-buttons slot=\"start\">\n <ion-button mat-icon-button color=\"accent\" (click)=\"openAboutModal($event)\">\n <mat-icon slot=\"icon-only\">help_outline</mat-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title (click)=\"openAboutModal($event)\" color=\"medium\">\n {{'MENU.FOOTER_VERSION_ABOUT'| translate: {version: appVersion} }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <button mat-icon-button color=\"accent\" (click)=\"toggleSplitPaneShow($event)\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(splitPane.when ? 'COMMON.BTN_HIDE_MENU' : 'COMMON.BTN_SHOW_MENU') |translate\">\n <mat-icon><span>{{splitPane.when ? '&#xab;' : '&#xbb;'}}</span></mat-icon>\n </button>\n </ion-buttons>\n </ion-toolbar>\n </ion-footer>\n\n </ion-menu>\n\n <ng-content></ng-content>\n\n</ion-split-pane>\n\n<ng-template #headerBottomRight>\n <ng-content select=\"[headerBottomRight]\"></ng-content>\n</ng-template>\n", styles: ["ion-menu{--ion-item-background: transparent;--ion-item-divider-background: transparent;--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint);--ion-item-background-selected: var(--ion-color-secondary100);--ion-item-text-color-selected: var(--ion-color-primary);--ion-item-icon-color-selected: var(--ion-color-primary);--ion-item-text-color-disable: var(--ion-color-medium);--ion-item-icon-color-disable: var(--ion-color-medium)}ion-menu ion-header ion-text{color:var(--ion-color-primary)}ion-menu ion-header .user-avatar{background-size:cover;background-repeat:no-repeat;background-position:center;background-color:var(--ion-color-secondary);border:solid 1px rgba(var(--ion-color-secondary-rgb),.5);overflow:hidden!important;font-size:var(--avatar-size, 60px)!important;line-height:var(--avatar-size, 60px);height:var(--avatar-size, 60px)!important;width:var(--avatar-size, 60px)!important;border-radius:50%;display:inline-block}ion-menu ion-header .user-avatar:hover{background-color:var(--ion-color-secondary-shade);border:solid 2px var(--ion-color-secondary-shade)}ion-menu ion-header .user-logo{text-align:right}ion-menu ion-header .user-logo img{max-width:120px;max-height:var(--avatar-size, 60px);width:auto}ion-menu ion-header .username{padding-top:0;margin-top:0;margin-bottom:0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}ion-menu ion-header button[mat-button]{padding:0;text-align:start!important;width:100%}ion-menu .scroll-content{margin-top:79px!important}ion-menu .has-user-header .scroll-content{margin-top:188px!important}ion-menu ion-content.has-profile-header{--offset-top: 188px}ion-menu ion-content.no-profile-header{--offset-top: 79px}ion-menu ion-content ion-list{min-height:100%;display:flex;flex-direction:column;justify-content:flex-start}ion-menu ion-content ion-list ion-menu-toggle.flex-spacer{flex:1 1 auto;display:flex;flex-direction:column;justify-content:flex-end}ion-menu ion-content ion-list ion-item.primary{--ion-item-icon-color: var(--ion-color-primary-tint);--ion-item-text-color: var(--ion-color-primary-tint)}ion-menu ion-content ion-list ion-item.secondary{--ion-item-icon-color: var(--ion-color-secondary-tint);--ion-item-text-color: var(--ion-color-secondary-tint)}ion-menu ion-content ion-list ion-item.tertiary{--ion-item-icon-color: var(--ion-color-tertiary-tint);--ion-item-text-color: var(--ion-color-tertiary-tint)}ion-menu ion-content ion-list ion-item.danger{--ion-item-icon-color: var(--ion-color-danger-tint);--ion-item-text-color: var(--ion-color-danger-tint)}ion-menu ion-content ion-list ion-item.medium{--ion-item-icon-color: var(--ion-color-medium-shade);--ion-item-text-color: var(--ion-color-medium-shade)}ion-menu ion-content ion-list ion-item.dark{--ion-item-icon-color: var(--ion-color-dark-tint);--ion-item-text-color: var(--ion-color-dark-tint)}ion-menu ion-content ion-list ion-item mat-icon,ion-menu ion-content ion-list ion-item ion-icon{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item ion-text,ion-menu ion-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-menu ion-content ion-list ion-item.selected{background-color:var(--ion-item-background-selected)!important;--color-hover: var(--ion-item-background-selected) !important}ion-menu ion-content ion-list ion-item.selected mat-icon,ion-menu ion-content ion-list ion-item.selected ion-icon{color:var(--ion-item-icon-color-selected)!important;fill:currentColor;stroke:currentColor}ion-menu ion-content ion-list ion-item.selected ion-text,ion-menu ion-content ion-list ion-item.selected ion-label{color:var(--ion-item-text-color-selected)!important}ion-menu ion-content ion-list ion-item.selected:hover{--color-hover: var(--ion-item-background-selected) !important;--ion-item-icon-color-selected: var(--ion-item-icon-color) !important;--ion-item-text-color-selected: var(--ion-item-text-color) !important}ion-menu ion-footer{display:block!important}ion-menu ion-footer ion-toolbar ion-title{cursor:pointer;font-size:12pt;font-weight:400;text-align:center;padding:0 8px}ion-menu ion-footer ion-toolbar ion-button{--width: 40px}@media screen and (max-width: 767px){ion-menu ion-footer{display:none!important;visibility:hidden!important}}@media screen and (min-width: 768px){ion-menu ion-scroll{overflow-y:auto!important}ion-menu .user-avatar{font-size:var(--avatar-size, 80px)!important;line-height:var(--avatar-size, 80px);height:var(--avatar-size, 80px)!important;width:var(--avatar-size, 80px)!important}ion-menu .user-logo img{max-height:var(--avatar-size, 80px)}}\n"] }]
@@ -24693,10 +24878,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImpor
24693
24878
  args: ['form', { static: true }]
24694
24879
  }] } });
24695
24880
 
24696
- function getRandomImage(files) {
24881
+ const getRandomImage = (files) => {
24697
24882
  const imgIndex = Math.floor(Math.random() * files.length);
24698
24883
  return files[imgIndex];
24699
- }
24884
+ };
24700
24885
  const APP_HOME_BUTTONS = new InjectionToken('homeButton');
24701
24886
  class HomePage {
24702
24887
  constructor(accountService, modalCtrl, translate, toastController, configService, platform, cd, network, settings, environment, locales, buttons) {
@@ -24832,8 +25017,9 @@ class HomePage {
24832
25017
  && (!this.offline || !img.startsWith('http')));
24833
25018
  // Background image found: select one randomly
24834
25019
  if (backgroundImages.length) {
24835
- const bgImage = getRandomImage(backgroundImages);
24836
- this.contentStyle = bgImage && { 'background-image': `url(${bgImage})` } || {};
25020
+ const bgImage = getRandomImage(backgroundImages)?.split('|');
25021
+ this.contentStyle = bgImage?.[0] && { 'background-image': `url(${bgImage?.[0]})` } || {};
25022
+ this.contentCredits = bgImage?.[1];
24837
25023
  }
24838
25024
  // Use background color
24839
25025
  else {
@@ -24902,10 +25088,10 @@ class HomePage {
24902
25088
  }
24903
25089
  }
24904
25090
  HomePage.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: HomePage, deps: [{ token: AccountService }, { token: i1.ModalController }, { token: i1$2.TranslateService }, { token: i1.ToastController }, { token: ConfigService }, { token: PlatformService }, { token: i0.ChangeDetectorRef }, { token: NetworkService }, { token: LocalSettingsService }, { token: ENVIRONMENT }, { token: APP_LOCALES }, { token: APP_HOME_BUTTONS, optional: true }], target: i0.ɵɵFactoryTarget.Component });
24905
- HomePage.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: HomePage, selector: "app-page-home", ngImport: i0, template: "<app-toolbar [canGoBack]=\"false\"\n visible-xs visible-sm visible-mobile>\n\n <!-- change locale button -->\n <ion-button slot=\"end\" *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\">&nbsp;{{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n</app-toolbar>\n\n<!-- change locale menu -->\n<mat-menu #localeMenu=\"matMenu\">\n <button *ngFor=\"let item of locales\"\n mat-menu-item\n (click)=\"changeLanguage(item.key)\">\n <ion-label>{{item.value}}</ion-label>\n </button>\n</mat-menu>\n\n<ion-content [ngStyle]=\"contentStyle\" no-padding-xs>\n\n <!-- loading spinner -->\n <div class=\"loading-page\" [class.cdk-visually-hidden]=\"!loading\">\n <div class=\"spinner\" *ngIf=\"loading && showSpinner\">\n <p [innerHTML]=\"'COMMON.LOADING_DOTS'|translate\"></p>\n <div class=\"sk-cube1 sk-cube\"></div>\n <div class=\"sk-cube2 sk-cube\"></div>\n <div class=\"sk-cube4 sk-cube\"></div>\n <div class=\"sk-cube3 sk-cube\"></div>\n </div>\n </div>\n\n <!-- Desktop: translucent top toolbar -->\n <div hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar translucent>\n <ion-buttons slot=\"start\">\n <ion-menu-toggle>\n <ion-button color=\"light\" fill=\"clear\">\n <ion-icon slot=\"icon-only\" name=\"menu\"></ion-icon>\n </ion-button>\n </ion-menu-toggle>\n </ion-buttons>\n\n <ion-buttons slot=\"end\">\n <!-- change locale button -->\n <ion-button color=\"secondary\" fill=\"solid\"\n *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\">&nbsp;{{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </div>\n\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\"\n [showInstallButton]=\"true\">\n </app-install-upgrade-card>\n\n <!-- Welcome card -->\n <ion-card *ngIf=\"!loading\"\n class=\"main welcome ion-padding ion-text-center ion-align-self-center\"\n @fadeInAnimation>\n <ion-card-header>\n <ion-card-title class=\"ion-text-center\">\n <span *ngIf=\"isWeb\" [innerHTML]=\"'HOME.WELCOME_WEB'|translate: {appName: appName}\"></span>\n <span *ngIf=\"!isWeb\" [innerHTML]=\"'HOME.WELCOME_APP'|translate: {appName: appName}\"></span>\n </ion-card-title>\n <ion-card-subtitle [innerHTML]=\"description\">\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content class=\"ion-no-padding\">\n <ion-text color=\"primary\">\n <img class=\"logo\" *ngIf=\"logo\" src=\"{{ logo }}\">\n </ion-text>\n <!-- register help text -->\n <ion-text *ngIf=\"!isLogin && canRegister\">\n <br/>\n <span translate>HOME.REGISTER_HELP</span>\n </ion-text>\n <div class=\"ion-padding-top\">\n\n <!-- If NOT login -->\n <ng-container *ngIf=\"!isLogin; else loginButtons;\">\n\n <ion-button *ngIf=\"canRegister\" expand=\"block\" color=\"tertiary\" (click)=\"register()\">\n <span translate>HOME.BTN_REGISTER</span>\n </ion-button>\n <ion-button expand=\"block\" color=\"light\" [routerLink]=\"['/']\" (click)=\"login()\">\n <span translate>AUTH.BTN_LOGIN</span>\n </ion-button>\n\n </ng-container>\n\n <!-- If user login -->\n <ng-template #loginButtons>\n\n <!-- Feature buttons -->\n <ng-container *ngIf=\"$filteredButtons | async as buttons\">\n <ng-container *ngFor=\"let item of buttons\">\n <ion-button *ngIf=\"item.path\"\n expand=\"block\" color=\"tertiary\"\n [class]=\"item.cssClass\"\n [routerLink]=\"item.path\"\n routerDirection=\"root\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-text>{{'HOME.BTN_DATA_ENTRY'|translate: {name: (item.title | translate) } }}</ion-text>\n </ion-button>\n\n <!-- divider -->\n <div *ngIf=\"!item.path && !item.action\" [class]=\"item.cssClass\">\n <ion-label translate>{{item.title}}&nbsp;</ion-label>\n </div>\n </ng-container>\n\n <!--<p *ngIf=\"buttons.length\" class=\"visible-mobile\">&nbsp;</p>-->\n </ng-container>\n\n <ion-button *ngIf=\"showAccountButton\" expand=\"block\" color=\"secondary\" [routerLink]=\"['/account']\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" name=\"person-circle\"></ion-icon>\n <ion-text translate>HOME.BTN_MY_ACCOUNT</ion-text>\n </ion-button>\n\n <p hidden-xs hidden-sm hidden-mobile>\n <ion-text [innerHTML]=\"'HOME.NOT_THIS_ACCOUNT_QUESTION' | translate: {displayName: accountName }\"></ion-text>\n <br/>\n <ion-text>\n <a href=\"#\" (click)=\"logout($event)\">\n <span translate>HOME.BTN_DISCONNECT</span>\n </a>\n </ion-text>\n </p>\n\n\n </ng-template>\n\n </div>\n </ion-card-content>\n </ion-card>\n\n <!-- Page history -->\n <ion-grid *ngIf=\"!loading && isLogin && pageHistory.length; else bottomBanner\"\n class=\"history-container ion-align-self-center\">\n <ion-row>\n <ion-col size=\"12\" size-xl=\"\" *ngFor=\"let page of pageHistory; trackBy: getPagePath\"\n class=\"ion-text-center\">\n <ion-card class=\"ion-align-self-start ion-text-start\" @fadeInAnimation>\n <ion-card-header class=\"ion-no-padding\">\n <!-- top bar -->\n <ion-card-subtitle>\n <button type=\"button\" tabindex=\"-1\"\n (click)=\"settings.removePageHistory(page.path)\"\n mat-icon-button class=\"ion-float-start ion-no-margin\">\n <mat-icon>close</mat-icon>\n </button>\n <ion-label [innerHTML]=\"page.subtitle|translate\"></ion-label>\n <ion-text class=\"ion-float-end\" [title]=\"page.time|dateFormat:{time: true}\">\n <small><ion-icon name=\"time-outline\"></ion-icon>&nbsp;{{ page.time|dateFromNow }}</small>&nbsp;\n </ion-text>\n </ion-card-subtitle>\n\n <!-- main page -->\n <ion-card-title class=\"ion-no-margin ion-no-padding\">\n <ion-item detail=\"true\"\n tappable class=\"text-1x\"\n [routerLink]=\"page.path\"\n routerDirection=\"root\" lines=\"none\">\n <!-- page icon-->\n <ion-icon *ngIf=\"page.icon\" slot=\"start\" [name]=\"page.icon\"></ion-icon>\n <mat-icon *ngIf=\"page.matIcon\" slot=\"start\">{{page.matIcon}}</mat-icon>\n\n <ion-label color=\"primary\" [innerHTML]=\"page.title\"></ion-label>\n </ion-item>\n </ion-card-title>\n </ion-card-header>\n\n <!-- children pages -->\n <ion-card-content class=\"ion-no-padding ion-padding-start\" *ngIf=\"page.children?.length\">\n <ion-list class=\"ion-no-padding\">\n <ion-item detail=\"true\"\n *ngFor=\"let childPage of page.children\"\n tappable class=\"text-1x\"\n [routerLink]=\"childPage.path\"\n routerDirection=\"root\">\n <!-- page icon-->\n <ion-icon *ngIf=\"childPage.icon\" slot=\"start\" color=\"dark\" [name]=\"childPage.icon\"></ion-icon>\n <mat-icon *ngIf=\"childPage.matIcon\" slot=\"start\" color=\"dark\">{{childPage.matIcon}}</mat-icon>\n\n <ion-label color=\"dark\" [innerHTML]=\"childPage.title\"></ion-label>\n </ion-item>\n </ion-list>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Display configured Departments logos -->\n <ng-template #bottomBanner>\n <div class=\"bottom-banner ion-text-center ion-padding\" *ngIf=\"!loadingBanner\" @fadeInAnimation>\n <a href=\"{{ item.siteUrl }}\" *ngFor=\"let item of $partners | async \">\n <img class=\"logo\" src=\"{{ item.logo }}\" alt=\"{{item.label}}\" [title]=\"item.label\" />\n </a>\n </div>\n </ng-template>\n\n\n</ion-content>\n", styles: ["h1,h2,h3,h4,h5{white-space:normal}.center,ion-content ion-card.welcome img{text-align:center;display:inline-block}ion-content{--background: transparent;background-size:cover;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;display:inline-block;padding:15px;background-color:var(--ion-color-primary)}ion-content .loading-page{display:block;height:100%;width:100%;position:absolute;background-color:var(--ion-color-primary);z-index:5;transition:background .5s linear;-webkit-transition:background 1.5s linear}ion-content .loading-page.hidden{background-color:rgba(var(--ion-color-primary),0)}ion-content ion-card{z-index:9}ion-content ion-card ion-card-header{display:block}ion-content ion-card ion-card-header ion-card-subtitle,ion-content ion-card ion-card-header ion-card-title{display:block;width:100%}ion-content ion-card.main{min-width:240px;max-width:400px;margin-left:auto;margin-right:auto}ion-content ion-card.welcome{background-color:#ffffffb3}ion-content ion-card.welcome button{margin-top:16px}ion-content ion-card.welcome img{max-width:250px}ion-content .history-container{margin-left:auto;margin-right:auto}ion-content .history-container ion-card{display:inline-block;box-sizing:border-box;z-index:8;min-width:240px;max-width:400px;width:100%;margin-left:auto;margin-right:auto;background-color:#ffffffb3}ion-content .history-container ion-card ion-card-header ion-card-subtitle{height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle button[float-start]{z-index:99;margin:0}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label{line-height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label[float-end]{padding-right:16px;font-weight:400}ion-content .history-container ion-card ion-card-header ion-card-title ion-item{--ion-item-background: $transparent;--ion-item-icon-color: var(--ion-color-primary);--ion-item-text-color: var(--ion-color-primary)}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-header ion-card-title ion-item mat-icon[slot=start]{margin-right:unset;margin-inline-end:16px!important;color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-text,ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-label{color:var(--ion-item-text-color)!important;-webkit-text-size-adjust:100%;text-size-adjust:100%}ion-content .history-container ion-card ion-card-content ion-list{--ion-item-background: var(--ion-color-transparent)}ion-content .history-container ion-card ion-card-content ion-list ion-item{--ion-item-icon-color: var(--ion-color-dark);--ion-item-text-color: var(--ion-color-dark)}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-content ion-list ion-item mat-icon[slot=start]{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor;margin-right:unset;margin-inline-end:16px!important}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-text,ion-content .history-container ion-card ion-card-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-content .bottom-banner{background-color:#ffffffb3;min-width:240px;z-index:0;position:absolute;left:16px;right:16px;bottom:16px}ion-content .bottom-banner img.logo{max-height:50px;margin-left:8px}ion-content ion-text{text-align:center}@media screen and (max-width: 575px){ion-content ion-card{min-width:160px;width:calc(100% - 20px);max-width:100%;margin-left:10px;margin-right:10px}ion-content ion-card h1{margin-top:0;font-size:20px}ion-content ion-card ion-card-content ion-button{margin-top:8px}ion-content .bottom-banner{display:block;position:unset}ion-content .bottom-banner img.logo{max-height:30px}}@media screen and (max-width: 767px) and (min-width: 576px){ion-content{min-height:555px}ion-content .bottom-banner img.logo{max-height:40px}}@media screen and (min-width: 768px){ion-content{min-height:calc(100% - 100px)}}\n"], dependencies: [{ kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i1.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i1.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i1.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i1.IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: i1.IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: i1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i1.IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { kind: "component", type: i1.IonRow, selector: "ion-row" }, { kind: "component", type: i1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i1.RouterLinkDelegate, selector: ":not(a):not(area)[routerLink]", inputs: ["routerDirection", "routerAnimation"] }, { kind: "directive", type: i1$2.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i11$2.MatMenu, selector: "mat-menu", exportAs: ["matMenu"] }, { kind: "component", type: i11$2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["disabled", "disableRipple", "role"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i11$2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", exportAs: ["matMenuTrigger"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["progressBarMode", "title", "color", "class", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "onSearch"] }, { kind: "directive", type: i1$6.RouterLink, selector: ":not(a):not(area)[routerLink]", inputs: ["queryParams", "fragment", "queryParamsHandling", "state", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: AppInstallUpgradeCard, selector: "app-install-upgrade-card", inputs: ["isLogin", "showUpgradeWarning", "showOfflineWarning", "showInstallButton"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }, { kind: "pipe", type: DateFormatPipe, name: "dateFormat" }, { kind: "pipe", type: DateFromNowPipe, name: "dateFromNow" }], animations: [fadeInAnimation, slideUpDownAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
25091
+ HomePage.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: HomePage, selector: "app-page-home", ngImport: i0, template: "<app-toolbar [canGoBack]=\"false\"\n visible-xs visible-sm visible-mobile>\n\n <!-- change locale button -->\n <ion-button slot=\"end\" *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\">&nbsp;{{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n</app-toolbar>\n\n<!-- change locale menu -->\n<mat-menu #localeMenu=\"matMenu\">\n <button *ngFor=\"let item of locales\"\n mat-menu-item\n (click)=\"changeLanguage(item.key)\">\n <ion-label>{{item.value}}</ion-label>\n </button>\n</mat-menu>\n\n<ion-content [ngStyle]=\"contentStyle\" no-padding-xs>\n\n <span class=\"content-credits\"><ion-text>{{ contentCredits }}</ion-text></span>\n\n <!-- loading spinner -->\n <div class=\"loading-page\" [class.cdk-visually-hidden]=\"!loading\">\n <div class=\"spinner\" *ngIf=\"loading && showSpinner\">\n <p [innerHTML]=\"'COMMON.LOADING_DOTS'|translate\"></p>\n <div class=\"sk-cube1 sk-cube\"></div>\n <div class=\"sk-cube2 sk-cube\"></div>\n <div class=\"sk-cube4 sk-cube\"></div>\n <div class=\"sk-cube3 sk-cube\"></div>\n </div>\n </div>\n\n <!-- Desktop: translucent top toolbar -->\n <div hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar translucent>\n <ion-buttons slot=\"start\">\n <ion-menu-toggle>\n <ion-button color=\"light\" fill=\"clear\">\n <ion-icon slot=\"icon-only\" name=\"menu\"></ion-icon>\n </ion-button>\n </ion-menu-toggle>\n </ion-buttons>\n\n <ion-buttons slot=\"end\">\n <!-- change locale button -->\n <ion-button color=\"secondary\" fill=\"solid\"\n *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\">&nbsp;{{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </div>\n\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\"\n [showInstallButton]=\"true\">\n </app-install-upgrade-card>\n\n <!-- Welcome card -->\n <ion-card *ngIf=\"!loading\"\n class=\"main welcome ion-padding ion-text-center ion-align-self-center\"\n @fadeInAnimation>\n <ion-card-header>\n <ion-card-title class=\"ion-text-center\">\n <span *ngIf=\"isWeb\" [innerHTML]=\"'HOME.WELCOME_WEB'|translate: {appName: appName}\"></span>\n <span *ngIf=\"!isWeb\" [innerHTML]=\"'HOME.WELCOME_APP'|translate: {appName: appName}\"></span>\n </ion-card-title>\n <ion-card-subtitle [innerHTML]=\"description\">\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content class=\"ion-no-padding\">\n <ion-text color=\"primary\">\n <img class=\"logo\" *ngIf=\"logo\" src=\"{{ logo }}\">\n </ion-text>\n <!-- register help text -->\n <ion-text *ngIf=\"!isLogin && canRegister\">\n <br/>\n <span translate>HOME.REGISTER_HELP</span>\n </ion-text>\n <div class=\"ion-padding-top\">\n\n <!-- If NOT login -->\n <ng-container *ngIf=\"!isLogin; else loginButtons;\">\n\n <ion-button *ngIf=\"canRegister\" expand=\"block\" color=\"tertiary\" (click)=\"register()\">\n <span translate>HOME.BTN_REGISTER</span>\n </ion-button>\n <ion-button expand=\"block\" color=\"light\" [routerLink]=\"['/']\" (click)=\"login()\">\n <span translate>AUTH.BTN_LOGIN</span>\n </ion-button>\n\n </ng-container>\n\n <!-- If user login -->\n <ng-template #loginButtons>\n\n <!-- Feature buttons -->\n <ng-container *ngIf=\"$filteredButtons | async as buttons\">\n <ng-container *ngFor=\"let item of buttons\">\n <ion-button *ngIf=\"item.path\"\n expand=\"block\" color=\"tertiary\"\n [class]=\"item.cssClass\"\n [routerLink]=\"item.path\"\n routerDirection=\"root\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-text>{{'HOME.BTN_DATA_ENTRY'|translate: {name: (item.title | translate) } }}</ion-text>\n </ion-button>\n\n <!-- divider -->\n <div *ngIf=\"!item.path && !item.action\" [class]=\"item.cssClass\">\n <ion-label translate>{{item.title}}&nbsp;</ion-label>\n </div>\n </ng-container>\n\n <!--<p *ngIf=\"buttons.length\" class=\"visible-mobile\">&nbsp;</p>-->\n </ng-container>\n\n <ion-button *ngIf=\"showAccountButton\" expand=\"block\" color=\"secondary\" [routerLink]=\"['/account']\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" name=\"person-circle\"></ion-icon>\n <ion-text translate>HOME.BTN_MY_ACCOUNT</ion-text>\n </ion-button>\n\n <p hidden-xs hidden-sm hidden-mobile>\n <ion-text [innerHTML]=\"'HOME.NOT_THIS_ACCOUNT_QUESTION' | translate: {displayName: accountName }\"></ion-text>\n <br/>\n <ion-text>\n <a href=\"#\" (click)=\"logout($event)\">\n <span translate>HOME.BTN_DISCONNECT</span>\n </a>\n </ion-text>\n </p>\n\n\n </ng-template>\n\n </div>\n </ion-card-content>\n </ion-card>\n\n <!-- Page history -->\n <ion-grid *ngIf=\"!loading && isLogin && pageHistory.length; else bottomBanner\"\n class=\"history-container ion-align-self-center\">\n <ion-row>\n <ion-col size=\"12\" size-xl=\"\" *ngFor=\"let page of pageHistory; trackBy: getPagePath\"\n class=\"ion-text-center\">\n <ion-card class=\"ion-align-self-start ion-text-start\" @fadeInAnimation>\n <ion-card-header class=\"ion-no-padding\">\n <!-- top bar -->\n <ion-card-subtitle>\n <button type=\"button\" tabindex=\"-1\"\n (click)=\"settings.removePageHistory(page.path)\"\n mat-icon-button class=\"ion-float-start ion-no-margin\">\n <mat-icon>close</mat-icon>\n </button>\n <ion-label [innerHTML]=\"page.subtitle|translate\"></ion-label>\n <ion-text class=\"ion-float-end\" [title]=\"page.time|dateFormat:{time: true}\">\n <small><ion-icon name=\"time-outline\"></ion-icon>&nbsp;{{ page.time|dateFromNow }}</small>&nbsp;\n </ion-text>\n </ion-card-subtitle>\n\n <!-- main page -->\n <ion-card-title class=\"ion-no-margin ion-no-padding\">\n <ion-item detail=\"true\"\n tappable class=\"text-1x\"\n [routerLink]=\"page.path\"\n routerDirection=\"root\" lines=\"none\">\n <!-- page icon-->\n <ion-icon *ngIf=\"page.icon\" slot=\"start\" [name]=\"page.icon\"></ion-icon>\n <mat-icon *ngIf=\"page.matIcon\" slot=\"start\">{{page.matIcon}}</mat-icon>\n\n <ion-label color=\"primary\" [innerHTML]=\"page.title\"></ion-label>\n </ion-item>\n </ion-card-title>\n </ion-card-header>\n\n <!-- children pages -->\n <ion-card-content class=\"ion-no-padding ion-padding-start\" *ngIf=\"page.children?.length\">\n <ion-list class=\"ion-no-padding\">\n <ion-item detail=\"true\"\n *ngFor=\"let childPage of page.children\"\n tappable class=\"text-1x\"\n [routerLink]=\"childPage.path\"\n routerDirection=\"root\">\n <!-- page icon-->\n <ion-icon *ngIf=\"childPage.icon\" slot=\"start\" color=\"dark\" [name]=\"childPage.icon\"></ion-icon>\n <mat-icon *ngIf=\"childPage.matIcon\" slot=\"start\" color=\"dark\">{{childPage.matIcon}}</mat-icon>\n\n <ion-label color=\"dark\" [innerHTML]=\"childPage.title\"></ion-label>\n </ion-item>\n </ion-list>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Display configured Departments logos -->\n <ng-template #bottomBanner>\n <div class=\"bottom-banner ion-text-center ion-padding\" *ngIf=\"!loadingBanner\" @fadeInAnimation>\n <a href=\"{{ item.siteUrl }}\" *ngFor=\"let item of $partners | async \">\n <img class=\"logo\" src=\"{{ item.logo }}\" alt=\"{{item.label}}\" [title]=\"item.label\" />\n </a>\n </div>\n </ng-template>\n\n\n</ion-content>\n", styles: ["h1,h2,h3,h4,h5{white-space:normal}.center,ion-content ion-card.welcome img{text-align:center;display:inline-block}ion-content{--background: transparent;background-size:cover;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;display:inline-block;padding:15px;background-color:var(--ion-color-primary)}ion-content .loading-page{display:block;height:100%;width:100%;position:absolute;background-color:var(--ion-color-primary);z-index:5;transition:background .5s linear;-webkit-transition:background 1.5s linear}ion-content .loading-page.hidden{background-color:rgba(var(--ion-color-primary),0)}ion-content ion-card{z-index:9}ion-content ion-card ion-card-header{display:block}ion-content ion-card ion-card-header ion-card-subtitle,ion-content ion-card ion-card-header ion-card-title{display:block;width:100%}ion-content ion-card.main{min-width:240px;max-width:400px;margin-left:auto;margin-right:auto}ion-content ion-card.welcome{background-color:#ffffffb3}ion-content ion-card.welcome button{margin-top:16px}ion-content ion-card.welcome img{max-width:250px}ion-content .history-container{margin-left:auto;margin-right:auto}ion-content .history-container ion-card{display:inline-block;box-sizing:border-box;z-index:8;min-width:240px;max-width:400px;width:100%;margin-left:auto;margin-right:auto;background-color:#ffffffb3}ion-content .history-container ion-card ion-card-header ion-card-subtitle{height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle button[float-start]{z-index:99;margin:0}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label{line-height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label[float-end]{padding-right:16px;font-weight:400}ion-content .history-container ion-card ion-card-header ion-card-title ion-item{--ion-item-background: $transparent;--ion-item-icon-color: var(--ion-color-primary);--ion-item-text-color: var(--ion-color-primary)}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-header ion-card-title ion-item mat-icon[slot=start]{margin-right:unset;margin-inline-end:16px!important;color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-text,ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-label{color:var(--ion-item-text-color)!important;-webkit-text-size-adjust:100%;text-size-adjust:100%}ion-content .history-container ion-card ion-card-content ion-list{--ion-item-background: var(--ion-color-transparent)}ion-content .history-container ion-card ion-card-content ion-list ion-item{--ion-item-icon-color: var(--ion-color-dark);--ion-item-text-color: var(--ion-color-dark)}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-content ion-list ion-item mat-icon[slot=start]{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor;margin-right:unset;margin-inline-end:16px!important}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-text,ion-content .history-container ion-card ion-card-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-content .bottom-banner{background-color:#ffffffb3;min-width:240px;z-index:0;position:absolute;left:16px;right:16px;bottom:16px}ion-content .bottom-banner img.logo{max-height:50px;margin-left:8px}ion-content ion-text{text-align:center}@media screen and (max-width: 575px){ion-content ion-card{min-width:160px;width:calc(100% - 20px);max-width:100%;margin-left:10px;margin-right:10px}ion-content ion-card h1{margin-top:0;font-size:20px}ion-content ion-card ion-card-content ion-button{margin-top:8px}ion-content .bottom-banner{display:block;position:unset}ion-content .bottom-banner img.logo{max-height:30px}}@media screen and (max-width: 767px) and (min-width: 576px){ion-content{min-height:555px}ion-content .bottom-banner img.logo{max-height:40px}}@media screen and (min-width: 768px){ion-content{min-height:calc(100% - 100px)}}.content-credits{position:absolute;right:5px;bottom:5px}\n"], dependencies: [{ kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i1.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i1.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i1.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i1.IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: i1.IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: i1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i1.IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { kind: "component", type: i1.IonRow, selector: "ion-row" }, { kind: "component", type: i1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i1.RouterLinkDelegate, selector: ":not(a):not(area)[routerLink]", inputs: ["routerDirection", "routerAnimation"] }, { kind: "directive", type: i1$2.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i8$1.MatMenu, selector: "mat-menu", exportAs: ["matMenu"] }, { kind: "component", type: i8$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["disabled", "disableRipple", "role"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i8$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", exportAs: ["matMenuTrigger"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["progressBarMode", "title", "color", "class", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "onSearch"] }, { kind: "directive", type: i1$6.RouterLink, selector: ":not(a):not(area)[routerLink]", inputs: ["queryParams", "fragment", "queryParamsHandling", "state", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: AppInstallUpgradeCard, selector: "app-install-upgrade-card", inputs: ["isLogin", "showUpgradeWarning", "showOfflineWarning", "showInstallButton"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }, { kind: "pipe", type: DateFormatPipe, name: "dateFormat" }, { kind: "pipe", type: DateFromNowPipe, name: "dateFromNow" }], animations: [fadeInAnimation, slideUpDownAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
24906
25092
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: HomePage, decorators: [{
24907
25093
  type: Component,
24908
- args: [{ selector: 'app-page-home', changeDetection: ChangeDetectionStrategy.OnPush, animations: [fadeInAnimation, slideUpDownAnimation], template: "<app-toolbar [canGoBack]=\"false\"\n visible-xs visible-sm visible-mobile>\n\n <!-- change locale button -->\n <ion-button slot=\"end\" *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\">&nbsp;{{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n</app-toolbar>\n\n<!-- change locale menu -->\n<mat-menu #localeMenu=\"matMenu\">\n <button *ngFor=\"let item of locales\"\n mat-menu-item\n (click)=\"changeLanguage(item.key)\">\n <ion-label>{{item.value}}</ion-label>\n </button>\n</mat-menu>\n\n<ion-content [ngStyle]=\"contentStyle\" no-padding-xs>\n\n <!-- loading spinner -->\n <div class=\"loading-page\" [class.cdk-visually-hidden]=\"!loading\">\n <div class=\"spinner\" *ngIf=\"loading && showSpinner\">\n <p [innerHTML]=\"'COMMON.LOADING_DOTS'|translate\"></p>\n <div class=\"sk-cube1 sk-cube\"></div>\n <div class=\"sk-cube2 sk-cube\"></div>\n <div class=\"sk-cube4 sk-cube\"></div>\n <div class=\"sk-cube3 sk-cube\"></div>\n </div>\n </div>\n\n <!-- Desktop: translucent top toolbar -->\n <div hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar translucent>\n <ion-buttons slot=\"start\">\n <ion-menu-toggle>\n <ion-button color=\"light\" fill=\"clear\">\n <ion-icon slot=\"icon-only\" name=\"menu\"></ion-icon>\n </ion-button>\n </ion-menu-toggle>\n </ion-buttons>\n\n <ion-buttons slot=\"end\">\n <!-- change locale button -->\n <ion-button color=\"secondary\" fill=\"solid\"\n *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\">&nbsp;{{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </div>\n\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\"\n [showInstallButton]=\"true\">\n </app-install-upgrade-card>\n\n <!-- Welcome card -->\n <ion-card *ngIf=\"!loading\"\n class=\"main welcome ion-padding ion-text-center ion-align-self-center\"\n @fadeInAnimation>\n <ion-card-header>\n <ion-card-title class=\"ion-text-center\">\n <span *ngIf=\"isWeb\" [innerHTML]=\"'HOME.WELCOME_WEB'|translate: {appName: appName}\"></span>\n <span *ngIf=\"!isWeb\" [innerHTML]=\"'HOME.WELCOME_APP'|translate: {appName: appName}\"></span>\n </ion-card-title>\n <ion-card-subtitle [innerHTML]=\"description\">\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content class=\"ion-no-padding\">\n <ion-text color=\"primary\">\n <img class=\"logo\" *ngIf=\"logo\" src=\"{{ logo }}\">\n </ion-text>\n <!-- register help text -->\n <ion-text *ngIf=\"!isLogin && canRegister\">\n <br/>\n <span translate>HOME.REGISTER_HELP</span>\n </ion-text>\n <div class=\"ion-padding-top\">\n\n <!-- If NOT login -->\n <ng-container *ngIf=\"!isLogin; else loginButtons;\">\n\n <ion-button *ngIf=\"canRegister\" expand=\"block\" color=\"tertiary\" (click)=\"register()\">\n <span translate>HOME.BTN_REGISTER</span>\n </ion-button>\n <ion-button expand=\"block\" color=\"light\" [routerLink]=\"['/']\" (click)=\"login()\">\n <span translate>AUTH.BTN_LOGIN</span>\n </ion-button>\n\n </ng-container>\n\n <!-- If user login -->\n <ng-template #loginButtons>\n\n <!-- Feature buttons -->\n <ng-container *ngIf=\"$filteredButtons | async as buttons\">\n <ng-container *ngFor=\"let item of buttons\">\n <ion-button *ngIf=\"item.path\"\n expand=\"block\" color=\"tertiary\"\n [class]=\"item.cssClass\"\n [routerLink]=\"item.path\"\n routerDirection=\"root\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-text>{{'HOME.BTN_DATA_ENTRY'|translate: {name: (item.title | translate) } }}</ion-text>\n </ion-button>\n\n <!-- divider -->\n <div *ngIf=\"!item.path && !item.action\" [class]=\"item.cssClass\">\n <ion-label translate>{{item.title}}&nbsp;</ion-label>\n </div>\n </ng-container>\n\n <!--<p *ngIf=\"buttons.length\" class=\"visible-mobile\">&nbsp;</p>-->\n </ng-container>\n\n <ion-button *ngIf=\"showAccountButton\" expand=\"block\" color=\"secondary\" [routerLink]=\"['/account']\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" name=\"person-circle\"></ion-icon>\n <ion-text translate>HOME.BTN_MY_ACCOUNT</ion-text>\n </ion-button>\n\n <p hidden-xs hidden-sm hidden-mobile>\n <ion-text [innerHTML]=\"'HOME.NOT_THIS_ACCOUNT_QUESTION' | translate: {displayName: accountName }\"></ion-text>\n <br/>\n <ion-text>\n <a href=\"#\" (click)=\"logout($event)\">\n <span translate>HOME.BTN_DISCONNECT</span>\n </a>\n </ion-text>\n </p>\n\n\n </ng-template>\n\n </div>\n </ion-card-content>\n </ion-card>\n\n <!-- Page history -->\n <ion-grid *ngIf=\"!loading && isLogin && pageHistory.length; else bottomBanner\"\n class=\"history-container ion-align-self-center\">\n <ion-row>\n <ion-col size=\"12\" size-xl=\"\" *ngFor=\"let page of pageHistory; trackBy: getPagePath\"\n class=\"ion-text-center\">\n <ion-card class=\"ion-align-self-start ion-text-start\" @fadeInAnimation>\n <ion-card-header class=\"ion-no-padding\">\n <!-- top bar -->\n <ion-card-subtitle>\n <button type=\"button\" tabindex=\"-1\"\n (click)=\"settings.removePageHistory(page.path)\"\n mat-icon-button class=\"ion-float-start ion-no-margin\">\n <mat-icon>close</mat-icon>\n </button>\n <ion-label [innerHTML]=\"page.subtitle|translate\"></ion-label>\n <ion-text class=\"ion-float-end\" [title]=\"page.time|dateFormat:{time: true}\">\n <small><ion-icon name=\"time-outline\"></ion-icon>&nbsp;{{ page.time|dateFromNow }}</small>&nbsp;\n </ion-text>\n </ion-card-subtitle>\n\n <!-- main page -->\n <ion-card-title class=\"ion-no-margin ion-no-padding\">\n <ion-item detail=\"true\"\n tappable class=\"text-1x\"\n [routerLink]=\"page.path\"\n routerDirection=\"root\" lines=\"none\">\n <!-- page icon-->\n <ion-icon *ngIf=\"page.icon\" slot=\"start\" [name]=\"page.icon\"></ion-icon>\n <mat-icon *ngIf=\"page.matIcon\" slot=\"start\">{{page.matIcon}}</mat-icon>\n\n <ion-label color=\"primary\" [innerHTML]=\"page.title\"></ion-label>\n </ion-item>\n </ion-card-title>\n </ion-card-header>\n\n <!-- children pages -->\n <ion-card-content class=\"ion-no-padding ion-padding-start\" *ngIf=\"page.children?.length\">\n <ion-list class=\"ion-no-padding\">\n <ion-item detail=\"true\"\n *ngFor=\"let childPage of page.children\"\n tappable class=\"text-1x\"\n [routerLink]=\"childPage.path\"\n routerDirection=\"root\">\n <!-- page icon-->\n <ion-icon *ngIf=\"childPage.icon\" slot=\"start\" color=\"dark\" [name]=\"childPage.icon\"></ion-icon>\n <mat-icon *ngIf=\"childPage.matIcon\" slot=\"start\" color=\"dark\">{{childPage.matIcon}}</mat-icon>\n\n <ion-label color=\"dark\" [innerHTML]=\"childPage.title\"></ion-label>\n </ion-item>\n </ion-list>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Display configured Departments logos -->\n <ng-template #bottomBanner>\n <div class=\"bottom-banner ion-text-center ion-padding\" *ngIf=\"!loadingBanner\" @fadeInAnimation>\n <a href=\"{{ item.siteUrl }}\" *ngFor=\"let item of $partners | async \">\n <img class=\"logo\" src=\"{{ item.logo }}\" alt=\"{{item.label}}\" [title]=\"item.label\" />\n </a>\n </div>\n </ng-template>\n\n\n</ion-content>\n", styles: ["h1,h2,h3,h4,h5{white-space:normal}.center,ion-content ion-card.welcome img{text-align:center;display:inline-block}ion-content{--background: transparent;background-size:cover;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;display:inline-block;padding:15px;background-color:var(--ion-color-primary)}ion-content .loading-page{display:block;height:100%;width:100%;position:absolute;background-color:var(--ion-color-primary);z-index:5;transition:background .5s linear;-webkit-transition:background 1.5s linear}ion-content .loading-page.hidden{background-color:rgba(var(--ion-color-primary),0)}ion-content ion-card{z-index:9}ion-content ion-card ion-card-header{display:block}ion-content ion-card ion-card-header ion-card-subtitle,ion-content ion-card ion-card-header ion-card-title{display:block;width:100%}ion-content ion-card.main{min-width:240px;max-width:400px;margin-left:auto;margin-right:auto}ion-content ion-card.welcome{background-color:#ffffffb3}ion-content ion-card.welcome button{margin-top:16px}ion-content ion-card.welcome img{max-width:250px}ion-content .history-container{margin-left:auto;margin-right:auto}ion-content .history-container ion-card{display:inline-block;box-sizing:border-box;z-index:8;min-width:240px;max-width:400px;width:100%;margin-left:auto;margin-right:auto;background-color:#ffffffb3}ion-content .history-container ion-card ion-card-header ion-card-subtitle{height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle button[float-start]{z-index:99;margin:0}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label{line-height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label[float-end]{padding-right:16px;font-weight:400}ion-content .history-container ion-card ion-card-header ion-card-title ion-item{--ion-item-background: $transparent;--ion-item-icon-color: var(--ion-color-primary);--ion-item-text-color: var(--ion-color-primary)}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-header ion-card-title ion-item mat-icon[slot=start]{margin-right:unset;margin-inline-end:16px!important;color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-text,ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-label{color:var(--ion-item-text-color)!important;-webkit-text-size-adjust:100%;text-size-adjust:100%}ion-content .history-container ion-card ion-card-content ion-list{--ion-item-background: var(--ion-color-transparent)}ion-content .history-container ion-card ion-card-content ion-list ion-item{--ion-item-icon-color: var(--ion-color-dark);--ion-item-text-color: var(--ion-color-dark)}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-content ion-list ion-item mat-icon[slot=start]{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor;margin-right:unset;margin-inline-end:16px!important}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-text,ion-content .history-container ion-card ion-card-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-content .bottom-banner{background-color:#ffffffb3;min-width:240px;z-index:0;position:absolute;left:16px;right:16px;bottom:16px}ion-content .bottom-banner img.logo{max-height:50px;margin-left:8px}ion-content ion-text{text-align:center}@media screen and (max-width: 575px){ion-content ion-card{min-width:160px;width:calc(100% - 20px);max-width:100%;margin-left:10px;margin-right:10px}ion-content ion-card h1{margin-top:0;font-size:20px}ion-content ion-card ion-card-content ion-button{margin-top:8px}ion-content .bottom-banner{display:block;position:unset}ion-content .bottom-banner img.logo{max-height:30px}}@media screen and (max-width: 767px) and (min-width: 576px){ion-content{min-height:555px}ion-content .bottom-banner img.logo{max-height:40px}}@media screen and (min-width: 768px){ion-content{min-height:calc(100% - 100px)}}\n"] }]
25094
+ args: [{ selector: 'app-page-home', changeDetection: ChangeDetectionStrategy.OnPush, animations: [fadeInAnimation, slideUpDownAnimation], template: "<app-toolbar [canGoBack]=\"false\"\n visible-xs visible-sm visible-mobile>\n\n <!-- change locale button -->\n <ion-button slot=\"end\" *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\">&nbsp;{{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n</app-toolbar>\n\n<!-- change locale menu -->\n<mat-menu #localeMenu=\"matMenu\">\n <button *ngFor=\"let item of locales\"\n mat-menu-item\n (click)=\"changeLanguage(item.key)\">\n <ion-label>{{item.value}}</ion-label>\n </button>\n</mat-menu>\n\n<ion-content [ngStyle]=\"contentStyle\" no-padding-xs>\n\n <span class=\"content-credits\"><ion-text>{{ contentCredits }}</ion-text></span>\n\n <!-- loading spinner -->\n <div class=\"loading-page\" [class.cdk-visually-hidden]=\"!loading\">\n <div class=\"spinner\" *ngIf=\"loading && showSpinner\">\n <p [innerHTML]=\"'COMMON.LOADING_DOTS'|translate\"></p>\n <div class=\"sk-cube1 sk-cube\"></div>\n <div class=\"sk-cube2 sk-cube\"></div>\n <div class=\"sk-cube4 sk-cube\"></div>\n <div class=\"sk-cube3 sk-cube\"></div>\n </div>\n </div>\n\n <!-- Desktop: translucent top toolbar -->\n <div hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar translucent>\n <ion-buttons slot=\"start\">\n <ion-menu-toggle>\n <ion-button color=\"light\" fill=\"clear\">\n <ion-icon slot=\"icon-only\" name=\"menu\"></ion-icon>\n </ion-button>\n </ion-menu-toggle>\n </ion-buttons>\n\n <ion-buttons slot=\"end\">\n <!-- change locale button -->\n <ion-button color=\"secondary\" fill=\"solid\"\n *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\">&nbsp;{{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </div>\n\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\"\n [showInstallButton]=\"true\">\n </app-install-upgrade-card>\n\n <!-- Welcome card -->\n <ion-card *ngIf=\"!loading\"\n class=\"main welcome ion-padding ion-text-center ion-align-self-center\"\n @fadeInAnimation>\n <ion-card-header>\n <ion-card-title class=\"ion-text-center\">\n <span *ngIf=\"isWeb\" [innerHTML]=\"'HOME.WELCOME_WEB'|translate: {appName: appName}\"></span>\n <span *ngIf=\"!isWeb\" [innerHTML]=\"'HOME.WELCOME_APP'|translate: {appName: appName}\"></span>\n </ion-card-title>\n <ion-card-subtitle [innerHTML]=\"description\">\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content class=\"ion-no-padding\">\n <ion-text color=\"primary\">\n <img class=\"logo\" *ngIf=\"logo\" src=\"{{ logo }}\">\n </ion-text>\n <!-- register help text -->\n <ion-text *ngIf=\"!isLogin && canRegister\">\n <br/>\n <span translate>HOME.REGISTER_HELP</span>\n </ion-text>\n <div class=\"ion-padding-top\">\n\n <!-- If NOT login -->\n <ng-container *ngIf=\"!isLogin; else loginButtons;\">\n\n <ion-button *ngIf=\"canRegister\" expand=\"block\" color=\"tertiary\" (click)=\"register()\">\n <span translate>HOME.BTN_REGISTER</span>\n </ion-button>\n <ion-button expand=\"block\" color=\"light\" [routerLink]=\"['/']\" (click)=\"login()\">\n <span translate>AUTH.BTN_LOGIN</span>\n </ion-button>\n\n </ng-container>\n\n <!-- If user login -->\n <ng-template #loginButtons>\n\n <!-- Feature buttons -->\n <ng-container *ngIf=\"$filteredButtons | async as buttons\">\n <ng-container *ngFor=\"let item of buttons\">\n <ion-button *ngIf=\"item.path\"\n expand=\"block\" color=\"tertiary\"\n [class]=\"item.cssClass\"\n [routerLink]=\"item.path\"\n routerDirection=\"root\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-text>{{'HOME.BTN_DATA_ENTRY'|translate: {name: (item.title | translate) } }}</ion-text>\n </ion-button>\n\n <!-- divider -->\n <div *ngIf=\"!item.path && !item.action\" [class]=\"item.cssClass\">\n <ion-label translate>{{item.title}}&nbsp;</ion-label>\n </div>\n </ng-container>\n\n <!--<p *ngIf=\"buttons.length\" class=\"visible-mobile\">&nbsp;</p>-->\n </ng-container>\n\n <ion-button *ngIf=\"showAccountButton\" expand=\"block\" color=\"secondary\" [routerLink]=\"['/account']\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" name=\"person-circle\"></ion-icon>\n <ion-text translate>HOME.BTN_MY_ACCOUNT</ion-text>\n </ion-button>\n\n <p hidden-xs hidden-sm hidden-mobile>\n <ion-text [innerHTML]=\"'HOME.NOT_THIS_ACCOUNT_QUESTION' | translate: {displayName: accountName }\"></ion-text>\n <br/>\n <ion-text>\n <a href=\"#\" (click)=\"logout($event)\">\n <span translate>HOME.BTN_DISCONNECT</span>\n </a>\n </ion-text>\n </p>\n\n\n </ng-template>\n\n </div>\n </ion-card-content>\n </ion-card>\n\n <!-- Page history -->\n <ion-grid *ngIf=\"!loading && isLogin && pageHistory.length; else bottomBanner\"\n class=\"history-container ion-align-self-center\">\n <ion-row>\n <ion-col size=\"12\" size-xl=\"\" *ngFor=\"let page of pageHistory; trackBy: getPagePath\"\n class=\"ion-text-center\">\n <ion-card class=\"ion-align-self-start ion-text-start\" @fadeInAnimation>\n <ion-card-header class=\"ion-no-padding\">\n <!-- top bar -->\n <ion-card-subtitle>\n <button type=\"button\" tabindex=\"-1\"\n (click)=\"settings.removePageHistory(page.path)\"\n mat-icon-button class=\"ion-float-start ion-no-margin\">\n <mat-icon>close</mat-icon>\n </button>\n <ion-label [innerHTML]=\"page.subtitle|translate\"></ion-label>\n <ion-text class=\"ion-float-end\" [title]=\"page.time|dateFormat:{time: true}\">\n <small><ion-icon name=\"time-outline\"></ion-icon>&nbsp;{{ page.time|dateFromNow }}</small>&nbsp;\n </ion-text>\n </ion-card-subtitle>\n\n <!-- main page -->\n <ion-card-title class=\"ion-no-margin ion-no-padding\">\n <ion-item detail=\"true\"\n tappable class=\"text-1x\"\n [routerLink]=\"page.path\"\n routerDirection=\"root\" lines=\"none\">\n <!-- page icon-->\n <ion-icon *ngIf=\"page.icon\" slot=\"start\" [name]=\"page.icon\"></ion-icon>\n <mat-icon *ngIf=\"page.matIcon\" slot=\"start\">{{page.matIcon}}</mat-icon>\n\n <ion-label color=\"primary\" [innerHTML]=\"page.title\"></ion-label>\n </ion-item>\n </ion-card-title>\n </ion-card-header>\n\n <!-- children pages -->\n <ion-card-content class=\"ion-no-padding ion-padding-start\" *ngIf=\"page.children?.length\">\n <ion-list class=\"ion-no-padding\">\n <ion-item detail=\"true\"\n *ngFor=\"let childPage of page.children\"\n tappable class=\"text-1x\"\n [routerLink]=\"childPage.path\"\n routerDirection=\"root\">\n <!-- page icon-->\n <ion-icon *ngIf=\"childPage.icon\" slot=\"start\" color=\"dark\" [name]=\"childPage.icon\"></ion-icon>\n <mat-icon *ngIf=\"childPage.matIcon\" slot=\"start\" color=\"dark\">{{childPage.matIcon}}</mat-icon>\n\n <ion-label color=\"dark\" [innerHTML]=\"childPage.title\"></ion-label>\n </ion-item>\n </ion-list>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Display configured Departments logos -->\n <ng-template #bottomBanner>\n <div class=\"bottom-banner ion-text-center ion-padding\" *ngIf=\"!loadingBanner\" @fadeInAnimation>\n <a href=\"{{ item.siteUrl }}\" *ngFor=\"let item of $partners | async \">\n <img class=\"logo\" src=\"{{ item.logo }}\" alt=\"{{item.label}}\" [title]=\"item.label\" />\n </a>\n </div>\n </ng-template>\n\n\n</ion-content>\n", styles: ["h1,h2,h3,h4,h5{white-space:normal}.center,ion-content ion-card.welcome img{text-align:center;display:inline-block}ion-content{--background: transparent;background-size:cover;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;display:inline-block;padding:15px;background-color:var(--ion-color-primary)}ion-content .loading-page{display:block;height:100%;width:100%;position:absolute;background-color:var(--ion-color-primary);z-index:5;transition:background .5s linear;-webkit-transition:background 1.5s linear}ion-content .loading-page.hidden{background-color:rgba(var(--ion-color-primary),0)}ion-content ion-card{z-index:9}ion-content ion-card ion-card-header{display:block}ion-content ion-card ion-card-header ion-card-subtitle,ion-content ion-card ion-card-header ion-card-title{display:block;width:100%}ion-content ion-card.main{min-width:240px;max-width:400px;margin-left:auto;margin-right:auto}ion-content ion-card.welcome{background-color:#ffffffb3}ion-content ion-card.welcome button{margin-top:16px}ion-content ion-card.welcome img{max-width:250px}ion-content .history-container{margin-left:auto;margin-right:auto}ion-content .history-container ion-card{display:inline-block;box-sizing:border-box;z-index:8;min-width:240px;max-width:400px;width:100%;margin-left:auto;margin-right:auto;background-color:#ffffffb3}ion-content .history-container ion-card ion-card-header ion-card-subtitle{height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle button[float-start]{z-index:99;margin:0}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label{line-height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label[float-end]{padding-right:16px;font-weight:400}ion-content .history-container ion-card ion-card-header ion-card-title ion-item{--ion-item-background: $transparent;--ion-item-icon-color: var(--ion-color-primary);--ion-item-text-color: var(--ion-color-primary)}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-header ion-card-title ion-item mat-icon[slot=start]{margin-right:unset;margin-inline-end:16px!important;color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-text,ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-label{color:var(--ion-item-text-color)!important;-webkit-text-size-adjust:100%;text-size-adjust:100%}ion-content .history-container ion-card ion-card-content ion-list{--ion-item-background: var(--ion-color-transparent)}ion-content .history-container ion-card ion-card-content ion-list ion-item{--ion-item-icon-color: var(--ion-color-dark);--ion-item-text-color: var(--ion-color-dark)}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-content ion-list ion-item mat-icon[slot=start]{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor;margin-right:unset;margin-inline-end:16px!important}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-text,ion-content .history-container ion-card ion-card-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-content .bottom-banner{background-color:#ffffffb3;min-width:240px;z-index:0;position:absolute;left:16px;right:16px;bottom:16px}ion-content .bottom-banner img.logo{max-height:50px;margin-left:8px}ion-content ion-text{text-align:center}@media screen and (max-width: 575px){ion-content ion-card{min-width:160px;width:calc(100% - 20px);max-width:100%;margin-left:10px;margin-right:10px}ion-content ion-card h1{margin-top:0;font-size:20px}ion-content ion-card ion-card-content ion-button{margin-top:8px}ion-content .bottom-banner{display:block;position:unset}ion-content .bottom-banner img.logo{max-height:30px}}@media screen and (max-width: 767px) and (min-width: 576px){ion-content{min-height:555px}ion-content .bottom-banner img.logo{max-height:40px}}@media screen and (min-width: 768px){ion-content{min-height:calc(100% - 100px)}}.content-credits{position:absolute;right:5px;bottom:5px}\n"] }]
24909
25095
  }], ctorParameters: function () { return [{ type: AccountService }, { type: i1.ModalController }, { type: i1$2.TranslateService }, { type: i1.ToastController }, { type: ConfigService }, { type: PlatformService }, { type: i0.ChangeDetectorRef }, { type: NetworkService }, { type: LocalSettingsService }, { type: Environment, decorators: [{
24910
25096
  type: Inject,
24911
25097
  args: [ENVIRONMENT]
@@ -25507,7 +25693,7 @@ class SettingsPage extends AppForm {
25507
25693
  SettingsPage.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: SettingsPage, deps: [{ token: i0.Injector }, { token: PlatformService }, { token: i1.NavController }, { token: LocalSettingsValidatorService }, { token: i1.AlertController }, { token: i1$2.TranslateService }, { token: i1$3.UntypedFormBuilder }, { token: AccountService }, { token: LocalSettingsService }, { token: i0.ChangeDetectorRef }, { token: NetworkService }, { token: APP_LOCALES }, { token: APP_SETTINGS_MENU_ITEMS, optional: true }], target: i0.ɵɵFactoryTarget.Component });
25508
25694
  SettingsPage.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: SettingsPage, selector: "page-settings", providers: [
25509
25695
  { provide: ValidatorService, useExisting: LocalSettingsValidatorService },
25510
- ], usesInheritance: true, ngImport: i0, template: "<app-toolbar [title]=\"'SETTINGS.TITLE'|translate\" color=\"primary\"\n [hasValidate]=\"dirty && !saving\"\n [hasClose]=\"!dirty && !saving\"\n (onValidate)=\"save($event)\"\n (onClose)=\"close($event)\">\n\n <ion-buttons slot=\"end\">\n <!-- options menu -->\n <ion-button [matMenuTriggerFor]=\"optionsMenu\"\n *ngIf=\"!saving\"\n [disabled]=\"saving\">\n <mat-icon slot=\"icon-only\">more_vert</mat-icon>\n </ion-button>\n </ion-buttons>\n</app-toolbar>\n\n<!-- Options menu -->\n<mat-menu #optionsMenu=\"matMenu\">\n <!-- Clear cache -->\n <button mat-menu-item (click)=\"clearCache($event)\">\n <mat-icon><ion-icon name=\"trash-outline\"></ion-icon></mat-icon>\n <ion-label translate>SETTINGS.BTN_CLEAR_CACHE</ion-label>\n </button>\n\n <!-- Reset -->\n <button mat-menu-item\n [disabled]=\"!dirty\"\n (click)=\"cancel($event)\">\n <mat-icon><ion-icon name=\"refresh\"></ion-icon></mat-icon>\n <mat-label translate>COMMON.BTN_RESET</mat-label>\n </button>\n\n <!-- additional items -->\n <ng-container *ngIf=\"!loading\">\n <ng-container *ngFor=\"let item of menuItems\">\n <a *ngIf=\"item.path\"\n mat-menu-item\n class=\"{{item.cssClass}} {{item.color}}\"\n (click)=\"executeAction($event, item)\">\n <mat-icon *ngIf=\"item.icon\"><ion-icon [name]=\"item.icon\"></ion-icon></mat-icon>\n <mat-icon *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <mat-label translate>{{item.title}}</mat-label>\n </a>\n <!-- divider -->\n <mat-divider *ngIf=\"!item.path && !item.action\"\n class=\"{{item.cssClass}} {{item.color}}\">\n <mat-label translate>{{item.title}}</mat-label>\n </mat-divider>\n </ng-container>\n </ng-container>\n</mat-menu>\n\n<ion-content>\n\n <form [formGroup]=\"form\" novalidate (ngSubmit)=\"save($event)\" class=\"form-container\">\n\n <ion-grid class=\"ion-no-padding\">\n <ion-row class=\"ion-no-padding\">\n\n <!-- left margin -->\n <ion-col size=\"0\" size-lg=\"2\">&nbsp;\n </ion-col>\n\n <ion-col class=\"ion-padding\">\n\n <!-- error -->\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=\"error\" [innerHTML]=\"error|translate\"></ion-label>\n </ion-item>\n\n <p [innerHTML]=\"'SETTINGS.DESCRIPTION'|translate\"></p>\n\n <!-- account inheritance (desktop) -->\n <mat-form-field [hidden]=\"!isLogin\" *ngIf=\"!mobile; else accountInheritanceMobile\">\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\">\n\n <!-- check box (if desktop) -->\n <mat-checkbox\n *ngIf=\"!mobile; else accountInheritanceMobile\"\n (change)=\"setAccountInheritance($event.checked)\"\n [checked]=\"accountInheritance\">\n <span translate>SETTINGS.INHERIT_FROM_ACCOUNT</span>\n </mat-checkbox>\n\n <mat-error *ngIf=\"form.controls.accountInheritance.hasError('required')\" translate>ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n\n <!-- account inheritance (if mobile) -->\n <ng-template #accountInheritanceMobile>\n <mat-form-field [hidden]=\"!isLogin\">\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\">\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.INHERIT_FROM_ACCOUNT</ion-text>\n <mat-slide-toggle matSuffix\n (change)=\"setAccountInheritance($event.checked)\"\n [checked]=\"accountInheritance\">\n\n </mat-slide-toggle>\n <mat-error *ngIf=\"form.controls.accountInheritance.hasError('required')\" translate>ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ng-template>\n\n <!-- locale -->\n <mat-form-field>\n <mat-select [placeholder]=\"'SETTINGS.LOCALE'|translate\" formControlName=\"locale\" required>\n <mat-option *ngFor=\"let item of locales\" [value]=\"item.key\">\n {{item.value}}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.locale.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n\n <!-- Network entry -->\n <h3>\n <ion-text translate>SETTINGS.NETWORK_DIVIDER</ion-text>\n </h3>\n\n <!-- Peer address -->\n <mat-form-field>\n <input matInput type=\"text\" [placeholder]=\"'SETTINGS.PEER_URL'|translate\"\n formControlName=\"peerUrl\"\n required>\n\n <button mat-icon-button type=\"button\" matSuffix (click)=\"showSelectPeerModal()\" tabindex=\"-1\"\n [title]=\"'SETTINGS.BTN_CHANGE_PEER'|translate\">\n <mat-icon>search</mat-icon>\n </button>\n\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('required')\"\n translate>ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('peerAlive')\"\n translate>SETTINGS.ERROR.PEER_NOT_REACHABLE\n </mat-error>\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('peerNotCompatible')\"\n translate>SETTINGS.ERROR.PEER_NOT_COMPATIBLE\n </mat-error>\n </mat-form-field>\n\n <!-- Offline mode (if mobile) -->\n <mat-form-field *ngIf=\"mobile\">\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\">\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.OFFLINE_MODE</ion-text>\n <mat-slide-toggle matSuffix\n (change)=\"network.setForceOffline($event.checked)\"\n [checked]=\"network.offline\">\n\n </mat-slide-toggle>\n </mat-form-field>\n\n <!-- Data entry -->\n <h3>\n <ion-text translate>SETTINGS.DATA_ENTRY_DIVIDER</ion-text>\n </h3>\n\n <!-- Usage mode -->\n <mat-form-field>\n <mat-select [placeholder]=\"'SETTINGS.USAGE_MODE'|translate\" formControlName=\"usageMode\"\n required>\n <mat-option *ngFor=\"let item of usageModes\" [value]=\"item\">\n {{'SETTINGS.USAGE_MODES.'+item|translate}}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.usageMode.hasError('required')\"\n translate>ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n\n <!-- lat/long format-->\n <mat-form-field>\n <mat-select [placeholder]=\"'SETTINGS.LAT_LONG_FORMAT'|translate\" formControlName=\"latLongFormat\"\n required>\n <mat-option *ngFor=\"let item of latLongFormats\" [value]=\"item\">\n {{'COMMON.LAT_LONG.'+item+'_PLACEHOLDER'|translate}}\n </mat-option>\n </mat-select>\n <mat-error\n *ngIf=\"form.controls.latLongFormat.hasError('required')\"\n translate>ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n\n <!-- fields options -->\n <ion-grid formArrayName=\"properties\" class=\"ion-no-padding\">\n <ion-row class=\"ion-no-padding\">\n <ion-col class=\"ion-no-padding ion-align-self-end\" size=\"12\">\n <span class=\"toolbar-spacer\"></span>\n\n <!-- Show more options -->\n <ion-button color=\"light\" *ngIf=\"propertiesForm?.length === 0\"\n [title]=\"'SETTINGS.BTN_SHOW_MORE_HELP'|translate\"\n (click)=\"propertiesFormHelper.add()\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n </ion-col>\n </ion-row>\n\n <!-- Fields options -->\n <ng-container *ngFor=\"let propertyForm of propertiesForm?.controls; let i=index\">\n <ion-row class=\"ion-no-padding\" [formGroupName]=\"i\">\n\n <!-- property key -->\n <ion-col class=\"ion-no-padding\">\n <mat-form-field>\n <mat-select formControlName=\"key\"\n [placeholder]=\" 'SETTINGS.PROPERTY_KEY'|translate\">\n <mat-option *ngFor=\"let item of propertyDefinitions\" [value]=\"item.key\">{{ item.label | translate }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n\n <!-- property value -->\n <ion-col class=\"ion-no-padding\" padding-left>\n <app-form-field *ngIf=\"getPropertyDefinition(i); let definition\"\n floatLabel=\"never\"\n [definition]=\"definition\"\n [formControl]=\"propertyForm|formGetControl:'value'\"\n [placeholder]=\"'SETTINGS.PROPERTY_VALUE' | translate\"\n [required]=\"true\">\n </app-form-field>\n </ion-col>\n <ion-col size=\"2\" class=\"ion-no-padding\">\n <button type=\"button\" mat-icon-button color=\"light\"\n [disabled]=\"loading\"\n [title]=\"'COMMON.BTN_DELETE'|translate\"\n (click)=\"removePropertyAt(i)\">\n <mat-icon>close</mat-icon>\n </button>\n <button *ngIf=\"propertiesFormHelper.isLast(i)\"\n type=\"button\"\n mat-icon-button\n color=\"light\"\n [disabled]=\"loading\"\n [title]=\"'SETTINGS.BTN_ADD_PROPERTY'|translate\"\n (click)=\"propertiesFormHelper.add()\">\n <mat-icon>add</mat-icon>\n </button>\n </ion-col>\n </ion-row>\n </ng-container>\n </ion-grid>\n </ion-col>\n\n <!-- right margin -->\n <ion-col size=\"0\" size-lg=\"2\">&nbsp;\n </ion-col>\n </ion-row>\n </ion-grid>\n\n\n </form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <app-form-buttons-bar (onCancel)=\"cancel()\" (onSave)=\"save($event)\" [disabled]=\"!form.dirty || saving\"></app-form-buttons-bar>\n</ion-footer>\n", dependencies: [{ kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i1.IonRow, selector: "ion-row" }, { kind: "component", type: i1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$2.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$3.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i1$3.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "directive", type: i6$1.MatError, selector: "mat-error", inputs: ["id"] }, { kind: "component", type: i6$1.MatFormField, selector: "mat-form-field", inputs: ["color", "appearance", "hideRequiredMarker", "hintLabel", "floatLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i6$1.MatSuffix, selector: "[matSuffix]" }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i6$2.MatCheckbox, selector: "mat-checkbox", inputs: ["disableRipple", "color", "tabIndex"], exportAs: ["matCheckbox"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i11$2.MatMenu, selector: "mat-menu", exportAs: ["matMenu"] }, { kind: "component", type: i11$2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["disabled", "disableRipple", "role"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i11$2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", exportAs: ["matMenuTrigger"] }, { kind: "component", type: i12.MatSelect, selector: "mat-select", inputs: ["disabled", "disableRipple", "tabIndex"], exportAs: ["matSelect"] }, { kind: "component", type: i2.MatOption, selector: "mat-option", exportAs: ["matOption"] }, { kind: "component", type: i18.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "component", type: i14.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["disabled", "disableRipple", "color", "tabIndex"], exportAs: ["matSlideToggle"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["progressBarMode", "title", "color", "class", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "onSearch"] }, { kind: "component", type: AppFormField, selector: "app-form-field", inputs: ["definition", "required", "readonly", "disabled", "formControl", "formControlName", "placeholder", "compact", "floatLabel", "tabindex", "autofocus", "clearable", "chipColor", "debug", "class"], outputs: ["keyup.enter"] }, { kind: "component", type: FormButtonsBarComponent, selector: "app-form-buttons-bar", inputs: ["disabled", "disabledCancel", "classList"], outputs: ["onCancel", "onSave", "onNext", "onBack"] }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }, { kind: "pipe", type: FormGetControlPipe, name: "formGetControl" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
25696
+ ], usesInheritance: true, ngImport: i0, template: "<app-toolbar [title]=\"'SETTINGS.TITLE'|translate\" color=\"primary\"\n [hasValidate]=\"dirty && !saving\"\n [hasClose]=\"!dirty && !saving\"\n (onValidate)=\"save($event)\"\n (onClose)=\"close($event)\">\n\n <ion-buttons slot=\"end\">\n <!-- options menu -->\n <ion-button [matMenuTriggerFor]=\"optionsMenu\"\n *ngIf=\"!saving\"\n [disabled]=\"saving\">\n <mat-icon slot=\"icon-only\">more_vert</mat-icon>\n </ion-button>\n </ion-buttons>\n</app-toolbar>\n\n<!-- Options menu -->\n<mat-menu #optionsMenu=\"matMenu\">\n <!-- Clear cache -->\n <button mat-menu-item (click)=\"clearCache($event)\">\n <mat-icon><ion-icon name=\"trash-outline\"></ion-icon></mat-icon>\n <ion-label translate>SETTINGS.BTN_CLEAR_CACHE</ion-label>\n </button>\n\n <!-- Reset -->\n <button mat-menu-item\n [disabled]=\"!dirty\"\n (click)=\"cancel($event)\">\n <mat-icon><ion-icon name=\"refresh\"></ion-icon></mat-icon>\n <mat-label translate>COMMON.BTN_RESET</mat-label>\n </button>\n\n <!-- additional items -->\n <ng-container *ngIf=\"!loading\">\n <ng-container *ngFor=\"let item of menuItems\">\n <a *ngIf=\"item.path\"\n mat-menu-item\n class=\"{{item.cssClass}} {{item.color}}\"\n (click)=\"executeAction($event, item)\">\n <mat-icon *ngIf=\"item.icon\"><ion-icon [name]=\"item.icon\"></ion-icon></mat-icon>\n <mat-icon *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <mat-label translate>{{item.title}}</mat-label>\n </a>\n <!-- divider -->\n <mat-divider *ngIf=\"!item.path && !item.action\"\n class=\"{{item.cssClass}} {{item.color}}\">\n <mat-label translate>{{item.title}}</mat-label>\n </mat-divider>\n </ng-container>\n </ng-container>\n</mat-menu>\n\n<ion-content>\n\n <form [formGroup]=\"form\" novalidate (ngSubmit)=\"save($event)\" class=\"form-container\">\n\n <ion-grid class=\"ion-no-padding\">\n <ion-row class=\"ion-no-padding\">\n\n <!-- left margin -->\n <ion-col size=\"0\" size-lg=\"2\">&nbsp;\n </ion-col>\n\n <ion-col class=\"ion-padding\">\n\n <!-- error -->\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=\"error\" [innerHTML]=\"error|translate\"></ion-label>\n </ion-item>\n\n <p [innerHTML]=\"'SETTINGS.DESCRIPTION'|translate\"></p>\n\n <!-- account inheritance (desktop) -->\n <mat-form-field [hidden]=\"!isLogin\" *ngIf=\"!mobile; else accountInheritanceMobile\">\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\">\n\n <!-- check box (if desktop) -->\n <mat-checkbox\n *ngIf=\"!mobile; else accountInheritanceMobile\"\n (change)=\"setAccountInheritance($event.checked)\"\n [checked]=\"accountInheritance\">\n <span translate>SETTINGS.INHERIT_FROM_ACCOUNT</span>\n </mat-checkbox>\n\n <mat-error *ngIf=\"form.controls.accountInheritance.hasError('required')\" translate>ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n\n <!-- account inheritance (if mobile) -->\n <ng-template #accountInheritanceMobile>\n <mat-form-field [hidden]=\"!isLogin\">\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\">\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.INHERIT_FROM_ACCOUNT</ion-text>\n <mat-slide-toggle matSuffix\n (change)=\"setAccountInheritance($event.checked)\"\n [checked]=\"accountInheritance\">\n\n </mat-slide-toggle>\n <mat-error *ngIf=\"form.controls.accountInheritance.hasError('required')\" translate>ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n </ng-template>\n\n <!-- locale -->\n <mat-form-field>\n <mat-select [placeholder]=\"'SETTINGS.LOCALE'|translate\" formControlName=\"locale\" required>\n <mat-option *ngFor=\"let item of locales\" [value]=\"item.key\">\n {{item.value}}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.locale.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n </mat-form-field>\n\n <!-- Network entry -->\n <h3>\n <ion-text translate>SETTINGS.NETWORK_DIVIDER</ion-text>\n </h3>\n\n <!-- Peer address -->\n <mat-form-field>\n <input matInput type=\"text\" [placeholder]=\"'SETTINGS.PEER_URL'|translate\"\n formControlName=\"peerUrl\"\n required>\n\n <button mat-icon-button type=\"button\" matSuffix (click)=\"showSelectPeerModal()\" tabindex=\"-1\"\n [title]=\"'SETTINGS.BTN_CHANGE_PEER'|translate\">\n <mat-icon>search</mat-icon>\n </button>\n\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('required')\"\n translate>ERROR.FIELD_REQUIRED\n </mat-error>\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('peerAlive')\"\n translate>SETTINGS.ERROR.PEER_NOT_REACHABLE\n </mat-error>\n <mat-error *ngIf=\"form.controls.peerUrl.hasError('peerNotCompatible')\"\n translate>SETTINGS.ERROR.PEER_NOT_COMPATIBLE\n </mat-error>\n </mat-form-field>\n\n <!-- Offline mode (if mobile) -->\n <mat-form-field *ngIf=\"mobile\">\n <input matInput hidden formControlName=\"accountInheritance\" type=\"text\">\n\n <!-- slide toggle -->\n <ion-text translate>SETTINGS.OFFLINE_MODE</ion-text>\n <mat-slide-toggle matSuffix\n (change)=\"network.setForceOffline($event.checked)\"\n [checked]=\"network.offline\">\n\n </mat-slide-toggle>\n </mat-form-field>\n\n <!-- Data entry -->\n <h3>\n <ion-text translate>SETTINGS.DATA_ENTRY_DIVIDER</ion-text>\n </h3>\n\n <!-- Usage mode -->\n <mat-form-field>\n <mat-select [placeholder]=\"'SETTINGS.USAGE_MODE'|translate\" formControlName=\"usageMode\"\n required>\n <mat-option *ngFor=\"let item of usageModes\" [value]=\"item\">\n {{'SETTINGS.USAGE_MODES.'+item|translate}}\n </mat-option>\n </mat-select>\n <mat-error *ngIf=\"form.controls.usageMode.hasError('required')\"\n translate>ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n\n <!-- lat/long format-->\n <mat-form-field>\n <mat-select [placeholder]=\"'SETTINGS.LAT_LONG_FORMAT'|translate\" formControlName=\"latLongFormat\"\n required>\n <mat-option *ngFor=\"let item of latLongFormats\" [value]=\"item\">\n {{'COMMON.LAT_LONG.'+item+'_PLACEHOLDER'|translate}}\n </mat-option>\n </mat-select>\n <mat-error\n *ngIf=\"form.controls.latLongFormat.hasError('required')\"\n translate>ERROR.FIELD_REQUIRED\n </mat-error>\n </mat-form-field>\n\n <!-- fields options -->\n <ion-grid formArrayName=\"properties\" class=\"ion-no-padding\">\n <ion-row class=\"ion-no-padding\">\n <ion-col class=\"ion-no-padding ion-align-self-end\" size=\"12\">\n <span class=\"toolbar-spacer\"></span>\n\n <!-- Show more options -->\n <ion-button color=\"light\" *ngIf=\"propertiesForm?.length === 0\"\n [title]=\"'SETTINGS.BTN_SHOW_MORE_HELP'|translate\"\n (click)=\"propertiesFormHelper.add()\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n </ion-col>\n </ion-row>\n\n <!-- Fields options -->\n <ng-container *ngFor=\"let propertyForm of propertiesForm?.controls; let i=index\">\n <ion-row class=\"ion-no-padding\" [formGroupName]=\"i\">\n\n <!-- property key -->\n <ion-col class=\"ion-no-padding\">\n <mat-form-field>\n <mat-select formControlName=\"key\"\n [placeholder]=\" 'SETTINGS.PROPERTY_KEY'|translate\">\n <mat-option *ngFor=\"let item of propertyDefinitions\" [value]=\"item.key\">{{ item.label | translate }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n\n <!-- property value -->\n <ion-col class=\"ion-no-padding\" padding-left>\n <app-form-field *ngIf=\"getPropertyDefinition(i); let definition\"\n floatLabel=\"never\"\n [definition]=\"definition\"\n [formControl]=\"propertyForm|formGetControl:'value'\"\n [placeholder]=\"'SETTINGS.PROPERTY_VALUE' | translate\"\n [required]=\"true\">\n </app-form-field>\n </ion-col>\n <ion-col size=\"2\" class=\"ion-no-padding\">\n <button type=\"button\" mat-icon-button color=\"light\"\n [disabled]=\"loading\"\n [title]=\"'COMMON.BTN_DELETE'|translate\"\n (click)=\"removePropertyAt(i)\">\n <mat-icon>close</mat-icon>\n </button>\n <button *ngIf=\"propertiesFormHelper.isLast(i)\"\n type=\"button\"\n mat-icon-button\n color=\"light\"\n [disabled]=\"loading\"\n [title]=\"'SETTINGS.BTN_ADD_PROPERTY'|translate\"\n (click)=\"propertiesFormHelper.add()\">\n <mat-icon>add</mat-icon>\n </button>\n </ion-col>\n </ion-row>\n </ng-container>\n </ion-grid>\n </ion-col>\n\n <!-- right margin -->\n <ion-col size=\"0\" size-lg=\"2\">&nbsp;\n </ion-col>\n </ion-row>\n </ion-grid>\n\n\n </form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <app-form-buttons-bar (onCancel)=\"cancel()\" (onSave)=\"save($event)\" [disabled]=\"!form.dirty || saving\"></app-form-buttons-bar>\n</ion-footer>\n", dependencies: [{ kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i1.IonRow, selector: "ion-row" }, { kind: "component", type: i1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$2.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$3.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i1$3.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "directive", type: i6$1.MatError, selector: "mat-error", inputs: ["id"] }, { kind: "component", type: i6$1.MatFormField, selector: "mat-form-field", inputs: ["color", "appearance", "hideRequiredMarker", "hintLabel", "floatLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i6$1.MatSuffix, selector: "[matSuffix]" }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i6$2.MatCheckbox, selector: "mat-checkbox", inputs: ["disableRipple", "color", "tabIndex"], exportAs: ["matCheckbox"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i8$1.MatMenu, selector: "mat-menu", exportAs: ["matMenu"] }, { kind: "component", type: i8$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["disabled", "disableRipple", "role"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i8$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", exportAs: ["matMenuTrigger"] }, { kind: "component", type: i12.MatSelect, selector: "mat-select", inputs: ["disabled", "disableRipple", "tabIndex"], exportAs: ["matSelect"] }, { kind: "component", type: i2.MatOption, selector: "mat-option", exportAs: ["matOption"] }, { kind: "component", type: i18.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "component", type: i14.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["disabled", "disableRipple", "color", "tabIndex"], exportAs: ["matSlideToggle"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["progressBarMode", "title", "color", "class", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "onSearch"] }, { kind: "component", type: AppFormField, selector: "app-form-field", inputs: ["definition", "required", "readonly", "disabled", "formControl", "formControlName", "placeholder", "compact", "floatLabel", "tabindex", "autofocus", "clearable", "chipColor", "debug", "class"], outputs: ["keyup.enter"] }, { kind: "component", type: FormButtonsBarComponent, selector: "app-form-buttons-bar", inputs: ["disabled", "disabledCancel", "classList"], outputs: ["onCancel", "onSave", "onNext", "onBack"] }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }, { kind: "pipe", type: FormGetControlPipe, name: "formGetControl" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
25511
25697
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: SettingsPage, decorators: [{
25512
25698
  type: Component,
25513
25699
  args: [{ selector: 'page-settings', providers: [
@@ -28219,7 +28405,7 @@ class AppPropertiesTable extends AppInMemoryTable {
28219
28405
  AppPropertiesTable.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AppPropertiesTable, deps: [{ token: i0.Injector }, { token: i1$3.UntypedFormBuilder }, { token: PropertyEntityValidator }, { token: ENVIRONMENT }], target: i0.ɵɵFactoryTarget.Component });
28220
28406
  AppPropertiesTable.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: AppPropertiesTable, selector: "app-properties-table", inputs: { definitions: "definitions", showToolbar: "showToolbar" }, providers: [
28221
28407
  { provide: PropertyEntityValidator, useClass: PropertyEntityValidator }
28222
- ], viewQueries: [{ propertyName: "filterExpansionPanel", first: true, predicate: MatExpansionPanel, descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<mat-toolbar *ngIf=\"showToolbar\" [class.expanded]=\"filterExpansionPanel.expanded\">\n\n <!-- Refresh -->\n <button mat-icon-button *ngIf=\"!mobile\"\n [title]=\"'COMMON.BTN_REFRESH'|translate\"\n (click)=\"onRefresh.emit()\">\n <mat-icon>refresh</mat-icon>\n </button>\n\n <ion-item *ngIf=\"!mobile && error; let error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" [innerHTML]=\"error|translate\"></ion-label>\n </ion-item>\n\n <div class=\"toolbar-spacer\"></div>\n\n <!-- Reset filter -->\n <button mat-icon-button (click)=\"resetFilter()\"\n *ngIf=\"filterCriteriaCount\">\n <mat-icon color=\"accent\">filter_list_alt</mat-icon>\n <mat-icon class=\"icon-secondary\" style=\"left: 16px; top: 5px; font-weight: bold;\">close</mat-icon>\n </button>\n\n <!-- Show filter -->\n <button mat-icon-button (click)=\"filterExpansionPanel.toggle()\">\n <mat-icon [matBadge]=\"filterCriteriaCount\"\n [matBadgeHidden]=\"filterIsEmpty\"\n matBadgeColor=\"accent\"\n matBadgeSize=\"small\"\n matBadgePosition=\"above after\">filter_list_alt\n </mat-icon>\n </button>\n\n</mat-toolbar>\n\n<ion-content class=\"ion-no-padding\">\n\n <!-- search -->\n <mat-expansion-panel #filterExpansionPanel\n class=\"filter-panel\"\n [class.ion-no-padding]=\"mobile\"\n [class.filter-panel-floating]=\"filterPanelFloating\">\n <form class=\"form-container\" [formGroup]=\"filterForm\" (ngSubmit)=\"onRefresh.emit()\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <!-- search -->\n <mat-form-field>\n <input matInput [placeholder]=\"'SETTINGS.FILTER.SEARCH'|translate\" formControlName=\"searchText\">\n\n <button mat-icon-button matSuffix tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearControlValue($event, filterForm.controls.searchText)\"\n [hidden]=\"filterForm.controls.searchText.disabled || !filterForm.controls.searchText.value\">\n <mat-icon>close</mat-icon>\n </button>\n </mat-form-field>\n </ion-col>\n\n <ion-col>\n <mat-boolean-field\n [style]=\"'checkbox'\"\n formControlName=\"notDefaultOnly\"\n [placeholder]=\"'SETTINGS.FILTER.ONLY_NOT_DEFAULT' | translate\"\n floatLabel=\"always\"\n ></mat-boolean-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n <mat-action-row>\n <!-- Counter -->\n <ion-label [hidden]=\"(loadingSubject|async) || filterForm.dirty\"\n [color]=\"empty && 'danger'\"\n class=\"ion-padding\">\n {{ (totalRowCount ? 'COMMON.RESULT_COUNT' : 'COMMON.NO_RESULT') | translate: {\n count: (totalRowCount |\n numberFormat)\n } }}\n </ion-label>\n\n <div class=\"toolbar-spacer\"></div>\n\n <button mat-icon-button color=\"accent\"\n *ngIf=\"filterPanelFloating\"\n (click)=\"toggleFilterPanelFloating()\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(filterPanelFloating ? 'COMMON.BTN_EXPAND' : 'COMMON.BTN_HIDE') |translate\">\n <mat-icon><span style=\"transform: rotate(90deg);\">{{filterPanelFloating ? '&#xbb;' : '&#xab;'}}</span></mat-icon>\n </button>\n\n <!-- Close panel -->\n <ion-button mat-button fill=\"clear\" color=\"dark\"\n (click)=\"closeFilterPanel()\"\n [disabled]=\"loadingSubject|async\">\n <ion-text translate>COMMON.BTN_CLOSE</ion-text>\n </ion-button>\n\n <!-- Search button -->\n <ion-button mat-button\n [color]=\"filterForm.dirty ? 'tertiary' : 'dark'\"\n [fill]=\"filterForm.dirty ? 'solid' : 'clear'\"\n (click)=\"applyFilterAndClosePanel($event)\"\n [disabled]=\"loadingSubject|async\">\n <ion-text translate>COMMON.BTN_APPLY</ion-text>\n </ion-button>\n\n </mat-action-row>\n </mat-expansion-panel>\n\n <ion-refresher slot=\"fixed\" *ngIf=\"mobile\" (ionRefresh)=\"doRefresh($event)\">\n <ion-refresher-content></ion-refresher-content>\n </ion-refresher>\n\n <!-- table -->\n <div class=\"table-container\">\n <table #table mat-table matSort matSortDisableClear\n [dataSource]=\"dataSource\"\n [matSortActive]=\"defaultSortBy\"\n [matSortDirection]=\"defaultSortDirection\"\n [trackBy]=\"trackByFn\">\n\n <ng-container matColumnDef=\"select\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"true\"></th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"true\"></td>\n </ng-container>\n\n <!-- id column (hidden) -->\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!debug\"></th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!debug\">\n {{ row.currentData.id }}\n </td>\n </ng-container>\n\n <!-- definition column -->\n <ng-container matColumnDef=\"definition\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label>{{ i18nColumnPrefix + 'DEFINITION' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n<!-- <mat-autocomplete-field-->\n<!-- *ngIf=\"row.id === -1; else readOnlyDefinition\"-->\n<!-- floatLabel=\"never\"-->\n<!-- [formControl]=\"row.validator | formGetControl : 'definition'\"-->\n<!-- [config]=\"autocompleteFields.definition\"-->\n<!-- [required]=\"true\"-->\n<!-- ></mat-autocomplete-field>-->\n<!-- <ng-template #readOnlyDefinition>-->\n {{ row.currentData | propertyGet : 'definition.translatedName' }}\n<!-- </ng-template>-->\n </td>\n </ng-container>\n\n <!-- value column -->\n <ng-container matColumnDef=\"value\">\n <th mat-header-cell *matHeaderCellDef>\n <ion-label>{{ i18nColumnPrefix + 'VALUE' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n <app-form-field\n *ngIf=\"row.validator | formGetValue: 'id'\"\n floatLabel=\"never\"\n [definition]=\"definitionsMapByKey[row.validator | formGetValue: 'id']\"\n [formControl]=\"row.validator | formGetControl:'value'\"\n [placeholder]=\"i18nColumnPrefix + 'VALUE' | translate\"\n [required]=\"true\">\n </app-form-field>\n </td>\n </ng-container>\n\n <!-- Actions buttons column -->\n <app-actions-column\n [stickyEnd]=\"true\"\n [canCancel]=\"false\"\n [canDelete]=\"false\"\n [dirtyIcon]=\"false\"\n [cellTemplate]=\"cellInjection\">\n\n <!-- cell injection-->\n <ng-template #cellInjection let-row>\n <button type=\"button\" mat-icon-button\n *ngIf=\"!mobile && !disabled && (row.validator | formGetValue : 'value') !== (row.validator | formGetValue : 'definition').defaultValue\"\n [class.visible-hover-row]=\"!mobile\"\n [tabindex]=\"-1\"\n [title]=\"'SETTINGS.BTN_RESET_PROPERTY'|translate\"\n (click)=\"resetProperty($event, row)\">\n <mat-icon>undo</mat-icon>\n </button>\n\n </ng-template>\n\n </app-actions-column>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"\n [class.mat-row-selected]=\"row.editing\"\n [class.mat-row-error]=\"row.validator?.invalid\"\n [class.mat-row-dirty]=\"row.validator?.dirty\"\n (click)=\"clickRow($event, row)\"\n (keydown.escape)=\"escapeEditingRow($event)\"\n [cdkTrapFocus]=\"row.validator?.invalid\"></tr>\n </table>\n\n <ng-container *ngIf=\"loadingSubject|async; else noResult\">\n <ion-item>\n <ion-skeleton-text animated></ion-skeleton-text>\n </ion-item>\n </ng-container>\n\n <ng-template #noResult>\n <ion-item *ngIf=\"totalRowCount === 0\">\n <ion-text color=\"danger\" class=\"text-italic\" translate>COMMON.NO_RESULT</ion-text>\n </ion-item>\n </ng-template>\n\n </div>\n</ion-content>\n", styles: [""], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i1.IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: i1.IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: i1.IonRow, selector: "ion-row" }, { kind: "component", type: i1.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$2.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i6$5.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i6$5.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i6$5.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i6$5.MatColumnDef, selector: "[matColumnDef]", inputs: ["sticky", "matColumnDef"] }, { kind: "directive", type: i6$5.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i6$5.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i6$5.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i6$5.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i6$5.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i6$5.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i7$3.MatSort, selector: "[matSort]", inputs: ["matSortDisabled", "matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i7$3.MatSortHeader, selector: "[mat-sort-header]", inputs: ["disabled", "mat-sort-header", "arrowPosition", "start", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "component", type: i6$1.MatFormField, selector: "mat-form-field", inputs: ["color", "appearance", "hideRequiredMarker", "hintLabel", "floatLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6$1.MatSuffix, selector: "[matSuffix]" }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i9$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["disabled", "expanded", "hideToggle", "togglePosition"], outputs: ["opened", "closed", "expandedChange", "afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "directive", type: i9$1.MatExpansionPanelActionRow, selector: "mat-action-row" }, { kind: "component", type: i8$1.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "directive", type: i6$6.MatBadge, selector: "[matBadge]", inputs: ["matBadgeDisabled", "matBadgeColor", "matBadgeOverlap", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "directive", type: i1$4.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: MatBooleanField, selector: "mat-boolean-field", inputs: ["disabled", "formControl", "formControlName", "placeholder", "floatLabel", "appearance", "readonly", "required", "compact", "style", "buttonsColCount", "class", "yesLabel", "noLabel", "showButtonIcons", "yesIcon", "noIcon", "tabindex", "value"], outputs: ["keyup.enter", "onBlur"] }, { kind: "component", type: AppFormField, selector: "app-form-field", inputs: ["definition", "required", "readonly", "disabled", "formControl", "formControlName", "placeholder", "compact", "floatLabel", "tabindex", "autofocus", "clearable", "chipColor", "debug", "class"], outputs: ["keyup.enter"] }, { kind: "component", type: ActionsColumnComponent, selector: "app-actions-column", inputs: ["stickyEnd", "canCancel", "canConfirm", "canDelete", "canBackward", "canForward", "canConfirmAndAdd", "dirtyIcon", "optionsTitle", "class", "cellTemplate"], outputs: ["optionsClick", "cancelOrDeleteClick", "confirmEditCreateClick", "confirmAndAddClick", "backward", "forward"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: NumberFormatPipe, name: "numberFormat" }, { kind: "pipe", type: FormGetControlPipe, name: "formGetControl" }, { kind: "pipe", type: FormGetValuePipe, name: "formGetValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
28408
+ ], viewQueries: [{ propertyName: "filterExpansionPanel", first: true, predicate: MatExpansionPanel, descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<mat-toolbar *ngIf=\"showToolbar\" [class.expanded]=\"filterExpansionPanel.expanded\">\n\n <!-- Refresh -->\n <button mat-icon-button *ngIf=\"!mobile\"\n [title]=\"'COMMON.BTN_REFRESH'|translate\"\n (click)=\"onRefresh.emit()\">\n <mat-icon>refresh</mat-icon>\n </button>\n\n <ion-item *ngIf=\"!mobile && error; let error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" [innerHTML]=\"error|translate\"></ion-label>\n </ion-item>\n\n <div class=\"toolbar-spacer\"></div>\n\n <!-- Reset filter -->\n <button mat-icon-button (click)=\"resetFilter()\"\n *ngIf=\"filterCriteriaCount\">\n <mat-icon color=\"accent\">filter_list_alt</mat-icon>\n <mat-icon class=\"icon-secondary\" style=\"left: 16px; top: 5px; font-weight: bold;\">close</mat-icon>\n </button>\n\n <!-- Show filter -->\n <button mat-icon-button (click)=\"filterExpansionPanel.toggle()\">\n <mat-icon [matBadge]=\"filterCriteriaCount\"\n [matBadgeHidden]=\"filterIsEmpty\"\n matBadgeColor=\"accent\"\n matBadgeSize=\"small\"\n matBadgePosition=\"above after\">filter_list_alt\n </mat-icon>\n </button>\n\n</mat-toolbar>\n\n<ion-content class=\"ion-no-padding\">\n\n <!-- search -->\n <mat-expansion-panel #filterExpansionPanel\n class=\"filter-panel\"\n [class.ion-no-padding]=\"mobile\"\n [class.filter-panel-floating]=\"filterPanelFloating\">\n <form class=\"form-container\" [formGroup]=\"filterForm\" (ngSubmit)=\"onRefresh.emit()\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <!-- search -->\n <mat-form-field>\n <input matInput [placeholder]=\"'SETTINGS.FILTER.SEARCH'|translate\" formControlName=\"searchText\">\n\n <button mat-icon-button matSuffix tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearControlValue($event, filterForm.controls.searchText)\"\n [hidden]=\"filterForm.controls.searchText.disabled || !filterForm.controls.searchText.value\">\n <mat-icon>close</mat-icon>\n </button>\n </mat-form-field>\n </ion-col>\n\n <ion-col>\n <mat-boolean-field\n [style]=\"'checkbox'\"\n formControlName=\"notDefaultOnly\"\n [placeholder]=\"'SETTINGS.FILTER.ONLY_NOT_DEFAULT' | translate\"\n floatLabel=\"always\"\n ></mat-boolean-field>\n </ion-col>\n </ion-row>\n </ion-grid>\n </form>\n\n <mat-action-row>\n <!-- Counter -->\n <ion-label [hidden]=\"(loadingSubject|async) || filterForm.dirty\"\n [color]=\"empty && 'danger'\"\n class=\"ion-padding\">\n {{ (totalRowCount ? 'COMMON.RESULT_COUNT' : 'COMMON.NO_RESULT') | translate: {\n count: (totalRowCount |\n numberFormat)\n } }}\n </ion-label>\n\n <div class=\"toolbar-spacer\"></div>\n\n <button mat-icon-button color=\"accent\"\n *ngIf=\"filterPanelFloating\"\n (click)=\"toggleFilterPanelFloating()\"\n class=\"hidden-xs hidden-sm hidden-md\"\n [title]=\"(filterPanelFloating ? 'COMMON.BTN_EXPAND' : 'COMMON.BTN_HIDE') |translate\">\n <mat-icon><span style=\"transform: rotate(90deg);\">{{filterPanelFloating ? '&#xbb;' : '&#xab;'}}</span></mat-icon>\n </button>\n\n <!-- Close panel -->\n <ion-button mat-button fill=\"clear\" color=\"dark\"\n (click)=\"closeFilterPanel()\"\n [disabled]=\"loadingSubject|async\">\n <ion-text translate>COMMON.BTN_CLOSE</ion-text>\n </ion-button>\n\n <!-- Search button -->\n <ion-button mat-button\n [color]=\"filterForm.dirty ? 'tertiary' : 'dark'\"\n [fill]=\"filterForm.dirty ? 'solid' : 'clear'\"\n (click)=\"applyFilterAndClosePanel($event)\"\n [disabled]=\"loadingSubject|async\">\n <ion-text translate>COMMON.BTN_APPLY</ion-text>\n </ion-button>\n\n </mat-action-row>\n </mat-expansion-panel>\n\n <ion-refresher slot=\"fixed\" *ngIf=\"mobile\" (ionRefresh)=\"doRefresh($event)\">\n <ion-refresher-content></ion-refresher-content>\n </ion-refresher>\n\n <!-- table -->\n <div class=\"table-container\">\n <table #table mat-table matSort matSortDisableClear\n [dataSource]=\"dataSource\"\n [matSortActive]=\"defaultSortBy\"\n [matSortDirection]=\"defaultSortDirection\"\n [trackBy]=\"trackByFn\">\n\n <ng-container matColumnDef=\"select\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"true\"></th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"true\"></td>\n </ng-container>\n\n <!-- id column (hidden) -->\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!debug\"></th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!debug\">\n {{ row.currentData.id }}\n </td>\n </ng-container>\n\n <!-- definition column -->\n <ng-container matColumnDef=\"definition\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label>{{ i18nColumnPrefix + 'DEFINITION' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n<!-- <mat-autocomplete-field-->\n<!-- *ngIf=\"row.id === -1; else readOnlyDefinition\"-->\n<!-- floatLabel=\"never\"-->\n<!-- [formControl]=\"row.validator | formGetControl : 'definition'\"-->\n<!-- [config]=\"autocompleteFields.definition\"-->\n<!-- [required]=\"true\"-->\n<!-- ></mat-autocomplete-field>-->\n<!-- <ng-template #readOnlyDefinition>-->\n {{ row.currentData | propertyGet : 'definition.translatedName' }}\n<!-- </ng-template>-->\n </td>\n </ng-container>\n\n <!-- value column -->\n <ng-container matColumnDef=\"value\">\n <th mat-header-cell *matHeaderCellDef>\n <ion-label>{{ i18nColumnPrefix + 'VALUE' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n <app-form-field\n *ngIf=\"row.validator | formGetValue: 'id'\"\n floatLabel=\"never\"\n [definition]=\"definitionsMapByKey[row.validator | formGetValue: 'id']\"\n [formControl]=\"row.validator | formGetControl:'value'\"\n [placeholder]=\"i18nColumnPrefix + 'VALUE' | translate\"\n [required]=\"true\">\n </app-form-field>\n </td>\n </ng-container>\n\n <!-- Actions buttons column -->\n <app-actions-column\n [stickyEnd]=\"true\"\n [canCancel]=\"false\"\n [canDelete]=\"false\"\n [dirtyIcon]=\"false\"\n [cellTemplate]=\"cellInjection\">\n\n <!-- cell injection-->\n <ng-template #cellInjection let-row>\n <button type=\"button\" mat-icon-button\n *ngIf=\"!mobile && !disabled && (row.validator | formGetValue : 'value') !== (row.validator | formGetValue : 'definition').defaultValue\"\n [class.visible-hover-row]=\"!mobile\"\n [tabindex]=\"-1\"\n [title]=\"'SETTINGS.BTN_RESET_PROPERTY'|translate\"\n (click)=\"resetProperty($event, row)\">\n <mat-icon>undo</mat-icon>\n </button>\n\n </ng-template>\n\n </app-actions-column>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"\n [class.mat-row-selected]=\"row.editing\"\n [class.mat-row-error]=\"row.validator?.invalid\"\n [class.mat-row-dirty]=\"row.validator?.dirty\"\n (click)=\"clickRow($event, row)\"\n (keydown.escape)=\"escapeEditingRow($event)\"\n [cdkTrapFocus]=\"row.validator?.invalid\"></tr>\n </table>\n\n <ng-container *ngIf=\"loadingSubject|async; else noResult\">\n <ion-item>\n <ion-skeleton-text animated></ion-skeleton-text>\n </ion-item>\n </ng-container>\n\n <ng-template #noResult>\n <ion-item *ngIf=\"totalRowCount === 0\">\n <ion-text color=\"danger\" class=\"text-italic\" translate>COMMON.NO_RESULT</ion-text>\n </ion-item>\n </ng-template>\n\n </div>\n</ion-content>\n", styles: [""], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i1.IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: i1.IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: i1.IonRow, selector: "ion-row" }, { kind: "component", type: i1.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$2.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i6$5.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i6$5.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i6$5.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i6$5.MatColumnDef, selector: "[matColumnDef]", inputs: ["sticky", "matColumnDef"] }, { kind: "directive", type: i6$5.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i6$5.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i6$5.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i6$5.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i6$5.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i6$5.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i7$3.MatSort, selector: "[matSort]", inputs: ["matSortDisabled", "matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i7$3.MatSortHeader, selector: "[mat-sort-header]", inputs: ["disabled", "mat-sort-header", "arrowPosition", "start", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "component", type: i6$1.MatFormField, selector: "mat-form-field", inputs: ["color", "appearance", "hideRequiredMarker", "hintLabel", "floatLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6$1.MatSuffix, selector: "[matSuffix]" }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i9$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["disabled", "expanded", "hideToggle", "togglePosition"], outputs: ["opened", "closed", "expandedChange", "afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "directive", type: i9$1.MatExpansionPanelActionRow, selector: "mat-action-row" }, { kind: "component", type: i8$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "directive", type: i6$6.MatBadge, selector: "[matBadge]", inputs: ["matBadgeDisabled", "matBadgeColor", "matBadgeOverlap", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "directive", type: i1$4.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: MatBooleanField, selector: "mat-boolean-field", inputs: ["disabled", "formControl", "formControlName", "placeholder", "floatLabel", "appearance", "readonly", "required", "compact", "style", "buttonsColCount", "class", "yesLabel", "noLabel", "showButtonIcons", "yesIcon", "noIcon", "tabindex", "value"], outputs: ["keyup.enter", "onBlur"] }, { kind: "component", type: AppFormField, selector: "app-form-field", inputs: ["definition", "required", "readonly", "disabled", "formControl", "formControlName", "placeholder", "compact", "floatLabel", "tabindex", "autofocus", "clearable", "chipColor", "debug", "class"], outputs: ["keyup.enter"] }, { kind: "component", type: ActionsColumnComponent, selector: "app-actions-column", inputs: ["stickyEnd", "canCancel", "canConfirm", "canDelete", "canBackward", "canForward", "canConfirmAndAdd", "dirtyIcon", "optionsTitle", "class", "cellTemplate"], outputs: ["optionsClick", "cancelOrDeleteClick", "confirmEditCreateClick", "confirmAndAddClick", "backward", "forward"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: NumberFormatPipe, name: "numberFormat" }, { kind: "pipe", type: FormGetControlPipe, name: "formGetControl" }, { kind: "pipe", type: FormGetValuePipe, name: "formGetValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
28223
28409
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AppPropertiesTable, decorators: [{
28224
28410
  type: Component,
28225
28411
  args: [{ selector: 'app-properties-table', providers: [
@@ -29990,7 +30176,7 @@ class UserTokenTable extends AppInMemoryTable {
29990
30176
  UserTokenTable.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: UserTokenTable, deps: [{ token: i0.Injector }, { token: i1$3.UntypedFormBuilder }, { token: i2$6.ValidatorService }, { token: ENVIRONMENT }, { token: APP_USER_TOKEN_SCOPES, optional: true }], target: i0.ɵɵFactoryTarget.Component });
29991
30177
  UserTokenTable.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: UserTokenTable, selector: "app-user-token-table", inputs: { useSticky: "useSticky" }, providers: [
29992
30178
  { provide: ValidatorService, useClass: UserTokenValidatorService }
29993
- ], usesInheritance: true, ngImport: i0, template: "<mat-toolbar>\n\n <!-- Refresh -->\n <button mat-icon-button *ngIf=\"!mobile\"\n [title]=\"'COMMON.BTN_REFRESH'|translate\"\n (click)=\"onRefresh.emit()\">\n <mat-icon>refresh</mat-icon>\n </button>\n\n <ion-item *ngIf=\"!mobile && error; let error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" [innerHTML]=\"error|translate\"></ion-label>\n </ion-item>\n\n <div class=\"toolbar-spacer\"></div>\n\n <ng-container *ngIf=\"selection.isEmpty(); else hasSelection\">\n\n <button mat-icon-button\n *ngIf=\"canEdit && !mobile\"\n [title]=\"'COMMON.BTN_ADD'|translate\"\n (click)=\"addToken($event)\">\n <mat-icon>add</mat-icon>\n </button>\n\n </ng-container>\n\n <ng-template #hasSelection>\n\n <button mat-icon-button\n [title]=\"'COMMON.BTN_DELETE' | translate\"\n (click)=\"deleteSelection($event)\">\n <mat-icon>delete_outline</mat-icon>\n </button>\n\n </ng-template>\n\n</mat-toolbar>\n\n<ion-content class=\"ion-no-padding\">\n\n <ion-refresher slot=\"fixed\" *ngIf=\"mobile\" (ionRefresh)=\"doRefresh($event)\">\n <ion-refresher-content></ion-refresher-content>\n </ion-refresher>\n\n <!-- table -->\n <div class=\"table-container\">\n <table #table mat-table matSort matSortDisableClear\n [dataSource]=\"dataSource\"\n [matSortActive]=\"defaultSortBy\"\n [matSortDirection]=\"defaultSortDirection\"\n [trackBy]=\"trackByFn\">\n\n <ng-container matColumnDef=\"select\" [sticky]=\"useSticky\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!inlineEdition\">\n <mat-checkbox (change)=\"$event ? masterToggle() : null\" [checked]=\"selection.hasValue() && isAllSelected()\"\n [indeterminate]=\"selection.hasValue() && !isAllSelected()\">\n </mat-checkbox>\n </th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!inlineEdition\">\n <mat-checkbox (click)=\"$event.stopPropagation()\" (change)=\"$event ? selection.toggle(row) : null\" [checked]=\"selection.isSelected(row)\">\n </mat-checkbox>\n </td>\n </ng-container>\n\n <!-- Id Column -->\n <ng-container matColumnDef=\"id\" [sticky]=\"useSticky\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label>#</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\">{{ row.currentData.id }}</td>\n </ng-container>\n\n <!-- name column -->\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label>{{ i18nColumnPrefix + 'NAME' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n {{ (row.currentData | propertyGet : 'name') || (i18nColumnPrefix + 'UNKNOWN' | translate) }}\n </td>\n </ng-container>\n\n<!-- <ng-container matColumnDef=\"flags\">-->\n<!-- <th mat-header-cell *matHeaderCellDef mat-sort-header>-->\n<!-- <ion-label>{{ i18nColumnPrefix + 'FLAGS' | translate }}</ion-label>-->\n<!-- </th>-->\n<!-- <td mat-cell *matCellDef=\"let row\">-->\n<!-- {{ row.currentData | propertyGet : 'flags' }}-->\n<!-- </td>-->\n<!-- </ng-container>-->\n\n <ng-container matColumnDef=\"scopes\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label>{{ i18nColumnPrefix + 'SCOPES' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n {{ display(row.currentData | propertyGet : 'scopes') }}\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"creationDate\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header class=\"mat-cell-date-time\">\n <ion-label>{{ i18nColumnPrefix + 'CREATION_DATE' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"mat-cell-date-time\">\n {{ row.currentData | propertyGet : 'creationDate' | dateFormat : {time: true} }}\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"expirationDate\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header class=\"mat-cell-date-time\">\n <ion-label>{{ i18nColumnPrefix + 'EXPIRATION_DATE' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"mat-cell-date-time\">\n {{ row.currentData | propertyGet : 'expirationDate' | dateFormat : {time: true} }}\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"lastUsedDate\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header class=\"mat-cell-date-time\">\n <ion-label>{{ i18nColumnPrefix + 'LAST_USED_DATE' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"mat-cell-date-time\">\n {{ row.currentData | propertyGet : 'lastUsedDate' | dateFormat : {time: true} }}\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"updateDate\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header class=\"mat-cell-date-time\">\n <ion-label>{{ i18nColumnPrefix + 'UPDATE_DATE' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"mat-cell-date-time\">\n {{ row.currentData | propertyGet : 'updateDate' | dateFormat : {time: true} }}\n </td>\n </ng-container>\n\n <!-- Actions buttons column -->\n <app-actions-column [stickyEnd]=\"useSticky\"\n (optionsClick)=\"openSelectColumnsModal($event)\"\n (cancelOrDeleteClick)=\"cancelOrDelete($event.event, $event.row)\"\n (confirmAndAddClick)=\"confirmAndAdd($event.event, $event.row)\"\n (backward)=\"confirmAndBackward($event.event, $event.row)\"\n (forward)=\"confirmAndForward($event.event, $event.row)\"\n [canCancel]=\"false\">\n </app-actions-column>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"\n [class.mat-row-selected]=\"row.editing\"\n [class.mat-row-error]=\"row.validator?.invalid\"\n [class.mat-row-dirty]=\"row.validator?.dirty\"\n (click)=\"clickRow($event, row)\"\n (keydown.escape)=\"escapeEditingRow($event)\"\n [cdkTrapFocus]=\"row.validator?.invalid\"></tr>\n </table>\n\n <ng-container *ngIf=\"loadingSubject|async; else noResult\">\n <ion-item>\n <ion-skeleton-text animated></ion-skeleton-text>\n </ion-item>\n </ng-container>\n\n <ng-template #noResult>\n <ion-item *ngIf=\"totalRowCount === 0\">\n <ion-text color=\"danger\" class=\"text-italic\" translate>COMMON.NO_RESULT</ion-text>\n </ion-item>\n </ng-template>\n\n </div>\n</ion-content>\n\n<ion-fab slot=\"fixed\" vertical=\"bottom\" horizontal=\"end\" *ngIf=\"mobile\">\n <ion-fab-button color=\"tertiary\" (click)=\"addToken($event)\">\n <ion-icon name=\"add\"></ion-icon>\n </ion-fab-button>\n</ion-fab>\n", styles: [".mat-cell-date-time{width:180px}.mat-column-id{width:90px}\n"], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonFab, selector: "ion-fab", inputs: ["activated", "edge", "horizontal", "vertical"] }, { kind: "component", type: i1.IonFabButton, selector: "ion-fab-button", inputs: ["activated", "closeIcon", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "show", "size", "target", "translucent", "type"] }, { kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i1.IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: i1.IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: i1.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$2.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i6$5.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i6$5.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i6$5.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i6$5.MatColumnDef, selector: "[matColumnDef]", inputs: ["sticky", "matColumnDef"] }, { kind: "directive", type: i6$5.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i6$5.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i6$5.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i6$5.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i6$5.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i6$5.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i7$3.MatSort, selector: "[matSort]", inputs: ["matSortDisabled", "matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i7$3.MatSortHeader, selector: "[mat-sort-header]", inputs: ["disabled", "mat-sort-header", "arrowPosition", "start", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "component", type: i6$2.MatCheckbox, selector: "mat-checkbox", inputs: ["disableRipple", "color", "tabIndex"], exportAs: ["matCheckbox"] }, { kind: "component", type: i8$1.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "directive", type: i1$4.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: ActionsColumnComponent, selector: "app-actions-column", inputs: ["stickyEnd", "canCancel", "canConfirm", "canDelete", "canBackward", "canForward", "canConfirmAndAdd", "dirtyIcon", "optionsTitle", "class", "cellTemplate"], outputs: ["optionsClick", "cancelOrDeleteClick", "confirmEditCreateClick", "confirmAndAddClick", "backward", "forward"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: DateFormatPipe, name: "dateFormat" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
30179
+ ], usesInheritance: true, ngImport: i0, template: "<mat-toolbar>\n\n <!-- Refresh -->\n <button mat-icon-button *ngIf=\"!mobile\"\n [title]=\"'COMMON.BTN_REFRESH'|translate\"\n (click)=\"onRefresh.emit()\">\n <mat-icon>refresh</mat-icon>\n </button>\n\n <ion-item *ngIf=\"!mobile && error; let error\" lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" [innerHTML]=\"error|translate\"></ion-label>\n </ion-item>\n\n <div class=\"toolbar-spacer\"></div>\n\n <ng-container *ngIf=\"selection.isEmpty(); else hasSelection\">\n\n <button mat-icon-button\n *ngIf=\"canEdit && !mobile\"\n [title]=\"'COMMON.BTN_ADD'|translate\"\n (click)=\"addToken($event)\">\n <mat-icon>add</mat-icon>\n </button>\n\n </ng-container>\n\n <ng-template #hasSelection>\n\n <button mat-icon-button\n [title]=\"'COMMON.BTN_DELETE' | translate\"\n (click)=\"deleteSelection($event)\">\n <mat-icon>delete_outline</mat-icon>\n </button>\n\n </ng-template>\n\n</mat-toolbar>\n\n<ion-content class=\"ion-no-padding\">\n\n <ion-refresher slot=\"fixed\" *ngIf=\"mobile\" (ionRefresh)=\"doRefresh($event)\">\n <ion-refresher-content></ion-refresher-content>\n </ion-refresher>\n\n <!-- table -->\n <div class=\"table-container\">\n <table #table mat-table matSort matSortDisableClear\n [dataSource]=\"dataSource\"\n [matSortActive]=\"defaultSortBy\"\n [matSortDirection]=\"defaultSortDirection\"\n [trackBy]=\"trackByFn\">\n\n <ng-container matColumnDef=\"select\" [sticky]=\"useSticky\">\n <th mat-header-cell *matHeaderCellDef [class.cdk-visually-hidden]=\"!inlineEdition\">\n <mat-checkbox (change)=\"$event ? masterToggle() : null\" [checked]=\"selection.hasValue() && isAllSelected()\"\n [indeterminate]=\"selection.hasValue() && !isAllSelected()\">\n </mat-checkbox>\n </th>\n <td mat-cell *matCellDef=\"let row\" [class.cdk-visually-hidden]=\"!inlineEdition\">\n <mat-checkbox (click)=\"$event.stopPropagation()\" (change)=\"$event ? selection.toggle(row) : null\" [checked]=\"selection.isSelected(row)\">\n </mat-checkbox>\n </td>\n </ng-container>\n\n <!-- Id Column -->\n <ng-container matColumnDef=\"id\" [sticky]=\"useSticky\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label>#</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\">{{ row.currentData.id }}</td>\n </ng-container>\n\n <!-- name column -->\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label>{{ i18nColumnPrefix + 'NAME' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n {{ (row.currentData | propertyGet : 'name') || (i18nColumnPrefix + 'UNKNOWN' | translate) }}\n </td>\n </ng-container>\n\n<!-- <ng-container matColumnDef=\"flags\">-->\n<!-- <th mat-header-cell *matHeaderCellDef mat-sort-header>-->\n<!-- <ion-label>{{ i18nColumnPrefix + 'FLAGS' | translate }}</ion-label>-->\n<!-- </th>-->\n<!-- <td mat-cell *matCellDef=\"let row\">-->\n<!-- {{ row.currentData | propertyGet : 'flags' }}-->\n<!-- </td>-->\n<!-- </ng-container>-->\n\n <ng-container matColumnDef=\"scopes\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n <ion-label>{{ i18nColumnPrefix + 'SCOPES' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n {{ display(row.currentData | propertyGet : 'scopes') }}\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"creationDate\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header class=\"mat-cell-date-time\">\n <ion-label>{{ i18nColumnPrefix + 'CREATION_DATE' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"mat-cell-date-time\">\n {{ row.currentData | propertyGet : 'creationDate' | dateFormat : {time: true} }}\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"expirationDate\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header class=\"mat-cell-date-time\">\n <ion-label>{{ i18nColumnPrefix + 'EXPIRATION_DATE' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"mat-cell-date-time\">\n {{ row.currentData | propertyGet : 'expirationDate' | dateFormat : {time: true} }}\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"lastUsedDate\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header class=\"mat-cell-date-time\">\n <ion-label>{{ i18nColumnPrefix + 'LAST_USED_DATE' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"mat-cell-date-time\">\n {{ row.currentData | propertyGet : 'lastUsedDate' | dateFormat : {time: true} }}\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"updateDate\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header class=\"mat-cell-date-time\">\n <ion-label>{{ i18nColumnPrefix + 'UPDATE_DATE' | translate }}</ion-label>\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"mat-cell-date-time\">\n {{ row.currentData | propertyGet : 'updateDate' | dateFormat : {time: true} }}\n </td>\n </ng-container>\n\n <!-- Actions buttons column -->\n <app-actions-column [stickyEnd]=\"useSticky\"\n (optionsClick)=\"openSelectColumnsModal($event)\"\n (cancelOrDeleteClick)=\"cancelOrDelete($event.event, $event.row)\"\n (confirmAndAddClick)=\"confirmAndAdd($event.event, $event.row)\"\n (backward)=\"confirmAndBackward($event.event, $event.row)\"\n (forward)=\"confirmAndForward($event.event, $event.row)\"\n [canCancel]=\"false\">\n </app-actions-column>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"\n [class.mat-row-selected]=\"row.editing\"\n [class.mat-row-error]=\"row.validator?.invalid\"\n [class.mat-row-dirty]=\"row.validator?.dirty\"\n (click)=\"clickRow($event, row)\"\n (keydown.escape)=\"escapeEditingRow($event)\"\n [cdkTrapFocus]=\"row.validator?.invalid\"></tr>\n </table>\n\n <ng-container *ngIf=\"loadingSubject|async; else noResult\">\n <ion-item>\n <ion-skeleton-text animated></ion-skeleton-text>\n </ion-item>\n </ng-container>\n\n <ng-template #noResult>\n <ion-item *ngIf=\"totalRowCount === 0\">\n <ion-text color=\"danger\" class=\"text-italic\" translate>COMMON.NO_RESULT</ion-text>\n </ion-item>\n </ng-template>\n\n </div>\n</ion-content>\n\n<ion-fab slot=\"fixed\" vertical=\"bottom\" horizontal=\"end\" *ngIf=\"mobile\">\n <ion-fab-button color=\"tertiary\" (click)=\"addToken($event)\">\n <ion-icon name=\"add\"></ion-icon>\n </ion-fab-button>\n</ion-fab>\n", styles: [".mat-cell-date-time{width:180px}.mat-column-id{width:90px}\n"], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonFab, selector: "ion-fab", inputs: ["activated", "edge", "horizontal", "vertical"] }, { kind: "component", type: i1.IonFabButton, selector: "ion-fab-button", inputs: ["activated", "closeIcon", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "show", "size", "target", "translucent", "type"] }, { kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i1.IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: i1.IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: i1.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "directive", type: i1$2.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i6$5.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i6$5.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i6$5.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i6$5.MatColumnDef, selector: "[matColumnDef]", inputs: ["sticky", "matColumnDef"] }, { kind: "directive", type: i6$5.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i6$5.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i6$5.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i6$5.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i6$5.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i6$5.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i7$3.MatSort, selector: "[matSort]", inputs: ["matSortDisabled", "matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i7$3.MatSortHeader, selector: "[mat-sort-header]", inputs: ["disabled", "mat-sort-header", "arrowPosition", "start", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "component", type: i6$2.MatCheckbox, selector: "mat-checkbox", inputs: ["disableRipple", "color", "tabIndex"], exportAs: ["matCheckbox"] }, { kind: "component", type: i8$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "directive", type: i1$4.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: ActionsColumnComponent, selector: "app-actions-column", inputs: ["stickyEnd", "canCancel", "canConfirm", "canDelete", "canBackward", "canForward", "canConfirmAndAdd", "dirtyIcon", "optionsTitle", "class", "cellTemplate"], outputs: ["optionsClick", "cancelOrDeleteClick", "confirmEditCreateClick", "confirmAndAddClick", "backward", "forward"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: DateFormatPipe, name: "dateFormat" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
29994
30180
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: UserTokenTable, decorators: [{
29995
30181
  type: Component,
29996
30182
  args: [{ selector: 'app-user-token-table', providers: [
@@ -38058,5 +38244,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImpor
38058
38244
  * Generated bundle index. Do not edit.
38059
38245
  */
38060
38246
 
38061
- export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_CONFIG_OPTIONS, APP_DEBUG_DATA_SERVICE, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_FRAGMENTS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_JOB_PROGRESSION_SERVICE, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_LOGGING_SERVICE, APP_MENU_ITEMS, APP_MENU_OPTIONS, APP_NAMED_FILTER_SERVICE, APP_PROGRESS_BAR_SERVICE, APP_SETTINGS_MENU_ITEMS, APP_STORAGE, APP_STORAGE_EXPLORER_PROTECTED_KEYS, APP_TESTING_PAGES, APP_USER_EVENT_LIST_INFINITE_SCROLL_THRESHOLD, APP_USER_EVENT_SERVICE, APP_USER_SETTINGS_OPTIONS, APP_USER_TOKEN_SCOPES, AboutModal, AbstractDateFormat, AbstractNamedFilterService, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, ActionsColumnComponent, AdminModule, AdminRoutingModule, AdminUsersModule, Alerts, AndroidOsEnvironment, AnimationState, AppAboutModalModule, AppAccountModule, AppAsyncTable, AppAuthForm, AppAuthModal, AppAuthModule, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, AppEntityFormModule, AppForm, AppFormArray, AppFormButtonsBarModule, AppFormField, AppFormModule, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHelpModal, AppHomePageModule, AppIconComponent, AppIconModule, AppImageGalleryComponent, AppInMemoryTable, AppInstallUpgradeCard, AppInstallUpgradeCardModule, AppListForm, AppListFormModule, AppLoadingSpinner, AppMenuModule, AppNullForm, AppPropertiesForm, AppPropertiesFormModule, AppPropertiesTable, AppRegisterModule, AppSelectPeerModule, AppSettingsPageModule, AppTabEditor, AppTabEditorOptions, AppTable, AppTableModule, AppTableUtils, AppTextPopoverModule, AppUpdateOfflineModeCard, AppUpdateOfflineModeCardModule, AppValidatorService, AppendToInputDirective, ArrayFilterPipe, ArrayFirstPipe, ArrayIncludesPipe, ArrayJoinPipe, ArrayLengthPipe, ArrayPluckPipe, AsAnyPipe, AsArrayPipe, AsObservablePipe, AudioProvider, AudioTestingModule, AudioTestingPage, AuthGuardService, AutoTitleDirective, AutocompleteTestPage, AutofocusDirective, BadgeDirective, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CellValueChangeListener, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigService, Configuration, CoreModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFormatService, DateFromNowPipe, DateShortTestPage, DateTestPage, DateTimeTestPage, DateUtils, DebugComponent, Department, DepartmentToStringPipe, DragAndDropDirective, DurationPipe, ED25519_SEED_LENGTH, EMPTY_PLACEHOLDER_CHAR, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesAsyncTableDataSource, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, ErrorCodes, EvenPipe, FileResponse, FileService, FileSizePipe, FilesUtils, FormArrayHelper, FormButtonsBarComponent, FormButtonsBarToken, FormErrorTranslatePipe, FormErrorTranslator, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetPipe, FormGetValuePipe, Fragments, GalleryTestPage, GeolocationUtils, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, IMAGE_DEFAULTS, IPosition, ImageAttachment, ImageAttachmentFilter, ImageAttachmentService, ImageGalleryModule, ImageGalleryTestingModule, ImageModule, ImageService, ImagesUtils, InMemoryEntitiesService, IsLoginAccountPipe, IsNilOrBlankPipe, IsNilOrNaNPipe, IsNilPipe, IsNotNilOrBlankPipe, IsNotNilOrNaNPipe, IsNotNilPipe, IsOnDeskPipe, IsOnFieldPipe, JobModule, JobProgression, JobProgressionComponent, JobProgressionIcon, JobProgressionList, JobProgressionService, JobProgressionTestService, JobProgressionTestingPage, JobTestingModule, JobUtils, JsonUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_DEFAULT_MAX_DECIMALS, LAT_LONG_PATTERNS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LogLevel, LogUtils, Logger, LoggingService, LoggingServiceModule, LongitudeFormatPipe, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MOMENT_NO_TIME_PROPERTY, MapGetPipe, MapKeysPipe, MapValuesPipe, MatAutocompleteConfigHolder, MatAutocompleteField, MatAutocompleteFieldUtils, MatBooleanField, MatChipsField, MatColorPipe, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatNumpadComponent, MatNumpadContainerComponent, MatNumpadContent, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItems, MenuOptions, MenuService, Message, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NamedFilter, NamedFilterFilter, NetworkService, NewTokenModal, NgInitDirective, NgVarDirective, NotEmptyArrayPipe, NumberFormatPipe, NumpadDirective, NumpadTestPage, ObservableTestPage, OddPipe, PEER_URL_REGEXP, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, ProgressBarService, ProgressInterceptor, PropertiesFormTestPage, PropertiesFormTestingModule, PropertyEntity, PropertyEntityFilter, PropertyEntityValidator, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialUtils, ReferentialValidatorService, RegisterConfirmPage, RegisterForm, RegisterModal, ResizableComponent, ResizableDirective, ResizableModule, SCRYPT_PARAMS, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_STORAGE_TESTING_PAGES, SHARED_TESTING_PAGES, SOCIAL_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SelectPeerModal, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedBadgeModule, SharedDebugModule, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMatAutocompleteModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatNumpadModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedToolbarModule, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, SocialTestingModule, Software, StartableService, StatusById, StatusIds, StatusList, StorageDrivers, StorageExplorerComponent, StorageExplorerModule, StorageService, StrIncludesPipe, StrLengthPipe, SwipeTestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TableValidatorService, TextForm, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ToStringPipe, ToastTestingModule, ToastTestingPage, Toasts, TokenScope, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UserEventModule, UserEventNotificationIcon, UserEventNotificationList, UserEventTestService, UserEventTestingModule, UserEventTestingPage, UserSettings, UserToken, UserTokenTable, UsersPage, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayGroupBy, arraySize, asInputElement, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, collectByProperty, copyEntity2Form, createPromiseEvent, createPromiseEventEmitter, departmentToString, departmentsToString, disableControls, emitPromiseEvent, entityToString, equals, equalsOrNil, escapeRegExp, fadeInAnimation, fadeInOutAnimation, filterFalse, filterNotNil, filterNumberInput, filterTrue, findParentWithClass, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorShade, getColorTint, getConnectionType, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, hexToRgb, hexToRgbArray, initArrayControlsFromValues, isAndroid, isBlankString, isCapacitor, isControlHasInput, isEmptyArray, isIOS, isInputElement, isInstanceOf, isInt, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isProgressEvent, isResponseEvent, isStartableService, isTouchUi, isWindows, joinProperties, joinPropertiesPath, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, matchMedia, matchUpperCase, mergeLoadResult, mixHex, moveInputCaretToSeparator, noTrailingSlash, notNilOrDefault, nullIfUndefined, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputRange, setCalculatedValue, setPropertyByPath, setTabIndex, sleep, slideInOutAnimation, slideUpDownAnimation, sort, splitById, splitByProperty, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toNotNil, toNumber, trimEmptyToNull, uncapitalizeFirstLetter, undefinedIfNull, updateValueAndValidity, waitFor, waitForFalse, waitForTrue, waitIdle, waitWhilePending };
38247
+ export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_CONFIG_OPTIONS, APP_DEBUG_DATA_SERVICE, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_FRAGMENTS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_JOB_PROGRESSION_SERVICE, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_LOGGING_SERVICE, APP_MENU_ITEMS, APP_MENU_OPTIONS, APP_NAMED_FILTER_SERVICE, APP_PROGRESS_BAR_SERVICE, APP_SETTINGS_MENU_ITEMS, APP_STORAGE, APP_STORAGE_EXPLORER_PROTECTED_KEYS, APP_TESTING_PAGES, APP_USER_EVENT_LIST_INFINITE_SCROLL_THRESHOLD, APP_USER_EVENT_SERVICE, APP_USER_SETTINGS_OPTIONS, APP_USER_TOKEN_SCOPES, AboutModal, AbstractDateFormat, AbstractNamedFilterService, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, ActionsColumnComponent, AdminModule, AdminRoutingModule, AdminUsersModule, Alerts, AndroidOsEnvironment, AnimationState, AppAboutModalModule, AppAccountModule, AppAsyncTable, AppAuthForm, AppAuthModal, AppAuthModule, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, AppEntityFormModule, AppForm, AppFormArray, AppFormButtonsBarModule, AppFormField, AppFormModule, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHelpModal, AppHomePageModule, AppIconComponent, AppIconModule, AppImageGalleryComponent, AppInMemoryTable, AppInstallUpgradeCard, AppInstallUpgradeCardModule, AppListForm, AppListFormModule, AppLoadingSpinner, AppMenuModule, AppNullForm, AppPropertiesForm, AppPropertiesFormModule, AppPropertiesTable, AppRegisterModule, AppSelectPeerModule, AppSettingsPageModule, AppTabEditor, AppTabEditorOptions, AppTable, AppTableModule, AppTableUtils, AppTextPopoverModule, AppUpdateOfflineModeCard, AppUpdateOfflineModeCardModule, AppValidatorService, AppendToInputDirective, ArrayFilterPipe, ArrayFirstPipe, ArrayIncludesPipe, ArrayJoinPipe, ArrayLengthPipe, ArrayPluckPipe, AsAnyPipe, AsArrayPipe, AsObservablePipe, AudioProvider, AudioTestingModule, AudioTestingPage, AuthGuardService, AutoTitleDirective, AutocompleteTestPage, AutofocusDirective, BadgeDirective, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CellValueChangeListener, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigService, Configuration, CoreModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFormatService, DateFromNowPipe, DateShortTestPage, DateTestPage, DateTimeTestPage, DateUtils, DebugComponent, Department, DepartmentToStringPipe, DragAndDropDirective, DurationPipe, ED25519_SEED_LENGTH, EMPTY_PLACEHOLDER_CHAR, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesAsyncTableDataSource, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, ErrorCodes, EvenPipe, FileResponse, FileService, FileSizePipe, FilesUtils, FormArrayHelper, FormButtonsBarComponent, FormButtonsBarToken, FormErrorTranslatePipe, FormErrorTranslator, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetPipe, FormGetValuePipe, Fragments, GalleryTestPage, GeolocationUtils, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, IMAGE_DEFAULTS, IPosition, ImageAttachment, ImageAttachmentFilter, ImageAttachmentService, ImageGalleryModule, ImageGalleryTestingModule, ImageModule, ImageService, ImagesUtils, InMemoryEntitiesService, IsLoginAccountPipe, IsNilOrBlankPipe, IsNilOrNaNPipe, IsNilPipe, IsNotNilOrBlankPipe, IsNotNilOrNaNPipe, IsNotNilPipe, IsOnDeskPipe, IsOnFieldPipe, JobModule, JobProgression, JobProgressionComponent, JobProgressionIcon, JobProgressionList, JobProgressionService, JobProgressionTestService, JobProgressionTestingPage, JobTestingModule, JobUtils, JsonUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_DEFAULT_MAX_DECIMALS, LAT_LONG_PATTERNS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LogLevel, LogUtils, Logger, LoggingService, LoggingServiceModule, LongitudeFormatPipe, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MOMENT_NO_TIME_PROPERTY, MapGetPipe, MapKeysPipe, MapValuesPipe, MatAutocompleteConfigHolder, MatAutocompleteField, MatAutocompleteFieldUtils, MatBooleanField, MatChipsField, MatColorPipe, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatNumpadComponent, MatNumpadContainerComponent, MatNumpadContent, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItems, MenuOptions, MenuService, Message, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NamedFilter, NamedFilterFilter, NamedFilterSelector, NetworkService, NewTokenModal, NgInitDirective, NgVarDirective, NotEmptyArrayPipe, NumberFormatPipe, NumpadDirective, NumpadTestPage, ObservableTestPage, OddPipe, PEER_URL_REGEXP, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, ProgressBarService, ProgressInterceptor, PropertiesFormTestPage, PropertiesFormTestingModule, PropertyEntity, PropertyEntityFilter, PropertyEntityValidator, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialUtils, ReferentialValidatorService, RegisterConfirmPage, RegisterForm, RegisterModal, ResizableComponent, ResizableDirective, ResizableModule, SCRYPT_PARAMS, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_STORAGE_TESTING_PAGES, SHARED_TESTING_PAGES, SOCIAL_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SelectPeerModal, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedBadgeModule, SharedDebugModule, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMatAutocompleteModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatNumpadModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedNamedFilterModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedToolbarModule, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, SocialTestingModule, Software, StartableService, StatusById, StatusIds, StatusList, StorageDrivers, StorageExplorerComponent, StorageExplorerModule, StorageService, StrIncludesPipe, StrLengthPipe, SwipeTestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TableValidatorService, TextForm, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ToStringPipe, ToastTestingModule, ToastTestingPage, Toasts, TokenScope, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UserEventModule, UserEventNotificationIcon, UserEventNotificationList, UserEventTestService, UserEventTestingModule, UserEventTestingPage, UserSettings, UserToken, UserTokenTable, UsersPage, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayGroupBy, arraySize, asInputElement, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, collectByProperty, copyEntity2Form, createPromiseEvent, createPromiseEventEmitter, departmentToString, departmentsToString, disableControls, emitPromiseEvent, entityToString, equals, equalsOrNil, escapeRegExp, fadeInAnimation, fadeInOutAnimation, filterFalse, filterNotNil, filterNumberInput, filterTrue, findParentWithClass, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorShade, getColorTint, getConnectionType, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, hexToRgb, hexToRgbArray, initArrayControlsFromValues, isAndroid, isBlankString, isCapacitor, isControlHasInput, isEmptyArray, isIOS, isInputElement, isInstanceOf, isInt, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isProgressEvent, isResponseEvent, isStartableService, isTouchUi, isWindows, joinProperties, joinPropertiesPath, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, matchMedia, matchUpperCase, mergeLoadResult, mixHex, moveInputCaretToSeparator, noTrailingSlash, notNilOrDefault, nullIfUndefined, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputRange, setCalculatedValue, setPropertyByPath, setTabIndex, sleep, slideInOutAnimation, slideUpDownAnimation, sort, splitById, splitByProperty, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toNotNil, toNumber, trimEmptyToNull, uncapitalizeFirstLetter, undefinedIfNull, updateValueAndValidity, waitFor, waitForFalse, waitForTrue, waitIdle, waitWhilePending };
38062
38248
  //# sourceMappingURL=sumaris-net.ngx-components.mjs.map