@sumaris-net/ngx-components 21.3.2 → 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 +42 -1
- package/fesm2022/sumaris-net.ngx-components.mjs +55 -20
- 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 +19 -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`)
|
|
@@ -25934,14 +25934,14 @@ class AccountService extends BaseGraphqlService {
|
|
|
25934
25934
|
console.error('[account] Error while saving account locally: ' + ((err && err.message) || err), err);
|
|
25935
25935
|
}
|
|
25936
25936
|
}
|
|
25937
|
-
async saveLocalSettingsRemotely(source) {
|
|
25937
|
+
async saveLocalSettingsRemotely(source, force) {
|
|
25938
25938
|
if (!source || !this.isLogin() || source.accountInheritance === false)
|
|
25939
25939
|
return; // Skip
|
|
25940
25940
|
const account = this.account;
|
|
25941
25941
|
// Merge local settings into account's settings
|
|
25942
25942
|
const settings = UserSettings.fromObject(account.settings) || new UserSettings();
|
|
25943
25943
|
const changed = settings.merge(source, this._remoteLocalSettingsKeys);
|
|
25944
|
-
if (!changed)
|
|
25944
|
+
if (!changed && !force)
|
|
25945
25945
|
return; // Skip if unchanged
|
|
25946
25946
|
// Save remotely
|
|
25947
25947
|
this.account.settings = settings;
|
|
@@ -33898,11 +33898,11 @@ class AboutModal {
|
|
|
33898
33898
|
}
|
|
33899
33899
|
}
|
|
33900
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 });
|
|
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)\"\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 });
|
|
33902
33902
|
}
|
|
33903
33903
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: AboutModal, decorators: [{
|
|
33904
33904
|
type: Component,
|
|
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)\"
|
|
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"] }]
|
|
33906
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: [{
|
|
33907
33907
|
type: Optional
|
|
33908
33908
|
}, {
|
|
@@ -41914,6 +41914,7 @@ class AppTable {
|
|
|
41914
41914
|
propagateRowError = false;
|
|
41915
41915
|
permanentSelectionAllowed = false; // Allow keep selected rows over page change or sort change
|
|
41916
41916
|
openRowThrottleTime = 800; // Prevent double click - see issue sumaris-app#864
|
|
41917
|
+
loadingDebounceTime = 250; // Debounce time for loading spinner
|
|
41917
41918
|
defaultSortBy;
|
|
41918
41919
|
defaultSortDirection;
|
|
41919
41920
|
defaultPageSize = DEFAULT_PAGE_SIZE;
|
|
@@ -42952,9 +42953,12 @@ class AppTable {
|
|
|
42952
42953
|
return true; // Already the edited row
|
|
42953
42954
|
if (event?.defaultPrevented)
|
|
42954
42955
|
return false;
|
|
42955
|
-
|
|
42956
|
-
if (!
|
|
42957
|
-
|
|
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
|
+
}
|
|
42958
42962
|
if (!row.editing && !this.loading) {
|
|
42959
42963
|
this.focusColumn = opts?.focusColumn ?? this.focusColumn;
|
|
42960
42964
|
this._dataSource.startEdit(row);
|
|
@@ -43517,7 +43521,7 @@ class AppTable {
|
|
|
43517
43521
|
this.checkIfRowPristine(row);
|
|
43518
43522
|
// Restore editing state
|
|
43519
43523
|
if (keepEditing) {
|
|
43520
|
-
this.editRow(undefined, row);
|
|
43524
|
+
this.editRow(undefined, row, { canceling: true });
|
|
43521
43525
|
}
|
|
43522
43526
|
}
|
|
43523
43527
|
async checkIfRowPristine(row, opts) {
|
|
@@ -43567,7 +43571,7 @@ class AppTable {
|
|
|
43567
43571
|
// If changed to True: propagate as soon as possible
|
|
43568
43572
|
tap$1((loading) => loading && this.setLoading(true)),
|
|
43569
43573
|
// If changed to False: wait 250ms before propagate (to make sure the spinner has been displayed)
|
|
43570
|
-
debounceTime(
|
|
43574
|
+
debounceTime(this.loadingDebounceTime), tap$1((loading) => !loading && this.setLoading(false)))
|
|
43571
43575
|
.subscribe();
|
|
43572
43576
|
this.registerSubscription(this._dataSourceLoadingSubscription);
|
|
43573
43577
|
}
|
|
@@ -43620,7 +43624,7 @@ class AppTable {
|
|
|
43620
43624
|
}
|
|
43621
43625
|
}
|
|
43622
43626
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: AppTable, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
|
|
43623
|
-
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 });
|
|
43624
43628
|
}
|
|
43625
43629
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: AppTable, decorators: [{
|
|
43626
43630
|
type: Directive
|
|
@@ -43660,6 +43664,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
43660
43664
|
type: Input
|
|
43661
43665
|
}], openRowThrottleTime: [{
|
|
43662
43666
|
type: Input
|
|
43667
|
+
}], loadingDebounceTime: [{
|
|
43668
|
+
type: Input
|
|
43663
43669
|
}], defaultSortBy: [{
|
|
43664
43670
|
type: Input
|
|
43665
43671
|
}], defaultSortDirection: [{
|
|
@@ -44787,6 +44793,7 @@ class AppAsyncTable {
|
|
|
44787
44793
|
propagateRowError = false;
|
|
44788
44794
|
permanentSelectionAllowed = false; // Allow keep selected rows over page change or sort change
|
|
44789
44795
|
openRowThrottleTime = 800; // Prevent double click - see issue sumaris-app#864
|
|
44796
|
+
loadingDebounceTime = 250; // Debounce time for loading spinner
|
|
44790
44797
|
defaultSortBy;
|
|
44791
44798
|
defaultSortDirection;
|
|
44792
44799
|
defaultPageSize = DEFAULT_PAGE_SIZE;
|
|
@@ -45395,6 +45402,14 @@ class AppAsyncTable {
|
|
|
45395
45402
|
const row = await this.addRowToTable(insertAt, opts);
|
|
45396
45403
|
return !!row;
|
|
45397
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
|
+
*/
|
|
45398
45413
|
async save(opts) {
|
|
45399
45414
|
opts = {
|
|
45400
45415
|
keepEditing: this.keepEditedRowOnSave,
|
|
@@ -45404,15 +45419,22 @@ class AppAsyncTable {
|
|
|
45404
45419
|
throw { code: ErrorCodes.TABLE_READ_ONLY, message: 'ERROR.TABLE_READ_ONLY' };
|
|
45405
45420
|
}
|
|
45406
45421
|
this.resetError();
|
|
45407
|
-
// Keep edited row id (should be done BEFORE confirmEditCreate()
|
|
45422
|
+
// Keep edited row id (should be done BEFORE confirmEditCreate())
|
|
45408
45423
|
const editedRow = this.dataSource.getSingleEditingRow();
|
|
45409
|
-
this.previouslyEditedRowId = opts.keepEditing ? (editedRow?.editing ? editedRow.id : undefined)
|
|
45424
|
+
this.previouslyEditedRowId = opts.keepEditing ? ((editedRow?.editing ? editedRow.id : undefined) ?? this.singleSelectedRow?.id) : undefined;
|
|
45410
45425
|
const previouslyEditedRowId = this.previouslyEditedRowId;
|
|
45411
45426
|
const previouslyEditedData = opts.keepEditing
|
|
45412
|
-
? (isNotNil(previouslyEditedRowId) && editedRow?.currentData)
|
|
45427
|
+
? ((isNotNil(previouslyEditedRowId) && editedRow?.currentData) ?? this.singleSelectedRow?.currentData)
|
|
45413
45428
|
: undefined;
|
|
45414
|
-
|
|
45415
|
-
|
|
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
|
+
}
|
|
45416
45438
|
}
|
|
45417
45439
|
// Mark as saving
|
|
45418
45440
|
this.markAsSaving();
|
|
@@ -45435,8 +45457,15 @@ class AppAsyncTable {
|
|
|
45435
45457
|
if (isNotNil(previouslyEditedRowId)) {
|
|
45436
45458
|
await this.selectRowByIdOrData(previouslyEditedRowId, previouslyEditedData);
|
|
45437
45459
|
}
|
|
45460
|
+
else if (isNotEmptyArray(previouslySelectedRowIds)) {
|
|
45461
|
+
await this.selectRowsByIds(previouslySelectedRowIds);
|
|
45462
|
+
}
|
|
45438
45463
|
}
|
|
45439
45464
|
}
|
|
45465
|
+
async selectRowsByIds(rowIds) {
|
|
45466
|
+
await this.waitIdle();
|
|
45467
|
+
this.selection.setSelection(...(this.dataSource?.getRows() ?? []).filter((row) => rowIds.includes(row.id)));
|
|
45468
|
+
}
|
|
45440
45469
|
async cancel(event, opts) {
|
|
45441
45470
|
// Check confirmation
|
|
45442
45471
|
if ((!opts || opts.interactive !== false) && this.dirty && (this.confirmBeforeCancel || this.onBeforeCancelRows.observed)) {
|
|
@@ -45817,8 +45846,11 @@ class AppAsyncTable {
|
|
|
45817
45846
|
return true; // Already the edited row
|
|
45818
45847
|
if (event?.defaultPrevented)
|
|
45819
45848
|
return false;
|
|
45820
|
-
|
|
45821
|
-
|
|
45849
|
+
// Don't call confirmEditCreate() if canceling
|
|
45850
|
+
if (!toBoolean(opts?.canceling, false)) {
|
|
45851
|
+
if (!(await this.confirmEditCreate())) {
|
|
45852
|
+
return false;
|
|
45853
|
+
}
|
|
45822
45854
|
}
|
|
45823
45855
|
if (!row.editing && !this.loading) {
|
|
45824
45856
|
this.focusColumn = opts?.focusColumn ?? this.focusColumn;
|
|
@@ -46359,6 +46391,7 @@ class AppAsyncTable {
|
|
|
46359
46391
|
// Make sure row will be cancelled, and NOT deleted
|
|
46360
46392
|
if (row.id === -1 || !row.editing)
|
|
46361
46393
|
throw new Error('Row cannot be canceling, but only deleting');
|
|
46394
|
+
// TODO invert this condition to call canCancelRows
|
|
46362
46395
|
const confirmed = !opts || opts.interactive !== false;
|
|
46363
46396
|
// Ask user confirmation, if cancel
|
|
46364
46397
|
if (!confirmed && row.dirty && (this.confirmBeforeCancel || this.onBeforeCancelRows.observed)) {
|
|
@@ -46374,7 +46407,7 @@ class AppAsyncTable {
|
|
|
46374
46407
|
await this.checkIfRowPristine(row);
|
|
46375
46408
|
// Restore editing state
|
|
46376
46409
|
if (keepEditing) {
|
|
46377
|
-
await this.editRow(undefined, row);
|
|
46410
|
+
await this.editRow(undefined, row, { canceling: true });
|
|
46378
46411
|
}
|
|
46379
46412
|
}
|
|
46380
46413
|
async checkIfRowPristine(row, opts) {
|
|
@@ -46424,7 +46457,7 @@ class AppAsyncTable {
|
|
|
46424
46457
|
// If changed to True: propagate as soon as possible
|
|
46425
46458
|
tap$1((loading) => loading && this.setLoading(true)),
|
|
46426
46459
|
// If changed to False: wait 250ms before propagate (to make sure the spinner has been displayed)
|
|
46427
|
-
debounceTime(
|
|
46460
|
+
debounceTime(this.loadingDebounceTime), tap$1((loading) => !loading && this.setLoading(false)))
|
|
46428
46461
|
.subscribe();
|
|
46429
46462
|
this.registerSubscription(this._dataSourceLoadingSubscription);
|
|
46430
46463
|
}
|
|
@@ -46476,7 +46509,7 @@ class AppAsyncTable {
|
|
|
46476
46509
|
}
|
|
46477
46510
|
}
|
|
46478
46511
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: AppAsyncTable, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
|
|
46479
|
-
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 });
|
|
46480
46513
|
}
|
|
46481
46514
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: AppAsyncTable, decorators: [{
|
|
46482
46515
|
type: Directive
|
|
@@ -46516,6 +46549,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
46516
46549
|
type: Input
|
|
46517
46550
|
}], openRowThrottleTime: [{
|
|
46518
46551
|
type: Input
|
|
46552
|
+
}], loadingDebounceTime: [{
|
|
46553
|
+
type: Input
|
|
46519
46554
|
}], defaultSortBy: [{
|
|
46520
46555
|
type: Input
|
|
46521
46556
|
}], defaultSortDirection: [{
|