@sumaris-net/ngx-components 21.3.1 → 21.3.3-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.
- package/AGENTS.md +78 -0
- package/CLAUDE.md +1 -0
- package/doc/changelog.md +46 -2
- package/fesm2022/sumaris-net.ngx-components.mjs +62 -22
- package/fesm2022/sumaris-net.ngx-components.mjs.map +1 -1
- package/package.json +1 -1
- package/src/assets/manifest.json +1 -1
- package/types/sumaris-net.ngx-components.d.ts +24 -3
package/AGENTS.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Developer Guide
|
|
2
|
+
|
|
3
|
+
## Project
|
|
4
|
+
|
|
5
|
+
`@sumaris-net/ngx-components` est une **bibliothèque Angular** (publiée sur npm) fournissant des composants de base réutilisables pour des applications Angular/Ionic. Elle est actuellement migrée vers Angular 21.
|
|
6
|
+
|
|
7
|
+
- Dépôt GitLab : https://gitlab.ifremer.fr/sih-public/sumaris/ngx-sumaris-components
|
|
8
|
+
- Branche principale : `master`
|
|
9
|
+
- Version actuelle : alignée sur la version majeure d'Angular (ex. `21.x.x` pour Angular 21)
|
|
10
|
+
|
|
11
|
+
## General
|
|
12
|
+
|
|
13
|
+
- Ask me if not possible, or instructions is not clear
|
|
14
|
+
- Use Angular 21, Angular Material 21 and Ionic 8
|
|
15
|
+
- Agent configuration (rules, skills, MCP) lives in `.agents/` - see [.agents/README.md](.agents/README.md)
|
|
16
|
+
|
|
17
|
+
## Stack
|
|
18
|
+
|
|
19
|
+
| Technologie | Version | Usage |
|
|
20
|
+
|-----------------------|----------|----------------------------------------------------------|
|
|
21
|
+
| Angular | ~21.2.x | Framework principal |
|
|
22
|
+
| Angular Material | ~21.2.x | Composants UI Material Design |
|
|
23
|
+
| Ionic | ~8.8.x | Composants mobiles et navigation |
|
|
24
|
+
| Capacitor | ~7.4.x | Accès aux APIs natives (Android / iOS) |
|
|
25
|
+
| RxState (@rx-angular) | ~21.x | Gestion d'état réactif dans les composants |
|
|
26
|
+
| Apollo / GraphQL | ~4.x | Communication avec le back-end via GraphQL |
|
|
27
|
+
| ngx-translate | ~16.x | Internationalisation (i18n) |
|
|
28
|
+
| Moment.js | ~2.30.x | Manipulation des dates |
|
|
29
|
+
| Playwright | ~1.58.x | Tests end-to-end |
|
|
30
|
+
| Jasmine / Karma | ~5.x | Tests unitaires |
|
|
31
|
+
| ng-packagr | ~21.2.x | Build et publication de la bibliothèque |
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm run start # Démarre l'app de démo sur http://localhost:4201
|
|
37
|
+
npm run build # Build de production de l'app de démo
|
|
38
|
+
npm run packagr # Build de la bibliothèque (dist/)
|
|
39
|
+
npm run publish # Build + publication npm
|
|
40
|
+
npm test # Tests unitaires (Jasmine + Karma)
|
|
41
|
+
npm run lint # Vérification ESLint
|
|
42
|
+
npm run lint:fix # Correction automatique ESLint
|
|
43
|
+
npm run e2e # Tests end-to-end (Playwright)
|
|
44
|
+
npm run e2e:install # Installation du navigateur Playwright (Chromium)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
> L'app de démo/test tourne sur **http://localhost:4201** (voir `testing.md` pour les tests Playwright).
|
|
48
|
+
|
|
49
|
+
## Key directories
|
|
50
|
+
|
|
51
|
+
| Répertoire | Description |
|
|
52
|
+
|----------------------|---------------------------------------------------------------------|
|
|
53
|
+
| `src/app/core/` | Module principal : entités de base, services, composants génériques |
|
|
54
|
+
| `src/app/shared/` | Module partagé : formulaires, utilitaires, directives |
|
|
55
|
+
| `src/app/social/` | Module social (commentaires, notifications) |
|
|
56
|
+
| `src/app/admin/` | Module administration (utilisateurs, permissions) |
|
|
57
|
+
| `src/app/testing/` | Composants et pages pour les tests et la démo |
|
|
58
|
+
| `src/theme/` | Styles globaux Ionic / Material / SCSS custom |
|
|
59
|
+
| `src/assets/i18n/` | Fichiers de traduction (en.json, fr.json…) |
|
|
60
|
+
|
|
61
|
+
## Rules
|
|
62
|
+
|
|
63
|
+
Ce tableau référence l'ensemble des règles de développement du projet. Chaque règle est détaillée dans un fichier dédié du dossier `.agents/rules/`.
|
|
64
|
+
|
|
65
|
+
| Fichier | Description |
|
|
66
|
+
|--------------------------------------------------------|-----------------------------------------------------------------------------------------------|
|
|
67
|
+
| [common.md](.agents/rules/common.md) | Règles communes : conventions de nommage, suffixes de fichiers, frameworks |
|
|
68
|
+
| [component.md](.agents/rules/component.md) | Écriture d'un composant Angular : RxState, template HTML, modals, ngx-translate |
|
|
69
|
+
| [entities.md](.agents/rules/entities.md) | Modèle d'entité TypeScript : `@EntityClass`, `asObject()`, `fromObject()` |
|
|
70
|
+
| [service.md](.agents/rules/service.md) | Écriture d'un service : `BaseEntityService`, GraphQL, CRUD, pattern de sauvegarde |
|
|
71
|
+
| [service-offline.md](.agents/rules/service-offline.md) | Gestion offline : `RootDataSynchroService`, `EntityStorage`, synchronisation |
|
|
72
|
+
| [service-quality.md](.agents/rules/service-quality.md) | Qualité des données : control, terminaison, validation, délégation aux services fils |
|
|
73
|
+
| [validator.md](.agents/rules/validator.md) | Écriture d'un validateur : `AppValidatorService`, Reactive Forms, options typées, FormArray |
|
|
74
|
+
| [table.md](.agents/rules/table.md) | Écriture d'un tableau : `AppTable`, validateur, colonnes, template HTML |
|
|
75
|
+
| [gitlab.md](.agents/rules/gitlab.md) | Workflow GitLab : branches, merge requests, revues de code, conventions |
|
|
76
|
+
| [configuration.md](.agents/rules/configuration.md) | Options de configuration par module : `FormFieldDefinition`, `ConfigService`, settings locaux |
|
|
77
|
+
| [testing.md](.agents/rules/testing.md) | Tests de l'application : unit test (Jasmine), reproduction de bugs, tests e2e via Playwright |
|
|
78
|
+
| [theming.md](.agents/rules/theming.md) | Thème et styles : rôle de chaque fichier de `src/theme`, SCSS/CSS (Ionic / Material) |
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
package/doc/changelog.md
CHANGED
|
@@ -2291,9 +2291,50 @@ enh: Environment: add useHash property to configure Angular router to use hash U
|
|
|
2291
2291
|
- fix(polyfills): 'npm run utils:compat' now add all core-js compat imports for old browsers defined in the file '.browserslistrc' (eg. Chrome 61+)
|
|
2292
2292
|
- fix(platform) Fix keyboard issue on Android before 14 (fix issue sumaris-app#2076)
|
|
2293
2293
|
|
|
2294
|
-
## 18.
|
|
2294
|
+
## 18.27.0
|
|
2295
2295
|
- enh(about) Add changelog link (Notes de version) to About modal, using ConfigurationService and markdown modal
|
|
2296
2296
|
|
|
2297
|
+
## 18.27.2
|
|
2298
|
+
- fix(about) Replace version placeholder `{version}` in changelog link
|
|
2299
|
+
|
|
2300
|
+
## 18.27.3
|
|
2301
|
+
- fix(about) Generate changelog from the gitlab release API. Now we can URL like :
|
|
2302
|
+
* https://gitlab.ifremer.fr/api/v4/projects/sih-public%2Fsumaris%2Fsumaris-app/releases
|
|
2303
|
+
* or https://gitlab.ifremer.fr/api/v4/projects/sih-public%2Fsumaris%2Fsumaris-app/releases/2.16.2
|
|
2304
|
+
- enh(upload-file): File size validation error message
|
|
2305
|
+
- enh(upload-file): Check for excluded extensions
|
|
2306
|
+
- enh(alerts): implement options, including `backdropDismiss` and `keyboardClose`
|
|
2307
|
+
|
|
2308
|
+
## 18.27.4
|
|
2309
|
+
- enh(alerts): expose new options to callers
|
|
2310
|
+
|
|
2311
|
+
## 18.27.5
|
|
2312
|
+
- fix(build) Fix build error on quadrige3-app (Angular 20)
|
|
2313
|
+
|
|
2314
|
+
## 18.27.7
|
|
2315
|
+
- fix(animation) Fix warning in the JS console, on expansionAnimation
|
|
2316
|
+
|
|
2317
|
+
## 18.27.8
|
|
2318
|
+
- fix(settings-page) Local settings was always overridden by account settings - Fix issue sumaris-app#1755
|
|
2319
|
+
|
|
2320
|
+
## 18.27.9
|
|
2321
|
+
- fix(image): increase default max dimensions to 2048x2048
|
|
2322
|
+
|
|
2323
|
+
## 18.27.10
|
|
2324
|
+
- fix(table): Don't call `confirmEditCreate()` on canceling row edition
|
|
2325
|
+
|
|
2326
|
+
## 18.27.11
|
|
2327
|
+
- enh(account): Add option to force account settings to be saved remotely
|
|
2328
|
+
|
|
2329
|
+
## 18.27.12
|
|
2330
|
+
- enh(table): Add option to set loading debounce time
|
|
2331
|
+
|
|
2332
|
+
## 18.27.14
|
|
2333
|
+
- enh(async-table): Add new options to save(): `keepSelection`, `confirmRow`, `throwIfNotConfirmed`
|
|
2334
|
+
|
|
2335
|
+
## 18.27.15
|
|
2336
|
+
- enh(about-modal): reorganise about modal (Mantis #69903)
|
|
2337
|
+
|
|
2297
2338
|
## 21.0.0
|
|
2298
2339
|
- enh(core) Migration to Angular 21 and Ionic 8
|
|
2299
2340
|
- enh(e2e) Allow to run e2e on many peer/login, using file `e2e/.environments.json` or `.environments.json` (or `.local/environments.json`)
|
|
@@ -2383,4 +2424,7 @@ The library now uses the Ionic **standalone** build (`@ionic/angular/standalone`
|
|
|
2383
2424
|
|
|
2384
2425
|
## 21.3.1
|
|
2385
2426
|
- enh(image-gallery): add `slideshowActionsTemplate` input to `AppImageGalleryComponent` (and `actionsTemplate` to `AppImageGallerySlideshowComponent`)
|
|
2386
|
-
- a `TemplateRef` slot rendered in the slideshow footer's middle column
|
|
2427
|
+
- a `TemplateRef` slot rendered in the slideshow footer's middle column
|
|
2428
|
+
|
|
2429
|
+
## 21.3.2
|
|
2430
|
+
- fix(latlong-field): remove duplicated subscript reserved space under lat/long fields
|
|
@@ -14662,6 +14662,11 @@ class MatLatLongField {
|
|
|
14662
14662
|
get readonly() {
|
|
14663
14663
|
return this._readonly;
|
|
14664
14664
|
}
|
|
14665
|
+
/**
|
|
14666
|
+
* Subscript sizing of the HOST wrapper (the one rendering projected hints/errors).
|
|
14667
|
+
* NOTE: the inner <mat-form-field> is always forced to 'dynamic' (its subscript is always
|
|
14668
|
+
* empty) — changing this input only affects the space reserved around the host wrapper.
|
|
14669
|
+
*/
|
|
14665
14670
|
set subscriptSizing(value) {
|
|
14666
14671
|
this._subscriptSizing = value;
|
|
14667
14672
|
}
|
|
@@ -14764,7 +14769,7 @@ class MatLatLongField {
|
|
|
14764
14769
|
multi: true,
|
|
14765
14770
|
useExisting: forwardRef(() => MatLatLongField),
|
|
14766
14771
|
},
|
|
14767
|
-
], viewQueries: [{ propertyName: "input", first: true, predicate: ["input"], descendants: true }], ngImport: i0, template: "<mat-form-field\n #formField\n [floatLabel]=\"floatLabel | asFloatLabelType\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"
|
|
14772
|
+
], viewQueries: [{ propertyName: "input", first: true, predicate: ["input"], descendants: true }], ngImport: i0, template: "<!-- Errors and hints are rendered by this component's own subscript wrapper (below the form-field),\n so force 'dynamic' here: with the default 'fixed' sizing, the inner form-field would reserve\n an extra ~22px subscript line, making rows with lat/long fields taller than other rows\n (e.g. ObsMer operation form). -->\n<mat-form-field\n #formField\n [floatLabel]=\"floatLabel | asFloatLabelType\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"'dynamic'\"\n [class.mdc-text-field--invalid]=\"formControl.touched && formControl.invalid\"\n class=\"mat-latlong-field mat-latlong-field-{{ type }} {{ classList }}\"\n>\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n @if (floatLabel !== 'never' && placeholder | isNotNilOrBlank) {\n <mat-label>{{ placeholder }}</mat-label>\n }\n\n <mat-latlong-input\n #input\n [formControl]=\"formControl\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [type]=\"type\"\n [latLongPattern]=\"pattern\"\n [defaultSign]=\"defaultSign\"\n [maxDecimals]=\"maxDecimals\"\n [readonly]=\"readonly\"\n ></mat-latlong-input>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n</mat-form-field>\n\n<!-- Do the same as MatFromField since angular 15 (see component source)-->\n<div\n class=\"mat-mdc-form-field-subscript-wrapper\"\n [class.mat-mdc-form-field-bottom-align]=\"subscriptSizing === 'fixed'\"\n [class.mat-mdc-form-field-subscript-dynamic-size]=\"subscriptSizing === 'dynamic'\"\n>\n @if (formControl.touched && formControl.invalid) {\n <!-- errors -->\n <div class=\"mat-mdc-form-field-error-wrapper\">\n @if (formControl.errors | mapKeys | arrayFirst; as errorKey) {\n <mat-error>\n {{ i18nErrorKeys | mapGet: errorKey | translate: (formControl.errors | mapGet: errorKey) }}\n </mat-error>\n } @else {\n <ng-container *ngTemplateOutlet=\"matErrorTemplate\"></ng-container>\n }\n </div>\n } @else {\n <!-- hints -->\n <mat-hint>\n <ng-container *ngTemplateOutlet=\"matHintTemplate\"></ng-container>\n </mat-hint>\n }\n</div>\n\n<ng-template #matPrefixTemplate>\n <ng-content select=\"[matPrefix]\"></ng-content>\n</ng-template>\n\n<ng-template #matSuffixTemplate>\n @if (clearable && !readonly) {\n <button\n matSuffix\n mat-icon-button\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearValue($event)\"\n [hidden]=\"formControl.value | isNil\"\n >\n <mat-icon>close</mat-icon>\n </button>\n }\n\n <ng-content select=\"[matSuffix]\"></ng-content>\n</ng-template>\n\n<ng-template #matErrorTemplate>\n <ng-content select=\"mat-error,[matError]\"></ng-content>\n</ng-template>\n\n<ng-template #matHintTemplate>\n <div class=\"mat-mdc-form-field-hint-wrapper\">\n <ng-content select=\"mat-hint:not([align='end']),[matHint]\"></ng-content>\n <div class=\"mat-mdc-form-field-hint-spacer\"></div>\n <ng-content select=\"mat-hint[align='end']\"></ng-content>\n </div>\n</ng-template>\n", styles: [":host{display:inline-block;width:100%;position:relative}\n"], dependencies: [{ kind: "directive", type: i4$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: i9.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatLatLongFieldInput, selector: "mat-latlong-input", inputs: ["aria-describedby", "required", "disabled", "value", "defaultSign", "type", "latLongPattern", "maxDecimals", "readonly"] }, { kind: "pipe", type: ArrayFirstPipe, name: "arrayFirst" }, { kind: "pipe", type: MapGetPipe, name: "mapGet" }, { kind: "pipe", type: MapKeysPipe, name: "mapKeys" }, { kind: "pipe", type: IsNotNilOrBlankPipe, name: "isNotNilOrBlank" }, { kind: "pipe", type: IsNilPipe, name: "isNil" }, { kind: "pipe", type: AsFloatLabelTypePipe, name: "asFloatLabelType" }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14768
14773
|
}
|
|
14769
14774
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MatLatLongField, decorators: [{
|
|
14770
14775
|
type: Component,
|
|
@@ -14774,7 +14779,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
14774
14779
|
multi: true,
|
|
14775
14780
|
useExisting: forwardRef(() => MatLatLongField),
|
|
14776
14781
|
},
|
|
14777
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<mat-form-field\n #formField\n [floatLabel]=\"floatLabel | asFloatLabelType\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"
|
|
14782
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<!-- Errors and hints are rendered by this component's own subscript wrapper (below the form-field),\n so force 'dynamic' here: with the default 'fixed' sizing, the inner form-field would reserve\n an extra ~22px subscript line, making rows with lat/long fields taller than other rows\n (e.g. ObsMer operation form). -->\n<mat-form-field\n #formField\n [floatLabel]=\"floatLabel | asFloatLabelType\"\n [appearance]=\"appearance\"\n [subscriptSizing]=\"'dynamic'\"\n [class.mdc-text-field--invalid]=\"formControl.touched && formControl.invalid\"\n class=\"mat-latlong-field mat-latlong-field-{{ type }} {{ classList }}\"\n>\n <div matPrefix>\n <ng-container *ngTemplateOutlet=\"matPrefixTemplate\"></ng-container>\n </div>\n\n @if (floatLabel !== 'never' && placeholder | isNotNilOrBlank) {\n <mat-label>{{ placeholder }}</mat-label>\n }\n\n <mat-latlong-input\n #input\n [formControl]=\"formControl\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [type]=\"type\"\n [latLongPattern]=\"pattern\"\n [defaultSign]=\"defaultSign\"\n [maxDecimals]=\"maxDecimals\"\n [readonly]=\"readonly\"\n ></mat-latlong-input>\n\n <div matSuffix>\n <ng-container *ngTemplateOutlet=\"matSuffixTemplate\"></ng-container>\n </div>\n</mat-form-field>\n\n<!-- Do the same as MatFromField since angular 15 (see component source)-->\n<div\n class=\"mat-mdc-form-field-subscript-wrapper\"\n [class.mat-mdc-form-field-bottom-align]=\"subscriptSizing === 'fixed'\"\n [class.mat-mdc-form-field-subscript-dynamic-size]=\"subscriptSizing === 'dynamic'\"\n>\n @if (formControl.touched && formControl.invalid) {\n <!-- errors -->\n <div class=\"mat-mdc-form-field-error-wrapper\">\n @if (formControl.errors | mapKeys | arrayFirst; as errorKey) {\n <mat-error>\n {{ i18nErrorKeys | mapGet: errorKey | translate: (formControl.errors | mapGet: errorKey) }}\n </mat-error>\n } @else {\n <ng-container *ngTemplateOutlet=\"matErrorTemplate\"></ng-container>\n }\n </div>\n } @else {\n <!-- hints -->\n <mat-hint>\n <ng-container *ngTemplateOutlet=\"matHintTemplate\"></ng-container>\n </mat-hint>\n }\n</div>\n\n<ng-template #matPrefixTemplate>\n <ng-content select=\"[matPrefix]\"></ng-content>\n</ng-template>\n\n<ng-template #matSuffixTemplate>\n @if (clearable && !readonly) {\n <button\n matSuffix\n mat-icon-button\n tabindex=\"-1\"\n type=\"button\"\n (click)=\"clearValue($event)\"\n [hidden]=\"formControl.value | isNil\"\n >\n <mat-icon>close</mat-icon>\n </button>\n }\n\n <ng-content select=\"[matSuffix]\"></ng-content>\n</ng-template>\n\n<ng-template #matErrorTemplate>\n <ng-content select=\"mat-error,[matError]\"></ng-content>\n</ng-template>\n\n<ng-template #matHintTemplate>\n <div class=\"mat-mdc-form-field-hint-wrapper\">\n <ng-content select=\"mat-hint:not([align='end']),[matHint]\"></ng-content>\n <div class=\"mat-mdc-form-field-hint-spacer\"></div>\n <ng-content select=\"mat-hint[align='end']\"></ng-content>\n </div>\n</ng-template>\n", styles: [":host{display:inline-block;width:100%;position:relative}\n"] }]
|
|
14778
14783
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1$2.FormGroupDirective, decorators: [{
|
|
14779
14784
|
type: Optional
|
|
14780
14785
|
}] }, { type: undefined, decorators: [{
|
|
@@ -25929,14 +25934,14 @@ class AccountService extends BaseGraphqlService {
|
|
|
25929
25934
|
console.error('[account] Error while saving account locally: ' + ((err && err.message) || err), err);
|
|
25930
25935
|
}
|
|
25931
25936
|
}
|
|
25932
|
-
async saveLocalSettingsRemotely(source) {
|
|
25937
|
+
async saveLocalSettingsRemotely(source, force) {
|
|
25933
25938
|
if (!source || !this.isLogin() || source.accountInheritance === false)
|
|
25934
25939
|
return; // Skip
|
|
25935
25940
|
const account = this.account;
|
|
25936
25941
|
// Merge local settings into account's settings
|
|
25937
25942
|
const settings = UserSettings.fromObject(account.settings) || new UserSettings();
|
|
25938
25943
|
const changed = settings.merge(source, this._remoteLocalSettingsKeys);
|
|
25939
|
-
if (!changed)
|
|
25944
|
+
if (!changed && !force)
|
|
25940
25945
|
return; // Skip if unchanged
|
|
25941
25946
|
// Save remotely
|
|
25942
25947
|
this.account.settings = settings;
|
|
@@ -33893,11 +33898,11 @@ class AboutModal {
|
|
|
33893
33898
|
}
|
|
33894
33899
|
}
|
|
33895
33900
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: AboutModal, deps: [{ token: i2.TranslateService }, { token: i2$2.ModalController }, { token: ConfigService }, { token: NetworkService }, { token: PlatformService }, { token: i2$4.HttpClient }, { token: i0.ChangeDetectorRef }, { token: ENVIRONMENT, optional: true }, { token: APP_ABOUT_DEVELOPERS, optional: true }, { token: APP_ABOUT_PARTNERS, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
33896
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: AboutModal, isStandalone: false, selector: "app-about-modal", ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-title>\n {{ 'ABOUT.TITLE' | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"close()\" visible-xs visible-sm visible-mobile>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n <ion-list lines=\"none\">\n <!-- Peer info -->\n @if (config$ | async; as config) {\n <ion-item>\n <span slot=\"start\"> </span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n <b>{{ config.label | capitalize }}</b>\n @if (config.name) {\n -\n <span [innerHTML]=\"config.name\"></span>\n }\n </h2>\n\n @if (config.description) {\n <p>\n <markdown [data]=\"config.description\" emoji></markdown>\n </p>\n }\n </ion-label>\n </ion-item>\n }\n\n <!-- Version info -->\n <ion-item>\n <span slot=\"start\"> </span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n @if (name) {\n <small>{{ 'ABOUT.POWERED_BY' | translate }}</small>\n {{ name }}\n }\n @if (version) {\n <span [innerHTML]=\"'ABOUT.VERSION' | translate: { version: version }\"></span>\n }\n @if (allowVersionDetails) {\n \n <small>\n <a (click)=\"showVersionDetails = !showVersionDetails\" tappable>\n {{ 'COMMON.BTN_SHOW_MORE' | translate }}\n <ion-icon [name]=\"!showVersionDetails ? 'chevron-down' : 'chevron-up'\"></ion-icon>\n </a>\n @if (changelogUrl) {\n \n <a (click)=\"openChangelog($event)\"\n [href]=\"changelogUrl\" tappable>{{ 'ABOUT.BTN_CHANGELOG' | translate}}</a>\n }\n </small>\n }\n </h2>\n\n <!-- version details -->\n @if (showVersionDetails) {\n <p class=\"version-details\">\n <!-- App version -->\n @if (buildDate) {\n <small>\n <ion-icon name=\"logo-angular\"></ion-icon>\n <span [innerHTML]=\"'ABOUT.APP_VERSION' | translate: { version: version }\"></span>\n \n <ion-icon name=\"time-outline\"></ion-icon>\n {{ buildDate | dateFormat: { time: true, seconds: false } }}\n </small>\n <br />\n }\n <!-- Pod version -->\n @if (nodeInfo?.softwareVersion && nodeInfo.buildDate) {\n <small>\n <ion-icon name=\"server\"></ion-icon>\n <span [innerHTML]=\"'ABOUT.POD_VERSION' | translate: { version: nodeInfo.softwareVersion }\"></span>\n \n <ion-icon name=\"time-outline\"></ion-icon>\n {{ nodeInfo.buildDate | dateFormat: { time: true, seconds: false } }}\n </small>\n }\n </p>\n }\n\n <!-- license-->\n <p [innerHTML]=\"'ABOUT.LICENSE' | translate\"></p>\n </ion-label>\n </ion-item>\n\n <!-- Help -->\n @if (forumUrl || helpUrl) {\n <ion-item>\n <mat-icon slot=\"start\">help_outline</mat-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.HELP' | translate }}\n </p>\n <p>\n <ion-button fill=\"solid\" color=\"accent\" href=\"{{ helpUrl }}\" target=\"help\" size=\"default\">\n <mat-icon slot=\"start\">description</mat-icon>\n <ion-text>{{ 'ABOUT.USER_MANUAL' | translate }}</ion-text>\n </ion-button>\n <ion-button fill=\"solid\" color=\"secondary\" href=\"{{ forumUrl }}\" target=\"forum\" size=\"default\">\n <ion-icon slot=\"start\" name=\"chatbubbles\"></ion-icon>\n <ion-text>{{ 'ABOUT.FORUM' | translate }}</ion-text>\n </ion-button>\n </p>\n </ion-label>\n </ion-item>\n }\n\n <!-- Report issue -->\n @if (reportIssueUrl) {\n <ion-item>\n <ion-icon slot=\"start\" name=\"bug\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.REPORT_ISSUE' | translate }}\n </p>\n <p>\n <a color=\"primary\" href=\"{{ reportIssueUrl }}\" target=\"_system\">{{ 'ABOUT.BTN_REPORT_ISSUE' | translate }}</a>\n </p>\n </ion-label>\n </ion-item>\n }\n\n <!-- Source code -->\n @if (sourceUrl) {\n <ion-item>\n <ion-icon slot=\"start\" name=\"code\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.SOURCE_CODE' | translate }}\n </p>\n <p>\n <a color=\"primary\" href=\"{{ sourceUrl }}\" target=\"_system\">{{ sourceUrl }}</a>\n </p>\n </ion-label>\n </ion-item>\n }\n\n <!-- Developers -->\n @if (developers | isNotEmptyArray) {\n <ion-item>\n <ion-icon slot=\"start\" name=\"people\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.DEVELOPED_BY' | translate }}\n </p>\n <ion-list lines=\"none\">\n @for (item of developers; track item) {\n <ion-item class=\"ion-no-padding\">\n <ion-label>\n <p>\n <a color=\"primary\" href=\"{{ item.siteUrl }}\" target=\"_system\">{{ item.name || item.label }}</a>\n </p>\n </ion-label>\n <ion-img slot=\"end\" [src]=\"item.logo\" />\n </ion-item>\n }\n </ion-list>\n </ion-label>\n </ion-item>\n }\n\n <!-- Partners -->\n @if (partners | isNotEmptyArray) {\n <ion-item class=\"item-partners\">\n <ion-icon slot=\"start\" name=\"megaphone\" color=\"dark\"></ion-icon>\n <ion-text>\n <p>{{ 'ABOUT.PARTNERS' | translate }}</p>\n <p>\n @for (item of partners; track item) {\n <a href=\"{{ item.siteUrl }}\" class=\"partners\" target=\"_system\">\n @if (item.logo) {\n <img [src]=\"item.logo\" [alt]=\"item.label\" />\n } @else {\n <ion-text>{{ item.label }}</ion-text>\n }\n </a>\n }\n </p>\n </ion-text>\n </ion-item>\n }\n </ion-list>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"solid\" color=\"tertiary\" (click)=\"close()\">{{ 'COMMON.BTN_CLOSE' | translate }}</ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", styles: [".item-partners ion-text{height:auto}.item-partners ion-text p:last-child{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:space-between}.item-partners ion-text p:last-child a{padding:2px}.item-partners ion-text p:last-child a img{text-align:center;display:inline-block;max-height:40px!important}\n"], dependencies: [{ kind: "component", type: i2$2.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: i2$2.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$2.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: i2$2.IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$2.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$2.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$2.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$2.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$2.IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$2.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$2.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$2.IonRow, selector: "ion-row" }, { kind: "component", type: i2$2.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2$2.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$2.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i4$4.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: i6$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: i4$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }, { kind: "pipe", type: DateFormatPipe, name: "dateFormat" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: CapitalizePipe, name: "capitalize" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
33901
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: AboutModal, isStandalone: false, selector: "app-about-modal", ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-title>\n {{ 'ABOUT.TITLE' | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"close()\" visible-xs visible-sm visible-mobile>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n <ion-list lines=\"none\">\n <!-- Peer info -->\n @if (config$ | async; as config) {\n <ion-item>\n <span slot=\"start\"> </span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n <b>{{ config.label | capitalize }}</b>\n @if (config.name) {\n -\n <span [innerHTML]=\"config.name\"></span>\n }\n </h2>\n\n @if (config.description) {\n <p>\n <markdown [data]=\"config.description\" emoji></markdown>\n </p>\n }\n </ion-label>\n </ion-item>\n }\n\n <!-- Version info -->\n <ion-item>\n <span slot=\"start\"> </span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n @if (name) {\n <small>{{ 'ABOUT.POWERED_BY' | translate }}</small>\n {{ name }}\n }\n @if (version) {\n <span [innerHTML]=\"'ABOUT.VERSION' | translate: { version: version }\"></span>\n }\n @if (allowVersionDetails) {\n \n <small>\n <a (click)=\"showVersionDetails = !showVersionDetails\" tappable>\n {{ 'COMMON.BTN_SHOW_MORE' | translate }}\n <ion-icon [name]=\"!showVersionDetails ? 'chevron-down' : 'chevron-up'\"></ion-icon>\n </a>\n @if (changelogUrl) {\n \n <a (click)=\"openChangelog($event)\" [href]=\"changelogUrl\" tappable>\n {{ 'ABOUT.BTN_CHANGELOG' | translate }}\n </a>\n }\n </small>\n }\n </h2>\n\n <!-- version details -->\n @if (showVersionDetails) {\n <p class=\"version-details\">\n <!-- App version -->\n @if (buildDate) {\n <small>\n <ion-icon name=\"logo-angular\"></ion-icon>\n <span [innerHTML]=\"'ABOUT.APP_VERSION' | translate: { version: version }\"></span>\n \n <ion-icon name=\"time-outline\"></ion-icon>\n {{ buildDate | dateFormat: { time: true, seconds: false } }}\n </small>\n <br />\n }\n <!-- Pod version -->\n @if (nodeInfo?.softwareVersion && nodeInfo.buildDate) {\n <small>\n <ion-icon name=\"server\"></ion-icon>\n <span [innerHTML]=\"'ABOUT.POD_VERSION' | translate: { version: nodeInfo.softwareVersion }\"></span>\n \n <ion-icon name=\"time-outline\"></ion-icon>\n {{ nodeInfo.buildDate | dateFormat: { time: true, seconds: false } }}\n </small>\n }\n </p>\n }\n\n <!-- license-->\n <p [innerHTML]=\"'ABOUT.LICENSE' | translate\"></p>\n </ion-label>\n </ion-item>\n\n <!-- Help -->\n @if (forumUrl || helpUrl) {\n <ion-item>\n <mat-icon slot=\"start\">help_outline</mat-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.HELP' | translate }}\n </p>\n <p>\n <ion-button fill=\"solid\" color=\"accent\" href=\"{{ helpUrl }}\" target=\"help\" size=\"default\">\n <mat-icon slot=\"start\">description</mat-icon>\n <ion-text>{{ 'ABOUT.USER_MANUAL' | translate }}</ion-text>\n </ion-button>\n <ion-button fill=\"solid\" color=\"secondary\" href=\"{{ forumUrl }}\" target=\"forum\" size=\"default\">\n <ion-icon slot=\"start\" name=\"chatbubbles\"></ion-icon>\n <ion-text>{{ 'ABOUT.FORUM' | translate }}</ion-text>\n </ion-button>\n </p>\n </ion-label>\n </ion-item>\n }\n\n <!-- Report issue -->\n @if (reportIssueUrl) {\n <ion-item>\n <ion-icon slot=\"start\" name=\"bug\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.REPORT_ISSUE' | translate }}\n </p>\n <p>\n <a color=\"primary\" href=\"{{ reportIssueUrl }}\" target=\"_system\">\n {{ 'ABOUT.BTN_REPORT_ISSUE' | translate }}\n </a>\n </p>\n </ion-label>\n </ion-item>\n }\n\n <!-- Partners -->\n @if (partners | isNotEmptyArray) {\n <ion-item class=\"item-partners\">\n <ion-icon slot=\"start\" name=\"megaphone\" color=\"dark\"></ion-icon>\n <ion-text>\n <p>{{ 'ABOUT.PARTNERS' | translate }}</p>\n <p>\n @for (item of partners; track item) {\n <a href=\"{{ item.siteUrl }}\" class=\"partners\" target=\"_system\">\n @if (item.logo) {\n <img [src]=\"item.logo\" [alt]=\"item.label\" />\n } @else {\n <ion-text>{{ item.label }}</ion-text>\n }\n </a>\n }\n </p>\n </ion-text>\n </ion-item>\n }\n\n <!-- Developers -->\n @if (developers | isNotEmptyArray) {\n <ion-item>\n <ion-icon slot=\"start\" name=\"people\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.DEVELOPED_BY' | translate }}\n </p>\n <ion-list lines=\"none\">\n @for (item of developers; track item) {\n <ion-item class=\"ion-no-padding\">\n <ion-label>\n <p>\n <a color=\"primary\" href=\"{{ item.siteUrl }}\" target=\"_system\">{{ item.name || item.label }}</a>\n </p>\n </ion-label>\n <ion-img slot=\"end\" [src]=\"item.logo\" />\n </ion-item>\n }\n </ion-list>\n </ion-label>\n </ion-item>\n } @else {\n <ion-item>\n <ion-icon slot=\"start\" name=\"people\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.DEVELOPED_BY' | translate }}\n </p>\n </ion-label>\n </ion-item>\n }\n\n <!-- Source code -->\n @if (sourceUrl) {\n <ion-item>\n <ion-icon slot=\"start\" name=\"code\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.SOURCE_CODE' | translate }}\n </p>\n <p>\n <a color=\"primary\" href=\"{{ sourceUrl }}\" target=\"_system\">{{ sourceUrl }}</a>\n </p>\n </ion-label>\n </ion-item>\n }\n </ion-list>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"solid\" color=\"tertiary\" (click)=\"close()\">{{ 'COMMON.BTN_CLOSE' | translate }}</ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", styles: [".item-partners ion-text{height:auto}.item-partners ion-text p:last-child{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:space-between}.item-partners ion-text p:last-child a{padding:2px}.item-partners ion-text p:last-child a img{text-align:center;display:inline-block;max-height:40px}\n"], dependencies: [{ kind: "component", type: i2$2.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: i2$2.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$2.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: i2$2.IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$2.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$2.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$2.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$2.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$2.IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$2.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$2.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$2.IonRow, selector: "ion-row" }, { kind: "component", type: i2$2.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2$2.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$2.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i4$4.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: i6$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: i4$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }, { kind: "pipe", type: DateFormatPipe, name: "dateFormat" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: CapitalizePipe, name: "capitalize" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
33897
33902
|
}
|
|
33898
33903
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: AboutModal, decorators: [{
|
|
33899
33904
|
type: Component,
|
|
33900
|
-
args: [{ selector: 'app-about-modal', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-title>\n {{ 'ABOUT.TITLE' | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"close()\" visible-xs visible-sm visible-mobile>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n <ion-list lines=\"none\">\n <!-- Peer info -->\n @if (config$ | async; as config) {\n <ion-item>\n <span slot=\"start\"> </span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n <b>{{ config.label | capitalize }}</b>\n @if (config.name) {\n -\n <span [innerHTML]=\"config.name\"></span>\n }\n </h2>\n\n @if (config.description) {\n <p>\n <markdown [data]=\"config.description\" emoji></markdown>\n </p>\n }\n </ion-label>\n </ion-item>\n }\n\n <!-- Version info -->\n <ion-item>\n <span slot=\"start\"> </span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n @if (name) {\n <small>{{ 'ABOUT.POWERED_BY' | translate }}</small>\n {{ name }}\n }\n @if (version) {\n <span [innerHTML]=\"'ABOUT.VERSION' | translate: { version: version }\"></span>\n }\n @if (allowVersionDetails) {\n \n <small>\n <a (click)=\"showVersionDetails = !showVersionDetails\" tappable>\n {{ 'COMMON.BTN_SHOW_MORE' | translate }}\n <ion-icon [name]=\"!showVersionDetails ? 'chevron-down' : 'chevron-up'\"></ion-icon>\n </a>\n @if (changelogUrl) {\n \n <a (click)=\"openChangelog($event)\"
|
|
33905
|
+
args: [{ selector: 'app-about-modal', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-title>\n {{ 'ABOUT.TITLE' | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"close()\" visible-xs visible-sm visible-mobile>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n <ion-list lines=\"none\">\n <!-- Peer info -->\n @if (config$ | async; as config) {\n <ion-item>\n <span slot=\"start\"> </span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n <b>{{ config.label | capitalize }}</b>\n @if (config.name) {\n -\n <span [innerHTML]=\"config.name\"></span>\n }\n </h2>\n\n @if (config.description) {\n <p>\n <markdown [data]=\"config.description\" emoji></markdown>\n </p>\n }\n </ion-label>\n </ion-item>\n }\n\n <!-- Version info -->\n <ion-item>\n <span slot=\"start\"> </span>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n @if (name) {\n <small>{{ 'ABOUT.POWERED_BY' | translate }}</small>\n {{ name }}\n }\n @if (version) {\n <span [innerHTML]=\"'ABOUT.VERSION' | translate: { version: version }\"></span>\n }\n @if (allowVersionDetails) {\n \n <small>\n <a (click)=\"showVersionDetails = !showVersionDetails\" tappable>\n {{ 'COMMON.BTN_SHOW_MORE' | translate }}\n <ion-icon [name]=\"!showVersionDetails ? 'chevron-down' : 'chevron-up'\"></ion-icon>\n </a>\n @if (changelogUrl) {\n \n <a (click)=\"openChangelog($event)\" [href]=\"changelogUrl\" tappable>\n {{ 'ABOUT.BTN_CHANGELOG' | translate }}\n </a>\n }\n </small>\n }\n </h2>\n\n <!-- version details -->\n @if (showVersionDetails) {\n <p class=\"version-details\">\n <!-- App version -->\n @if (buildDate) {\n <small>\n <ion-icon name=\"logo-angular\"></ion-icon>\n <span [innerHTML]=\"'ABOUT.APP_VERSION' | translate: { version: version }\"></span>\n \n <ion-icon name=\"time-outline\"></ion-icon>\n {{ buildDate | dateFormat: { time: true, seconds: false } }}\n </small>\n <br />\n }\n <!-- Pod version -->\n @if (nodeInfo?.softwareVersion && nodeInfo.buildDate) {\n <small>\n <ion-icon name=\"server\"></ion-icon>\n <span [innerHTML]=\"'ABOUT.POD_VERSION' | translate: { version: nodeInfo.softwareVersion }\"></span>\n \n <ion-icon name=\"time-outline\"></ion-icon>\n {{ nodeInfo.buildDate | dateFormat: { time: true, seconds: false } }}\n </small>\n }\n </p>\n }\n\n <!-- license-->\n <p [innerHTML]=\"'ABOUT.LICENSE' | translate\"></p>\n </ion-label>\n </ion-item>\n\n <!-- Help -->\n @if (forumUrl || helpUrl) {\n <ion-item>\n <mat-icon slot=\"start\">help_outline</mat-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.HELP' | translate }}\n </p>\n <p>\n <ion-button fill=\"solid\" color=\"accent\" href=\"{{ helpUrl }}\" target=\"help\" size=\"default\">\n <mat-icon slot=\"start\">description</mat-icon>\n <ion-text>{{ 'ABOUT.USER_MANUAL' | translate }}</ion-text>\n </ion-button>\n <ion-button fill=\"solid\" color=\"secondary\" href=\"{{ forumUrl }}\" target=\"forum\" size=\"default\">\n <ion-icon slot=\"start\" name=\"chatbubbles\"></ion-icon>\n <ion-text>{{ 'ABOUT.FORUM' | translate }}</ion-text>\n </ion-button>\n </p>\n </ion-label>\n </ion-item>\n }\n\n <!-- Report issue -->\n @if (reportIssueUrl) {\n <ion-item>\n <ion-icon slot=\"start\" name=\"bug\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.REPORT_ISSUE' | translate }}\n </p>\n <p>\n <a color=\"primary\" href=\"{{ reportIssueUrl }}\" target=\"_system\">\n {{ 'ABOUT.BTN_REPORT_ISSUE' | translate }}\n </a>\n </p>\n </ion-label>\n </ion-item>\n }\n\n <!-- Partners -->\n @if (partners | isNotEmptyArray) {\n <ion-item class=\"item-partners\">\n <ion-icon slot=\"start\" name=\"megaphone\" color=\"dark\"></ion-icon>\n <ion-text>\n <p>{{ 'ABOUT.PARTNERS' | translate }}</p>\n <p>\n @for (item of partners; track item) {\n <a href=\"{{ item.siteUrl }}\" class=\"partners\" target=\"_system\">\n @if (item.logo) {\n <img [src]=\"item.logo\" [alt]=\"item.label\" />\n } @else {\n <ion-text>{{ item.label }}</ion-text>\n }\n </a>\n }\n </p>\n </ion-text>\n </ion-item>\n }\n\n <!-- Developers -->\n @if (developers | isNotEmptyArray) {\n <ion-item>\n <ion-icon slot=\"start\" name=\"people\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.DEVELOPED_BY' | translate }}\n </p>\n <ion-list lines=\"none\">\n @for (item of developers; track item) {\n <ion-item class=\"ion-no-padding\">\n <ion-label>\n <p>\n <a color=\"primary\" href=\"{{ item.siteUrl }}\" target=\"_system\">{{ item.name || item.label }}</a>\n </p>\n </ion-label>\n <ion-img slot=\"end\" [src]=\"item.logo\" />\n </ion-item>\n }\n </ion-list>\n </ion-label>\n </ion-item>\n } @else {\n <ion-item>\n <ion-icon slot=\"start\" name=\"people\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.DEVELOPED_BY' | translate }}\n </p>\n </ion-label>\n </ion-item>\n }\n\n <!-- Source code -->\n @if (sourceUrl) {\n <ion-item>\n <ion-icon slot=\"start\" name=\"code\" color=\"dark\"></ion-icon>\n <ion-label>\n <p>\n {{ 'ABOUT.SOURCE_CODE' | translate }}\n </p>\n <p>\n <a color=\"primary\" href=\"{{ sourceUrl }}\" target=\"_system\">{{ sourceUrl }}</a>\n </p>\n </ion-label>\n </ion-item>\n }\n </ion-list>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"solid\" color=\"tertiary\" (click)=\"close()\">{{ 'COMMON.BTN_CLOSE' | translate }}</ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", styles: [".item-partners ion-text{height:auto}.item-partners ion-text p:last-child{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:space-between}.item-partners ion-text p:last-child a{padding:2px}.item-partners ion-text p:last-child a img{text-align:center;display:inline-block;max-height:40px}\n"] }]
|
|
33901
33906
|
}], ctorParameters: () => [{ type: i2.TranslateService }, { type: i2$2.ModalController }, { type: ConfigService }, { type: NetworkService }, { type: PlatformService }, { type: i2$4.HttpClient }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
|
|
33902
33907
|
type: Optional
|
|
33903
33908
|
}, {
|
|
@@ -41909,6 +41914,7 @@ class AppTable {
|
|
|
41909
41914
|
propagateRowError = false;
|
|
41910
41915
|
permanentSelectionAllowed = false; // Allow keep selected rows over page change or sort change
|
|
41911
41916
|
openRowThrottleTime = 800; // Prevent double click - see issue sumaris-app#864
|
|
41917
|
+
loadingDebounceTime = 250; // Debounce time for loading spinner
|
|
41912
41918
|
defaultSortBy;
|
|
41913
41919
|
defaultSortDirection;
|
|
41914
41920
|
defaultPageSize = DEFAULT_PAGE_SIZE;
|
|
@@ -42947,9 +42953,12 @@ class AppTable {
|
|
|
42947
42953
|
return true; // Already the edited row
|
|
42948
42954
|
if (event?.defaultPrevented)
|
|
42949
42955
|
return false;
|
|
42950
|
-
|
|
42951
|
-
if (!
|
|
42952
|
-
|
|
42956
|
+
// Don't call confirmEditCreate() if canceling
|
|
42957
|
+
if (!toBoolean(opts?.canceling, false)) {
|
|
42958
|
+
const confirmed = this.confirmEditCreate();
|
|
42959
|
+
if (!confirmed)
|
|
42960
|
+
return false;
|
|
42961
|
+
}
|
|
42953
42962
|
if (!row.editing && !this.loading) {
|
|
42954
42963
|
this.focusColumn = opts?.focusColumn ?? this.focusColumn;
|
|
42955
42964
|
this._dataSource.startEdit(row);
|
|
@@ -43512,7 +43521,7 @@ class AppTable {
|
|
|
43512
43521
|
this.checkIfRowPristine(row);
|
|
43513
43522
|
// Restore editing state
|
|
43514
43523
|
if (keepEditing) {
|
|
43515
|
-
this.editRow(undefined, row);
|
|
43524
|
+
this.editRow(undefined, row, { canceling: true });
|
|
43516
43525
|
}
|
|
43517
43526
|
}
|
|
43518
43527
|
async checkIfRowPristine(row, opts) {
|
|
@@ -43562,7 +43571,7 @@ class AppTable {
|
|
|
43562
43571
|
// If changed to True: propagate as soon as possible
|
|
43563
43572
|
tap$1((loading) => loading && this.setLoading(true)),
|
|
43564
43573
|
// If changed to False: wait 250ms before propagate (to make sure the spinner has been displayed)
|
|
43565
|
-
debounceTime(
|
|
43574
|
+
debounceTime(this.loadingDebounceTime), tap$1((loading) => !loading && this.setLoading(false)))
|
|
43566
43575
|
.subscribe();
|
|
43567
43576
|
this.registerSubscription(this._dataSourceLoadingSubscription);
|
|
43568
43577
|
}
|
|
@@ -43615,7 +43624,7 @@ class AppTable {
|
|
|
43615
43624
|
}
|
|
43616
43625
|
}
|
|
43617
43626
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: AppTable, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
|
|
43618
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.22", type: AppTable, isStandalone: true, inputs: { settingsId: "settingsId", debug: "debug", i18nColumnPrefix: "i18nColumnPrefix", i18nColumnSuffix: "i18nColumnSuffix", autoLoad: "autoLoad", readOnly: "readOnly", inlineEdition: "inlineEdition", focusFirstColumn: "focusFirstColumn", confirmBeforeDelete: "confirmBeforeDelete", confirmBeforeCancel: "confirmBeforeCancel", undoableDeletion: "undoableDeletion", saveBeforeDelete: "saveBeforeDelete", keepEditedRowOnSave: "keepEditedRowOnSave", saveBeforeSort: "saveBeforeSort", saveBeforeFilter: "saveBeforeFilter", propagateRowError: "propagateRowError", permanentSelectionAllowed: "permanentSelectionAllowed", openRowThrottleTime: "openRowThrottleTime", defaultSortBy: "defaultSortBy", defaultSortDirection: "defaultSortDirection", defaultPageSize: "defaultPageSize", defaultPageSizeOptions: "defaultPageSizeOptions", focusColumn: "focusColumn", dataSource: "dataSource", filter: "filter", disabled: "disabled", paginator: "paginator" }, outputs: { onRefresh: "onRefresh", onClickRow: "onClickRow", onOpenRow: "onOpenRow", onNewRow: "onNewRow", onStartEditingRow: "onStartEditingRow", onConfirmEditCreateRow: "onConfirmEditCreateRow", onCancelOrDeleteRow: "onCancelOrDeleteRow", onBeforeDeleteRows: "onBeforeDeleteRows", onBeforeCancelRows: "onBeforeCancelRows", onBeforeSave: "onBeforeSave", onAfterDeletedRows: "onAfterDeletedRows", onSort: "onSort", onDirty: "onDirty", onError: "onError" }, viewQueries: [{ propertyName: "table", first: true, predicate: MatTable, descendants: true }, { propertyName: "childPaginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0 });
|
|
43627
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.22", type: AppTable, isStandalone: true, inputs: { settingsId: "settingsId", debug: "debug", i18nColumnPrefix: "i18nColumnPrefix", i18nColumnSuffix: "i18nColumnSuffix", autoLoad: "autoLoad", readOnly: "readOnly", inlineEdition: "inlineEdition", focusFirstColumn: "focusFirstColumn", confirmBeforeDelete: "confirmBeforeDelete", confirmBeforeCancel: "confirmBeforeCancel", undoableDeletion: "undoableDeletion", saveBeforeDelete: "saveBeforeDelete", keepEditedRowOnSave: "keepEditedRowOnSave", saveBeforeSort: "saveBeforeSort", saveBeforeFilter: "saveBeforeFilter", propagateRowError: "propagateRowError", permanentSelectionAllowed: "permanentSelectionAllowed", openRowThrottleTime: "openRowThrottleTime", loadingDebounceTime: "loadingDebounceTime", defaultSortBy: "defaultSortBy", defaultSortDirection: "defaultSortDirection", defaultPageSize: "defaultPageSize", defaultPageSizeOptions: "defaultPageSizeOptions", focusColumn: "focusColumn", dataSource: "dataSource", filter: "filter", disabled: "disabled", paginator: "paginator" }, outputs: { onRefresh: "onRefresh", onClickRow: "onClickRow", onOpenRow: "onOpenRow", onNewRow: "onNewRow", onStartEditingRow: "onStartEditingRow", onConfirmEditCreateRow: "onConfirmEditCreateRow", onCancelOrDeleteRow: "onCancelOrDeleteRow", onBeforeDeleteRows: "onBeforeDeleteRows", onBeforeCancelRows: "onBeforeCancelRows", onBeforeSave: "onBeforeSave", onAfterDeletedRows: "onAfterDeletedRows", onSort: "onSort", onDirty: "onDirty", onError: "onError" }, viewQueries: [{ propertyName: "table", first: true, predicate: MatTable, descendants: true }, { propertyName: "childPaginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0 });
|
|
43619
43628
|
}
|
|
43620
43629
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: AppTable, decorators: [{
|
|
43621
43630
|
type: Directive
|
|
@@ -43655,6 +43664,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
43655
43664
|
type: Input
|
|
43656
43665
|
}], openRowThrottleTime: [{
|
|
43657
43666
|
type: Input
|
|
43667
|
+
}], loadingDebounceTime: [{
|
|
43668
|
+
type: Input
|
|
43658
43669
|
}], defaultSortBy: [{
|
|
43659
43670
|
type: Input
|
|
43660
43671
|
}], defaultSortDirection: [{
|
|
@@ -44782,6 +44793,7 @@ class AppAsyncTable {
|
|
|
44782
44793
|
propagateRowError = false;
|
|
44783
44794
|
permanentSelectionAllowed = false; // Allow keep selected rows over page change or sort change
|
|
44784
44795
|
openRowThrottleTime = 800; // Prevent double click - see issue sumaris-app#864
|
|
44796
|
+
loadingDebounceTime = 250; // Debounce time for loading spinner
|
|
44785
44797
|
defaultSortBy;
|
|
44786
44798
|
defaultSortDirection;
|
|
44787
44799
|
defaultPageSize = DEFAULT_PAGE_SIZE;
|
|
@@ -45390,6 +45402,14 @@ class AppAsyncTable {
|
|
|
45390
45402
|
const row = await this.addRowToTable(insertAt, opts);
|
|
45391
45403
|
return !!row;
|
|
45392
45404
|
}
|
|
45405
|
+
/**
|
|
45406
|
+
* Save the table
|
|
45407
|
+
*
|
|
45408
|
+
* @param opts.keepEditing if true, the edited row will be kept after save (defaulting to this.keepEditedRowOnSave)
|
|
45409
|
+
* @param opts.keepSelection if true, the selected row will be kept after save (no effect if opts.keepEditing is true)
|
|
45410
|
+
* @param opts.confirmRow if true or not provided, the edited row will be confirmed before save
|
|
45411
|
+
* @param opts.throwIfNotConfirmed if true or not provided, an error will be thrown if the edited row is not confirmed
|
|
45412
|
+
*/
|
|
45393
45413
|
async save(opts) {
|
|
45394
45414
|
opts = {
|
|
45395
45415
|
keepEditing: this.keepEditedRowOnSave,
|
|
@@ -45399,15 +45419,22 @@ class AppAsyncTable {
|
|
|
45399
45419
|
throw { code: ErrorCodes.TABLE_READ_ONLY, message: 'ERROR.TABLE_READ_ONLY' };
|
|
45400
45420
|
}
|
|
45401
45421
|
this.resetError();
|
|
45402
|
-
// Keep edited row id (should be done BEFORE confirmEditCreate()
|
|
45422
|
+
// Keep edited row id (should be done BEFORE confirmEditCreate())
|
|
45403
45423
|
const editedRow = this.dataSource.getSingleEditingRow();
|
|
45404
|
-
this.previouslyEditedRowId = opts.keepEditing ? (editedRow?.editing ? editedRow.id : undefined)
|
|
45424
|
+
this.previouslyEditedRowId = opts.keepEditing ? ((editedRow?.editing ? editedRow.id : undefined) ?? this.singleSelectedRow?.id) : undefined;
|
|
45405
45425
|
const previouslyEditedRowId = this.previouslyEditedRowId;
|
|
45406
45426
|
const previouslyEditedData = opts.keepEditing
|
|
45407
|
-
? (isNotNil(previouslyEditedRowId) && editedRow?.currentData)
|
|
45427
|
+
? ((isNotNil(previouslyEditedRowId) && editedRow?.currentData) ?? this.singleSelectedRow?.currentData)
|
|
45408
45428
|
: undefined;
|
|
45409
|
-
|
|
45410
|
-
|
|
45429
|
+
const previouslySelectedRowIds = !opts.keepEditing && opts.keepSelection && this.selection.hasValue() ? this.selection.selected.map((row) => row.id) : undefined;
|
|
45430
|
+
if (opts.confirmRow !== false && !(await this.confirmEditCreate())) {
|
|
45431
|
+
if (opts.throwIfNotConfirmed !== false) {
|
|
45432
|
+
throw { code: ErrorCodes.TABLE_INVALID_ROW_ERROR, message: 'ERROR.TABLE_INVALID_ROW_ERROR' };
|
|
45433
|
+
}
|
|
45434
|
+
else {
|
|
45435
|
+
// Return false if confirmRow is true and throwIfNotConfirmed is false
|
|
45436
|
+
return false;
|
|
45437
|
+
}
|
|
45411
45438
|
}
|
|
45412
45439
|
// Mark as saving
|
|
45413
45440
|
this.markAsSaving();
|
|
@@ -45430,8 +45457,15 @@ class AppAsyncTable {
|
|
|
45430
45457
|
if (isNotNil(previouslyEditedRowId)) {
|
|
45431
45458
|
await this.selectRowByIdOrData(previouslyEditedRowId, previouslyEditedData);
|
|
45432
45459
|
}
|
|
45460
|
+
else if (isNotEmptyArray(previouslySelectedRowIds)) {
|
|
45461
|
+
await this.selectRowsByIds(previouslySelectedRowIds);
|
|
45462
|
+
}
|
|
45433
45463
|
}
|
|
45434
45464
|
}
|
|
45465
|
+
async selectRowsByIds(rowIds) {
|
|
45466
|
+
await this.waitIdle();
|
|
45467
|
+
this.selection.setSelection(...(this.dataSource?.getRows() ?? []).filter((row) => rowIds.includes(row.id)));
|
|
45468
|
+
}
|
|
45435
45469
|
async cancel(event, opts) {
|
|
45436
45470
|
// Check confirmation
|
|
45437
45471
|
if ((!opts || opts.interactive !== false) && this.dirty && (this.confirmBeforeCancel || this.onBeforeCancelRows.observed)) {
|
|
@@ -45812,8 +45846,11 @@ class AppAsyncTable {
|
|
|
45812
45846
|
return true; // Already the edited row
|
|
45813
45847
|
if (event?.defaultPrevented)
|
|
45814
45848
|
return false;
|
|
45815
|
-
|
|
45816
|
-
|
|
45849
|
+
// Don't call confirmEditCreate() if canceling
|
|
45850
|
+
if (!toBoolean(opts?.canceling, false)) {
|
|
45851
|
+
if (!(await this.confirmEditCreate())) {
|
|
45852
|
+
return false;
|
|
45853
|
+
}
|
|
45817
45854
|
}
|
|
45818
45855
|
if (!row.editing && !this.loading) {
|
|
45819
45856
|
this.focusColumn = opts?.focusColumn ?? this.focusColumn;
|
|
@@ -46354,6 +46391,7 @@ class AppAsyncTable {
|
|
|
46354
46391
|
// Make sure row will be cancelled, and NOT deleted
|
|
46355
46392
|
if (row.id === -1 || !row.editing)
|
|
46356
46393
|
throw new Error('Row cannot be canceling, but only deleting');
|
|
46394
|
+
// TODO invert this condition to call canCancelRows
|
|
46357
46395
|
const confirmed = !opts || opts.interactive !== false;
|
|
46358
46396
|
// Ask user confirmation, if cancel
|
|
46359
46397
|
if (!confirmed && row.dirty && (this.confirmBeforeCancel || this.onBeforeCancelRows.observed)) {
|
|
@@ -46369,7 +46407,7 @@ class AppAsyncTable {
|
|
|
46369
46407
|
await this.checkIfRowPristine(row);
|
|
46370
46408
|
// Restore editing state
|
|
46371
46409
|
if (keepEditing) {
|
|
46372
|
-
await this.editRow(undefined, row);
|
|
46410
|
+
await this.editRow(undefined, row, { canceling: true });
|
|
46373
46411
|
}
|
|
46374
46412
|
}
|
|
46375
46413
|
async checkIfRowPristine(row, opts) {
|
|
@@ -46419,7 +46457,7 @@ class AppAsyncTable {
|
|
|
46419
46457
|
// If changed to True: propagate as soon as possible
|
|
46420
46458
|
tap$1((loading) => loading && this.setLoading(true)),
|
|
46421
46459
|
// If changed to False: wait 250ms before propagate (to make sure the spinner has been displayed)
|
|
46422
|
-
debounceTime(
|
|
46460
|
+
debounceTime(this.loadingDebounceTime), tap$1((loading) => !loading && this.setLoading(false)))
|
|
46423
46461
|
.subscribe();
|
|
46424
46462
|
this.registerSubscription(this._dataSourceLoadingSubscription);
|
|
46425
46463
|
}
|
|
@@ -46471,7 +46509,7 @@ class AppAsyncTable {
|
|
|
46471
46509
|
}
|
|
46472
46510
|
}
|
|
46473
46511
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: AppAsyncTable, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
|
|
46474
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.22", type: AppAsyncTable, isStandalone: true, inputs: { settingsId: "settingsId", debug: "debug", i18nColumnPrefix: "i18nColumnPrefix", i18nColumnSuffix: "i18nColumnSuffix", autoLoad: "autoLoad", readOnly: "readOnly", inlineEdition: "inlineEdition", focusFirstColumn: "focusFirstColumn", confirmBeforeDelete: "confirmBeforeDelete", confirmBeforeCancel: "confirmBeforeCancel", undoableDeletion: "undoableDeletion", saveBeforeDelete: "saveBeforeDelete", keepEditedRowOnSave: "keepEditedRowOnSave", saveBeforeSort: "saveBeforeSort", saveBeforeFilter: "saveBeforeFilter", propagateRowError: "propagateRowError", permanentSelectionAllowed: "permanentSelectionAllowed", openRowThrottleTime: "openRowThrottleTime", defaultSortBy: "defaultSortBy", defaultSortDirection: "defaultSortDirection", defaultPageSize: "defaultPageSize", defaultPageSizeOptions: "defaultPageSizeOptions", focusColumn: "focusColumn", dataSource: "dataSource", filter: "filter", disabled: "disabled", paginator: "paginator" }, outputs: { onRefresh: "onRefresh", onClickRow: "onClickRow", onOpenRow: "onOpenRow", onNewRow: "onNewRow", onStartEditingRow: "onStartEditingRow", onConfirmEditCreateRow: "onConfirmEditCreateRow", onCancelOrDeleteRow: "onCancelOrDeleteRow", onBeforeDeleteRows: "onBeforeDeleteRows", onBeforeCancelRows: "onBeforeCancelRows", onBeforeSave: "onBeforeSave", onAfterDeletedRows: "onAfterDeletedRows", onSort: "onSort", onDirty: "onDirty", onError: "onError" }, viewQueries: [{ propertyName: "table", first: true, predicate: MatTable, descendants: true }, { propertyName: "childPaginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0 });
|
|
46512
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.22", type: AppAsyncTable, isStandalone: true, inputs: { settingsId: "settingsId", debug: "debug", i18nColumnPrefix: "i18nColumnPrefix", i18nColumnSuffix: "i18nColumnSuffix", autoLoad: "autoLoad", readOnly: "readOnly", inlineEdition: "inlineEdition", focusFirstColumn: "focusFirstColumn", confirmBeforeDelete: "confirmBeforeDelete", confirmBeforeCancel: "confirmBeforeCancel", undoableDeletion: "undoableDeletion", saveBeforeDelete: "saveBeforeDelete", keepEditedRowOnSave: "keepEditedRowOnSave", saveBeforeSort: "saveBeforeSort", saveBeforeFilter: "saveBeforeFilter", propagateRowError: "propagateRowError", permanentSelectionAllowed: "permanentSelectionAllowed", openRowThrottleTime: "openRowThrottleTime", loadingDebounceTime: "loadingDebounceTime", defaultSortBy: "defaultSortBy", defaultSortDirection: "defaultSortDirection", defaultPageSize: "defaultPageSize", defaultPageSizeOptions: "defaultPageSizeOptions", focusColumn: "focusColumn", dataSource: "dataSource", filter: "filter", disabled: "disabled", paginator: "paginator" }, outputs: { onRefresh: "onRefresh", onClickRow: "onClickRow", onOpenRow: "onOpenRow", onNewRow: "onNewRow", onStartEditingRow: "onStartEditingRow", onConfirmEditCreateRow: "onConfirmEditCreateRow", onCancelOrDeleteRow: "onCancelOrDeleteRow", onBeforeDeleteRows: "onBeforeDeleteRows", onBeforeCancelRows: "onBeforeCancelRows", onBeforeSave: "onBeforeSave", onAfterDeletedRows: "onAfterDeletedRows", onSort: "onSort", onDirty: "onDirty", onError: "onError" }, viewQueries: [{ propertyName: "table", first: true, predicate: MatTable, descendants: true }, { propertyName: "childPaginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0 });
|
|
46475
46513
|
}
|
|
46476
46514
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: AppAsyncTable, decorators: [{
|
|
46477
46515
|
type: Directive
|
|
@@ -46511,6 +46549,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
46511
46549
|
type: Input
|
|
46512
46550
|
}], openRowThrottleTime: [{
|
|
46513
46551
|
type: Input
|
|
46552
|
+
}], loadingDebounceTime: [{
|
|
46553
|
+
type: Input
|
|
46514
46554
|
}], defaultSortBy: [{
|
|
46515
46555
|
type: Input
|
|
46516
46556
|
}], defaultSortDirection: [{
|