@cityway/bo-ui 1.1.0-beta010 → 1.1.0-beta011
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/cityway-bo-ui-1.1.0-beta011.tgz +0 -0
- package/docs/components/import-wizard.md +22 -2
- package/fesm2022/cityway-bo-ui.mjs +77 -23
- package/fesm2022/cityway-bo-ui.mjs.map +1 -1
- package/index.d.ts +52 -10
- package/lib/assets/i18n/common.en.json +40 -25
- package/lib/assets/i18n/common.fr.json +40 -33
- package/package.json +2 -2
- package/cityway-bo-ui-1.1.0-beta009.tgz +0 -0
- package/cityway-bo-ui-1.1.0-beta010.tgz +0 -0
|
Binary file
|
|
@@ -109,7 +109,7 @@ interface ImportWizardConfig {
|
|
|
109
109
|
modes: readonly [ImportModeOption, ...ImportModeOption[]]; // jamais vide ; 1 élément = imposé
|
|
110
110
|
interruption: ImportInterruption; // requis : ce qui se passe vraiment à l'arrêt
|
|
111
111
|
showErrorDetails?: boolean; // [Option]
|
|
112
|
-
scope
|
|
112
|
+
scope: ImportScope; // requis — voir ci-dessous
|
|
113
113
|
}
|
|
114
114
|
```
|
|
115
115
|
|
|
@@ -139,6 +139,26 @@ Aucune de ces situations n'est rattrapée à l'exécution : elles ne compilent p
|
|
|
139
139
|
|
|
140
140
|
---
|
|
141
141
|
|
|
142
|
+
### `scope` — et le nom de l'objet importé
|
|
143
|
+
|
|
144
|
+
`scope` est un type discriminé : le libellé de l'objet **voyage avec la portée qui l'affiche**, comme `keyFieldHint` voyage avec son mode. Indéclarable en `singleConfig` (aucun compteur n'y est rendu), inoubliable en `list`.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
scope: { kind: ImportScopeEnum.list, objectLabel: i18nKey('DOMAIN.USER') }
|
|
148
|
+
// ou
|
|
149
|
+
scope: { kind: ImportScopeEnum.singleConfig }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`objectLabel` est une clé **racine**, suffixée par le composant de la catégorie de pluriel de la langue active — exactement comme les libellés de compteur. Le bundle de l'application doit donc déclarer les trois catégories :
|
|
153
|
+
|
|
154
|
+
```jsonc
|
|
155
|
+
"DOMAIN": { "USER": { "one": "utilisateur", "many": "utilisateurs", "other": "utilisateurs" } }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Rendu : « **2 431** utilisateurs à ajouter ».
|
|
159
|
+
|
|
160
|
+
> ⚠️ **Accord.** Les libellés du *rapport* sont des participes passés accordés au masculin pluriel (« … ajoutés », « … supprimés »). Un nom féminin (« lignes ») donnerait « lignes ajoutés ». Le cas échéant, surcharger `_COMMON._IMPORT._COUNTS.REPORT_*` dans le bundle de l'application — le deepmerge du loader donne le dernier mot à l'application.
|
|
161
|
+
|
|
142
162
|
## 4. Exemples
|
|
143
163
|
|
|
144
164
|
### Import d'une liste, ouvert depuis le bouton *Importer* de l'en-tête
|
|
@@ -270,7 +290,7 @@ readonly configImport: ImportWizardConfig = {
|
|
|
270
290
|
accept: '.json',
|
|
271
291
|
acceptLabel: 'JSON',
|
|
272
292
|
modes: [{ mode: ImportModeEnum.replaceAll }], // un seul mode => imposé, lecture seule
|
|
273
|
-
scope: ImportScopeEnum.singleConfig,
|
|
293
|
+
scope: { kind: ImportScopeEnum.singleConfig }, // pas de compteurs, message de conformité
|
|
274
294
|
interruption: { kind: ImportInterruptionEnum.atomic }, // back-end transactionnel
|
|
275
295
|
source: { analyze: …, execute: … },
|
|
276
296
|
};
|
|
@@ -1229,22 +1229,32 @@ var ImportInterruptionEnum;
|
|
|
1229
1229
|
//
|
|
1230
1230
|
// Pas dans basic-ui : ce paquet est derriere une frontiere de publication, et rien ne
|
|
1231
1231
|
// presse. A remonter le jour ou basic-ui affichera lui aussi des decomptes.
|
|
1232
|
-
/**
|
|
1232
|
+
/**
|
|
1233
|
+
* Instances `Intl` mises en cache : une par langue, pas une par decompte affiche.
|
|
1234
|
+
*
|
|
1235
|
+
* `lang` peut etre `undefined` : c'est le cas ou le service de traduction n'a resolu NI
|
|
1236
|
+
* langue courante NI langue par defaut. `Intl` interprete `undefined` comme « prends la
|
|
1237
|
+
* locale de l'environnement », ce qui est la seule reponse honnete — un litteral y
|
|
1238
|
+
* imposerait la locale de l'auteur de la lib (cf. l'avertissement de `formatCount`).
|
|
1239
|
+
* La cle de cache `''` represente ce cas.
|
|
1240
|
+
*/
|
|
1233
1241
|
const PLURAL_RULES = new Map();
|
|
1234
1242
|
const NUMBER_FORMATS = new Map();
|
|
1235
1243
|
function pluralRules(lang) {
|
|
1236
|
-
|
|
1244
|
+
const key = lang ?? '';
|
|
1245
|
+
let rules = PLURAL_RULES.get(key);
|
|
1237
1246
|
if (!rules) {
|
|
1238
1247
|
rules = new Intl.PluralRules(lang);
|
|
1239
|
-
PLURAL_RULES.set(
|
|
1248
|
+
PLURAL_RULES.set(key, rules);
|
|
1240
1249
|
}
|
|
1241
1250
|
return rules;
|
|
1242
1251
|
}
|
|
1243
1252
|
function numberFormat(lang) {
|
|
1244
|
-
|
|
1253
|
+
const key = lang ?? '';
|
|
1254
|
+
let format = NUMBER_FORMATS.get(key);
|
|
1245
1255
|
if (!format) {
|
|
1246
1256
|
format = new Intl.NumberFormat(lang);
|
|
1247
|
-
NUMBER_FORMATS.set(
|
|
1257
|
+
NUMBER_FORMATS.set(key, format);
|
|
1248
1258
|
}
|
|
1249
1259
|
return format;
|
|
1250
1260
|
}
|
|
@@ -1310,12 +1320,20 @@ class ImportWizardComponent {
|
|
|
1310
1320
|
* Langue active, en signal : la categorie de pluriel et le format des nombres doivent
|
|
1311
1321
|
* suivre un changement de langue a chaud.
|
|
1312
1322
|
*/
|
|
1313
|
-
this.lang = toSignal(
|
|
1323
|
+
this.lang = toSignal(
|
|
1324
|
+
// Annotation explicite : `toSignal` impose que `initialValue` etende le type de la
|
|
1325
|
+
// source. Sans elle, la source est `string` et n'accepte pas `undefined`.
|
|
1326
|
+
this.translate.onLangChange.pipe(map((event) => event.lang)),
|
|
1314
1327
|
// `currentLang` et `getDefaultLang()` sont types `string` en @ngx-translate/core 16,
|
|
1315
|
-
//
|
|
1316
|
-
//
|
|
1317
|
-
//
|
|
1318
|
-
|
|
1328
|
+
// nullables a partir de la 17 (`Language | null`) : le `|| undefined` normalise, et le
|
|
1329
|
+
// contrat local devient `string | undefined`.
|
|
1330
|
+
//
|
|
1331
|
+
// Surtout, PAS de repli litteral (un `|| 'fr'` par exemple) : `lang` alimente
|
|
1332
|
+
// `Intl.PluralRules` / `Intl.NumberFormat`, et figer une locale imposerait les regles
|
|
1333
|
+
// de pluriel et les separateurs francais a une application qui n'a rien demande —
|
|
1334
|
+
// exactement ce que l'avertissement de `formatCount` proscrit. `undefined` laisse
|
|
1335
|
+
// `Intl` prendre la locale de l'environnement.
|
|
1336
|
+
{ initialValue: this.translate.currentLang || this.translate.getDefaultLang() || undefined });
|
|
1319
1337
|
/** Pont signal du control : un `computed` lisant `mode.value` resterait fige. */
|
|
1320
1338
|
this.modeValue = toSignal(this.mode.valueChanges, { initialValue: null });
|
|
1321
1339
|
/** Idem pour le fichier : meme piege de `computed` fige. */
|
|
@@ -1372,8 +1390,13 @@ class ImportWizardComponent {
|
|
|
1372
1390
|
this.spriteIcon = SpriteIconEnum;
|
|
1373
1391
|
this.loaderSize = LoaderSizeEnum;
|
|
1374
1392
|
this.progressType = ProgressBarTypeEnum;
|
|
1375
|
-
this.scope = computed(() => this.config().scope
|
|
1376
|
-
this.isList = computed(() => this.scope() === ImportScopeEnum.list, ...(ngDevMode ? [{ debugName: "isList" }] : []));
|
|
1393
|
+
this.scope = computed(() => this.config().scope, ...(ngDevMode ? [{ debugName: "scope" }] : []));
|
|
1394
|
+
this.isList = computed(() => this.scope().kind === ImportScopeEnum.list, ...(ngDevMode ? [{ debugName: "isList" }] : []));
|
|
1395
|
+
/** Cle racine du nom de l'objet importe. `undefined` hors scope `list`, ou rien n'est compte. */
|
|
1396
|
+
this.objectLabelKey = computed(() => {
|
|
1397
|
+
const scope = this.scope();
|
|
1398
|
+
return scope.kind === ImportScopeEnum.list ? scope.objectLabel : undefined;
|
|
1399
|
+
}, ...(ngDevMode ? [{ debugName: "objectLabelKey" }] : []));
|
|
1377
1400
|
this.interruption = computed(() => this.config().interruption, ...(ngDevMode ? [{ debugName: "interruption" }] : []));
|
|
1378
1401
|
/** Le bouton d'arret n'existe que si l'interruption est reellement possible. */
|
|
1379
1402
|
this.canInterrupt = computed(() => this.interruption().kind !== ImportInterruptionEnum.notInterruptible, ...(ngDevMode ? [{ debugName: "canInterrupt" }] : []));
|
|
@@ -1395,8 +1418,6 @@ class ImportWizardComponent {
|
|
|
1395
1418
|
this.modes = computed(() => this.config().modes, ...(ngDevMode ? [{ debugName: "modes" }] : []));
|
|
1396
1419
|
this.isModeImposed = computed(() => this.modes().length === 1, ...(ngDevMode ? [{ debugName: "isModeImposed" }] : []));
|
|
1397
1420
|
this.modeOptions = computed(() => this.modes().map(({ mode }) => ({ value: mode, label: ImportWizardComponent.MODE_LABEL[mode] })), ...(ngDevMode ? [{ debugName: "modeOptions" }] : []));
|
|
1398
|
-
/** Libelle du mode quand il est impose (affiche en lecture seule, sans groupe radio). */
|
|
1399
|
-
this.imposedModeLabel = computed(() => ImportWizardComponent.MODE_LABEL[this.modes()[0].mode], ...(ngDevMode ? [{ debugName: "imposedModeLabel" }] : []));
|
|
1400
1421
|
/**
|
|
1401
1422
|
* Mode retenu : le seul propose s'il est impose, sinon la selection de l'utilisateur.
|
|
1402
1423
|
* `null` en choix libre tant qu'il n'a pas choisi — il n'y a AUCUNE preselection, sous
|
|
@@ -1426,11 +1447,15 @@ class ImportWizardComponent {
|
|
|
1426
1447
|
}, ...(ngDevMode ? [{ debugName: "selectedKeyFieldHint" }] : []));
|
|
1427
1448
|
/** Avertissement sur la portee du mode retenu : `replaceAll` est destructif, les autres non. */
|
|
1428
1449
|
this.modeNoticeType = computed(() => this.selectedMode() === ImportModeEnum.replaceAll ? AlertTypeEnum.warning : AlertTypeEnum.info, ...(ngDevMode ? [{ debugName: "modeNoticeType" }] : []));
|
|
1429
|
-
/**
|
|
1430
|
-
|
|
1450
|
+
/**
|
|
1451
|
+
* Contenu de l'alerte de portee : une phrase d'introduction, et pour les modes de fusion
|
|
1452
|
+
* la liste a puces de la maquette. `null` tant qu'aucun mode n'est retenu — l'alerte ne
|
|
1453
|
+
* decrit jamais un mode que l'utilisateur n'a pas choisi.
|
|
1454
|
+
*/
|
|
1455
|
+
this.modeNotice = computed(() => {
|
|
1431
1456
|
const selected = this.selectedMode();
|
|
1432
|
-
return selected ? ImportWizardComponent.MODE_NOTICE[selected] :
|
|
1433
|
-
}, ...(ngDevMode ? [{ debugName: "
|
|
1457
|
+
return selected ? ImportWizardComponent.MODE_NOTICE[selected] : null;
|
|
1458
|
+
}, ...(ngDevMode ? [{ debugName: "modeNotice" }] : []));
|
|
1434
1459
|
/**
|
|
1435
1460
|
* `cw-file-to-download` attend des `Attachment`, dont la taille est en Ko : conversion
|
|
1436
1461
|
* a la frontiere. Le contrat public, lui, n'a qu'une seule unite — l'octet.
|
|
@@ -1457,6 +1482,18 @@ class ImportWizardComponent {
|
|
|
1457
1482
|
* `computed` lisant la langue : categorie de pluriel et separateur de milliers suivent
|
|
1458
1483
|
* un changement de langue a chaud.
|
|
1459
1484
|
*/
|
|
1485
|
+
/** « N erreurs » : meme rendu que les compteurs (nombre colore + libelle accorde). */
|
|
1486
|
+
this.errorCountRow = computed(() => {
|
|
1487
|
+
const total = this.errors().length;
|
|
1488
|
+
if (!total)
|
|
1489
|
+
return null;
|
|
1490
|
+
const lang = this.lang();
|
|
1491
|
+
return {
|
|
1492
|
+
key: pluralizedCountKey('_COMMON._IMPORT._COUNTS.ERRORS', total, lang),
|
|
1493
|
+
count: formatCount(total, lang),
|
|
1494
|
+
tone: 'danger',
|
|
1495
|
+
};
|
|
1496
|
+
}, ...(ngDevMode ? [{ debugName: "errorCountRow" }] : []));
|
|
1460
1497
|
this.previewRows = computed(() => this.countRows(this.analysis()?.counts, 'PREVIEW'), ...(ngDevMode ? [{ debugName: "previewRows" }] : []));
|
|
1461
1498
|
this.reportRows = computed(() => this.countRows(this.report()?.counts, 'REPORT'), ...(ngDevMode ? [{ debugName: "reportRows" }] : []));
|
|
1462
1499
|
// `cw-card.collapsed` n'est qu'un etat INITIAL (lu au ngOnInit) : le rebinder ne replie
|
|
@@ -1497,9 +1534,20 @@ class ImportWizardComponent {
|
|
|
1497
1534
|
[ImportModeEnum.replaceAll]: '_COMMON._IMPORT._MODE.REPLACE_ALL',
|
|
1498
1535
|
}; }
|
|
1499
1536
|
static { this.MODE_NOTICE = {
|
|
1500
|
-
[ImportModeEnum.addOnly]:
|
|
1501
|
-
|
|
1502
|
-
|
|
1537
|
+
[ImportModeEnum.addOnly]: {
|
|
1538
|
+
lead: '_COMMON._IMPORT._MODE.MERGE_LEAD',
|
|
1539
|
+
items: ['_COMMON._IMPORT._MODE.MERGE_ADDED', '_COMMON._IMPORT._MODE.MERGE_UNCHANGED'],
|
|
1540
|
+
},
|
|
1541
|
+
[ImportModeEnum.upsert]: {
|
|
1542
|
+
lead: '_COMMON._IMPORT._MODE.MERGE_LEAD',
|
|
1543
|
+
items: [
|
|
1544
|
+
'_COMMON._IMPORT._MODE.MERGE_ADDED',
|
|
1545
|
+
'_COMMON._IMPORT._MODE.MERGE_UPDATED',
|
|
1546
|
+
'_COMMON._IMPORT._MODE.MERGE_UNCHANGED',
|
|
1547
|
+
],
|
|
1548
|
+
},
|
|
1549
|
+
// Ecrasement : une seule phrase, sans puces (cf. maquette).
|
|
1550
|
+
[ImportModeEnum.replaceAll]: { lead: '_COMMON._IMPORT._MODE.REPLACE_ALL_NOTICE', items: [] },
|
|
1503
1551
|
}; }
|
|
1504
1552
|
static { this.INTERRUPTION_MESSAGE = {
|
|
1505
1553
|
[ImportInterruptionStateEnum.unchanged]: '_COMMON._IMPORT._INTERRUPTED.UNCHANGED',
|
|
@@ -1695,6 +1743,9 @@ class ImportWizardComponent {
|
|
|
1695
1743
|
if (!counts)
|
|
1696
1744
|
return [];
|
|
1697
1745
|
const lang = this.lang();
|
|
1746
|
+
// Le nom de l'objet est lui-meme accorde au nombre : sa cle racine porte les memes
|
|
1747
|
+
// categories de pluriel que les libelles de compteur (cf. `ImportScope.objectLabel`).
|
|
1748
|
+
const labelKey = this.objectLabelKey();
|
|
1698
1749
|
const rows = [
|
|
1699
1750
|
['CREATED', counts.created, 'success'],
|
|
1700
1751
|
['UPDATED', counts.updated, 'primary'],
|
|
@@ -1705,12 +1756,15 @@ class ImportWizardComponent {
|
|
|
1705
1756
|
.filter((row) => row[1] !== undefined)
|
|
1706
1757
|
.map(([name, value, tone]) => ({
|
|
1707
1758
|
key: pluralizedCountKey(`_COMMON._IMPORT._COUNTS.${prefix}_${name}`, value, lang),
|
|
1759
|
+
params: labelKey
|
|
1760
|
+
? { label: this.translate.instant(pluralizedCountKey(labelKey, value, lang)) }
|
|
1761
|
+
: undefined,
|
|
1708
1762
|
count: formatCount(value, lang),
|
|
1709
1763
|
tone,
|
|
1710
1764
|
}));
|
|
1711
1765
|
}
|
|
1712
1766
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.20", ngImport: i0, type: ImportWizardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1713
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.20", type: ImportWizardComponent, isStandalone: true, selector: "cw-import-wizard", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { completed: "completed" }, host: { classAttribute: "cw-import-wizard" }, viewQueries: [{ propertyName: "panels", predicate: ["stepPanel"], descendants: true, read: CardComponent, isSignal: true }, { propertyName: "selectionTarget", first: true, predicate: ["selectionTarget"], descendants: true, isSignal: true }, { propertyName: "analysisStatus", first: true, predicate: ["analysisStatus"], descendants: true, isSignal: true }, { propertyName: "analysisOutcome", first: true, predicate: ["analysisOutcome"], descendants: true, isSignal: true }, { propertyName: "executionStatus", first: true, predicate: ["executionStatus"], descendants: true, isSignal: true }, { propertyName: "cancellingStatus", first: true, predicate: ["cancellingStatus"], descendants: true, isSignal: true }, { propertyName: "reportOutcome", first: true, predicate: ["reportOutcome"], descendants: true, isSignal: true }, { propertyName: "interruptedOutcome", first: true, predicate: ["interruptedOutcome"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Pastille d'etape : icone ronde verte (terminee) / rouge (echec), sinon le numero.\n Rendue dans [header-start] pour etre AVANT le titre, comme sur les maquettes.\n Le numero seul n'etant pas parlant a l'oreille, le statut est double en visually-hidden --\n et c'est LUI qui distingue \u00AB en cours \u00BB de \u00AB a venir \u00BB pour qui ne voit pas la couleur. -->\n<ng-template #badge let-stepKey=\"stepKey\" let-number=\"number\">\n @if (stateOf(stepKey) === stepState.done) {\n <cw-icon [icon]=\"spriteIcon.check\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.success\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else if (stateOf(stepKey) === stepState.error) {\n <cw-icon [icon]=\"spriteIcon.close\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.danger\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else {\n <div class=\"icon-circular icon-circular-sm\" [class]=\"'icon-circular-' + badgeVariant(stepKey)\">\n <span aria-hidden=\"true\">{{ number }}</span>\n <span class=\"visually-hidden\">{{ stepStatusLabel(stepKey) | translate }}</span>\n </div>\n }\n</ng-template>\n\n<ng-template #counts let-rows=\"rows\">\n <ul class=\"list-unstyled d-flex-h gap-md\">\n @for (row of rows; track row.key) {\n <li>\n <span class=\"fs-lg fw-bold\" [class]=\"'text-' + row.tone\">{{ row.count }}</span> {{ row.key | translate }}\n </li>\n }\n </ul>\n</ng-template>\n\n<cw-accordion [closeOthers]=\"true\" [destroyOnHide]=\"false\">\n\n <!-- Etape 1 : fichier + type d'import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.SELECT'\"\n [collapseId]=\"'import-step-select'\"\n [collapsed]=\"!isPanelOpen(step.select)\"\n [collapseLocked]=\"stateOf(step.select) === stepState.done\"\n [disabled]=\"stateOf(step.select) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.select, number: 1 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n <div class=\"row\">\n <div class=\"col-lg-6\">\n <!-- Retour depuis une analyse non conforme : cible de focus. Le bouton qui declenche\n ce retour disparait avec le repli du panneau 2, le focus retomberait sur <body>. -->\n @if (selectionReopened()) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #selectionTarget>\n {{ '_COMMON._IMPORT._A11Y.SELECTION' | translate }}\n </h3>\n }\n\n <cw-file-drop\n [formControl]=\"file\"\n [accept]=\"config().accept\"\n [acceptLabel]=\"config().acceptLabel\"\n [maxSizeMb]=\"maxSizeMb()\"\n [label]=\"'_COMMON._IMPORT.FILE_TO_IMPORT' | translate\">\n </cw-file-drop>\n </div>\n\n @if (config().templateFile) {\n <div class=\"col-lg-6\">\n <cw-card [type]=\"cardType.light\">\n <strong>{{ '_COMMON._IMPORT._TEMPLATE.TITLE' | translate }}</strong>\n <span>{{ '_COMMON._IMPORT._TEMPLATE.TEXT' | translate }}</span>\n <cw-file-to-download [attachments]=\"templateAttachments()\"></cw-file-to-download>\n </cw-card>\n </div>\n }\n </div>\n\n <!-- Choix impose : on informe sans champ de saisie. Choix libre : groupe radio,\n dont la selection est REQUISE (pas de preselection, cf. `selectedMode`). -->\n @if (isModeImposed()) {\n <div class=\"d-flex-v gap-2xs\">\n <p class=\"mb-none\">\n <strong>{{ '_COMMON._IMPORT._MODE.LABEL' | translate }}</strong>\n {{ imposedModeLabel() | translate }}\n </p>\n <!-- Champ cle affiche ici AUSSI : c'est le cas le plus courant, et le seul ou\n l'utilisateur ne voyait pas sur quoi portait le rapprochement. -->\n @if (selectedKeyFieldHint(); as keyFieldHint) {\n <span class=\"fs fs-xs text-light\">{{ keyFieldHint | translate }}</span>\n }\n </div>\n }\n @else {\n <cw-form-field\n [type]=\"fieldType.radio\"\n [control]=\"mode\"\n [name]=\"'importMode'\"\n [label]=\"'_COMMON._IMPORT._MODE.LABEL'\"\n [options]=\"modeOptions()\"\n [isRequired]=\"true\"\n [help]=\"selectedKeyFieldHint()\">\n </cw-form-field>\n }\n\n <!-- Portee du mode retenu (destructif ou non) : exigence SFD \u00AB l'utilisateur est\n informe du type d'import selectionne ou impose \u00BB. Rien tant qu'aucun mode n'est\n retenu : l'alerte ne decrit jamais un mode que l'utilisateur n'a pas choisi. -->\n @if (modeNoticeKey(); as noticeKey) {\n <cw-alert [type]=\"modeNoticeType()\">\n {{ noticeKey | translate }}\n </cw-alert>\n }\n\n <!-- Un bouton desactive muet est un cul-de-sac : ce texte dit ce qui manque, et il est\n place AVANT le bouton, seul chemin possible puisqu'un bouton desactive n'est pas\n focusable (donc pas d'aria-describedby exploitable). Pas de classe de couleur\n atenuee : c'est une information necessaire, elle garde le contraste du corps. -->\n <div class=\"d-flex-h justify-end gap-md\">\n @if (missingRequirementKey(); as missing) {\n <p class=\"mb-none fs-xs\">{{ missing | translate }}</p>\n }\n\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.CONTINUE'\"\n [dataTest]=\"'btn-import-continue'\"\n [isDisabled]=\"!canContinue()\"\n (btnAction)=\"continueToAnalysis()\">\n </cw-button>\n </div>\n </div>\n </cw-card>\n\n <!-- Etape 2 : analyse a blanc -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.ANALYSIS'\"\n [collapseId]=\"'import-step-analysis'\"\n [collapsed]=\"!isPanelOpen(step.analysis)\"\n [collapseLocked]=\"stateOf(step.analysis) === stepState.done\"\n [disabled]=\"stateOf(step.analysis) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.analysis, number: 2 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (analyzing()) {\n <!-- Cible de focus : ce texte EST le message d'etat, il est donc lu une fois et une\n seule (aucune region live en doublon). -->\n <span tabindex=\"-1\" #analysisStatus>{{ '_COMMON._IMPORT.ANALYSIS_IN_PROGRESS' | translate }}</span>\n }\n @else if (analysis(); as result) {\n <!-- Cible de focus a l'arrivee du resultat : libelle de section, pas une reprise du\n message, pour que la lecture enchaine sur l'alerte puis les compteurs. -->\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #analysisOutcome>\n {{ '_COMMON._IMPORT._A11Y.ANALYSIS_RESULT' | translate }}\n </h3>\n\n @if (result.valid) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.ANALYSIS_OK' | translate }}\n </cw-alert>\n\n <!-- scope=list : previsualisation chiffree. scope=singleConfig : le message ci-dessus suffit. -->\n @if (isList() && previewRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: previewRows() }\"></ng-container>\n }\n\n <div class=\"list-unstyled d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.START'\"\n [dataTest]=\"'btn-import-start'\"\n (btnAction)=\"startImport()\">\n </cw-button>\n </div>\n }\n @else {\n <cw-alert [type]=\"alertType.danger\">\n {{ '_COMMON._IMPORT.ANALYSIS_KO' | translate }}\n </cw-alert>\n\n @if (errors().length) {\n <ul class=\"mb-none\">\n @for (issue of errors(); track $index) {\n <li>\n @if (issue.line !== undefined) {\n <strong>{{ '_COMMON._IMPORT.ERROR_LINE' | translate: { line: issue.line } }}</strong>\n }\n {{ issue.messageKey ? (issue.messageKey | translate: issue.messageParams) : issue.message }}\n </li>\n }\n </ul>\n }\n\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.BACK_TO_SELECTION'\"\n [dataTest]=\"'btn-import-back'\"\n (btnAction)=\"backToSelection()\">\n </cw-button>\n </div>\n }\n }\n </div>\n </cw-card>\n\n <!-- Etape 3 : import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.EXECUTION'\"\n [collapseId]=\"'import-step-execution'\"\n [collapsed]=\"!isPanelOpen(step.execution)\"\n [disabled]=\"stateOf(step.execution) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.execution, number: 3 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (isImporting) {\n <div class=\"d-flex-v gap-2xs\">\n <!-- Cible de focus. Le bouton \u00AB Arreter l'import \u00BB est le focusable suivant. -->\n <span tabindex=\"-1\" #executionStatus>{{ '_COMMON._IMPORT.IN_PROGRESS' | translate }}</span>\n @if (progress()?.remainingLabel; as remaining) {\n <span>{{ '_COMMON._IMPORT.REMAINING' | translate: { value: remaining } }}</span>\n }\n </div>\n\n <!-- Avancement connu => barre deterministe. Aucune progression recue => spinner\n indetermine, plutot qu'une barre figee a 0 %. La bascule se fait a la premiere\n emission `ImportProgress` : une source qui n'en emet jamais reste sur le spinner.\n `displayText` a false : le libelle \u00AB Import en cours \u00BB est deja au-dessus. -->\n @if (progress(); as current) {\n <cw-progress-bar\n [value]=\"current.percent\"\n [type]=\"progressType.information\"\n [label]=\"'_COMMON._IMPORT.IN_PROGRESS' | translate\">\n </cw-progress-bar>\n }\n @else {\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n }\n\n <!-- Pas de bouton d'arret quand l'objet declare l'import non interruptible : un\n bouton qui ne peut rien arreter serait pire que son absence. -->\n @if (canInterrupt()) {\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.dangerOutline\"\n [label]=\"'_COMMON._IMPORT.STOP'\"\n [dataTest]=\"'btn-import-stop'\"\n (btnAction)=\"stopImport()\">\n </cw-button>\n </div>\n }\n }\n @else if (cancelling()) {\n <!-- Annulation demandee au serveur, reponse en attente. Cible de focus. -->\n <span tabindex=\"-1\" #cancellingStatus>{{ '_COMMON._IMPORT.CANCELLING' | translate }}</span>\n\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n\n <!-- Sortie de secours : aucun delai n'etant defendable depuis la lib, c'est\n l'utilisateur qui decide d'arreter d'attendre. Conclut en \u00AB etat incertain \u00BB. -->\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.STOP_WAITING'\"\n [dataTest]=\"'btn-import-stop-waiting'\"\n (btnAction)=\"stopWaiting()\">\n </cw-button>\n </div>\n }\n @else if (interruptionMessageKey(); as interruptionMessage) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #interruptedOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n <cw-alert [type]=\"interruptionAlertType()\">\n {{ interruptionMessage | translate }}\n </cw-alert>\n }\n @else if (report(); as result) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #reportOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n @if (result.success) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.SUCCESS' | translate }}\n </cw-alert>\n\n @if (isList() && reportRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: reportRows() }\"></ng-container>\n }\n }\n @else {\n <!-- Le message generique ne promet l'absence d'ecriture que si l'atomicite est\n declaree (cf. `failureMessageKey`). -->\n <cw-alert [type]=\"alertType.danger\">\n {{ result.errorMessage ?? (failureMessageKey() | translate) }}\n </cw-alert>\n }\n }\n </div>\n </cw-card>\n\n</cw-accordion>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: AccordionComponent, selector: "cw-accordion", inputs: ["closeOthers", "allowMultiple", "destroyOnHide"], outputs: ["panelOpened", "panelClosed"] }, { kind: "component", type: AlertComponent, selector: "cw-alert", inputs: ["type", "title", "isDismissible", "cssClass", "dataTest"], outputs: ["close"] }, { kind: "component", type: ButtonComponent, selector: "cw-button", inputs: ["type", "label", "id", "link", "isDropdown", "hideCaret", "withIcon", "withOnlyIcon", "iconSize", "iconPosition", "dropdownPosition", "isMinWidth", "isDisabled", "isLoading", "loadingAnnounce", "cssClass", "dataTest", "skeletonMode", "withTooltip", "tooltipText", "tooltipTitle", "tooltipTrigger", "tooltipPosition", "tooltipType"], outputs: ["btnAction", "dropDownClicked"] }, { kind: "component", type: CardComponent, selector: "cw-card", inputs: ["title", "subtitle", "type", "titleRank", "cssClass", "collapsible", "collapsed", "disabled", "collapseLocked", "collapseStretched", "collapseId", "destroyOnHide"], outputs: ["collapsibleChange", "destroyOnHideChange", "collapseClicked"] }, { kind: "component", type: FileDropComponent, selector: "cw-file-drop", inputs: ["mode", "multiple", "maxFiles", "accept", "acceptLabel", "maxSizeMb", "recommendedDimensions", "label", "isDisabled", "errorMessage", "dataTest"], outputs: ["filesChange", "rejected", "retry"] }, { kind: "component", type: FileToDownloadComponent, selector: "cw-file-to-download", inputs: ["attachments"] }, { kind: "component", type: FormFieldComponent, selector: "cw-form-field", inputs: ["control", "formGroup", "type", "label", "displayLabel", "name", "placeholder", "autocomplete", "options", "rangeOptions", "validationOptions", "autocompleteOptions", "richTextOptions", "errorMessages", "isRequired", "help", "tooltip", "addonLeft", "addonRight", "isInAddon", "isReadonly", "isDisabled", "sliderValue", "sliderOptions", "cssClass", "skeletonMode", "clearable", "appendTo", "initialCountry", "indeterminate"], outputs: ["refreshEvent", "textChanged", "valueChange", "phoneRegionChange"] }, { kind: "component", type: IconComponent, selector: "cw-icon", inputs: ["icon", "label", "type", "iconSize", "iconCssClass", "iconUiType", "iconColorCustom", "spritePath"] }, { kind: "component", type: LoaderComponent, selector: "cw-loader", inputs: ["size", "displayText"] }, { kind: "component", type: ProgressBarComponent, selector: "cw-progress-bar", inputs: ["value", "max", "size", "type", "showValue", "label", "dataTest"] }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1767
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.20", type: ImportWizardComponent, isStandalone: true, selector: "cw-import-wizard", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { completed: "completed" }, host: { classAttribute: "cw-import-wizard" }, viewQueries: [{ propertyName: "panels", predicate: ["stepPanel"], descendants: true, read: CardComponent, isSignal: true }, { propertyName: "selectionTarget", first: true, predicate: ["selectionTarget"], descendants: true, isSignal: true }, { propertyName: "analysisStatus", first: true, predicate: ["analysisStatus"], descendants: true, isSignal: true }, { propertyName: "analysisOutcome", first: true, predicate: ["analysisOutcome"], descendants: true, isSignal: true }, { propertyName: "executionStatus", first: true, predicate: ["executionStatus"], descendants: true, isSignal: true }, { propertyName: "cancellingStatus", first: true, predicate: ["cancellingStatus"], descendants: true, isSignal: true }, { propertyName: "reportOutcome", first: true, predicate: ["reportOutcome"], descendants: true, isSignal: true }, { propertyName: "interruptedOutcome", first: true, predicate: ["interruptedOutcome"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Pastille d'etape : icone ronde verte (terminee) / rouge (echec), sinon le numero.\n Rendue dans [header-start] pour etre AVANT le titre, comme sur les maquettes.\n Le numero seul n'etant pas parlant a l'oreille, le statut est double en visually-hidden --\n et c'est LUI qui distingue \u00AB en cours \u00BB de \u00AB a venir \u00BB pour qui ne voit pas la couleur. -->\n<ng-template #badge let-stepKey=\"stepKey\" let-number=\"number\">\n @if (stateOf(stepKey) === stepState.done) {\n <cw-icon [icon]=\"spriteIcon.check\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.success\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else if (stateOf(stepKey) === stepState.error) {\n <cw-icon [icon]=\"spriteIcon.close\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.danger\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else {\n <div class=\"icon-circular icon-circular-sm\" [class]=\"'icon-circular-' + badgeVariant(stepKey)\">\n <span aria-hidden=\"true\">{{ number }}</span>\n <span class=\"visually-hidden\">{{ stepStatusLabel(stepKey) | translate }}</span>\n </div>\n }\n</ng-template>\n\n<ng-template #counts let-rows=\"rows\">\n <ul class=\"list-unstyled d-flex-h gap-md\">\n @for (row of rows; track row.key) {\n <li>\n <span class=\"fs-lg fw-bold\" [class]=\"'text-' + row.tone\">{{ row.count }}</span> {{ row.key | translate: row.params }}\n </li>\n }\n </ul>\n</ng-template>\n\n<cw-accordion [closeOthers]=\"true\" [destroyOnHide]=\"false\">\n\n <!-- Etape 1 : fichier + type d'import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.SELECT'\"\n [collapseId]=\"'import-step-select'\"\n [collapsed]=\"!isPanelOpen(step.select)\"\n [collapseLocked]=\"stateOf(step.select) === stepState.done\"\n [disabled]=\"stateOf(step.select) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.select, number: 1 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n <div class=\"row\">\n <div class=\"col-lg-6\">\n <!-- Retour depuis une analyse non conforme : cible de focus. Le bouton qui declenche\n ce retour disparait avec le repli du panneau 2, le focus retomberait sur <body>. -->\n @if (selectionReopened()) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #selectionTarget>\n {{ '_COMMON._IMPORT._A11Y.SELECTION' | translate }}\n </h3>\n }\n\n <cw-file-drop\n [formControl]=\"file\"\n [accept]=\"config().accept\"\n [acceptLabel]=\"config().acceptLabel\"\n [maxSizeMb]=\"maxSizeMb()\"\n [label]=\"'_COMMON._IMPORT.FILE_TO_IMPORT' | translate\">\n </cw-file-drop>\n </div>\n\n @if (config().templateFile) {\n <div class=\"col-lg-6\">\n <cw-card [type]=\"cardType.light\">\n <strong>{{ '_COMMON._IMPORT._TEMPLATE.TITLE' | translate }}</strong>\n <span>{{ '_COMMON._IMPORT._TEMPLATE.TEXT' | translate }}</span>\n <cw-file-to-download [attachments]=\"templateAttachments()\"></cw-file-to-download>\n </cw-card>\n </div>\n }\n </div>\n\n <!-- Mode IMPOSE : aucun champ, et pas davantage de ligne \u00AB Type d'import : ... \u00BB \u2014 la\n maquette ne rend que l'alerte de portee ci-dessous, qui informe deja du mode retenu.\n Le champ cle de rapprochement y est replie (voir l'alerte).\n Choix LIBRE : groupe radio, dont la selection est REQUISE (pas de preselection). -->\n @if (!isModeImposed()) {\n <cw-form-field\n [type]=\"fieldType.radio\"\n [control]=\"mode\"\n [name]=\"'importMode'\"\n [label]=\"'_COMMON._IMPORT._MODE.LABEL'\"\n [options]=\"modeOptions()\"\n [isRequired]=\"true\"\n [help]=\"selectedKeyFieldHint()\">\n </cw-form-field>\n }\n\n <!-- Portee du mode retenu (destructif ou non) : exigence SFD \u00AB l'utilisateur est\n informe du type d'import selectionne ou impose \u00BB. Rien tant qu'aucun mode n'est\n retenu : l'alerte ne decrit jamais un mode que l'utilisateur n'a pas choisi. -->\n @if (modeNotice(); as notice) {\n <cw-alert [type]=\"modeNoticeType()\">\n {{ notice.lead | translate }}\n @if (notice.items.length) {\n <ul class=\"mb-none\">\n @for (item of notice.items; track $index) {\n <li>{{ item | translate }}</li>\n }\n </ul>\n }\n <!-- Mode impose : l'alerte est le SEUL endroit qui reste pour dire sur quoi porte\n le rapprochement. En choix libre, c'est l'aide du groupe radio qui le porte. -->\n @if (isModeImposed() && selectedKeyFieldHint(); as keyFieldHint) {\n <p class=\"mb-none\">{{ keyFieldHint | translate }}</p>\n }\n </cw-alert>\n }\n\n <!-- Un bouton desactive muet est un cul-de-sac : ce texte dit ce qui manque, et il est\n place AVANT le bouton, seul chemin possible puisqu'un bouton desactive n'est pas\n focusable (donc pas d'aria-describedby exploitable). Pas de classe de couleur\n atenuee : c'est une information necessaire, elle garde le contraste du corps. -->\n <div class=\"d-flex-h justify-end gap-md\">\n @if (missingRequirementKey(); as missing) {\n <p class=\"mb-none fs-xs\">{{ missing | translate }}</p>\n }\n\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.CONTINUE'\"\n [dataTest]=\"'btn-import-continue'\"\n [isDisabled]=\"!canContinue()\"\n (btnAction)=\"continueToAnalysis()\">\n </cw-button>\n </div>\n </div>\n </cw-card>\n\n <!-- Etape 2 : analyse a blanc -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.ANALYSIS'\"\n [collapseId]=\"'import-step-analysis'\"\n [collapsed]=\"!isPanelOpen(step.analysis)\"\n [collapseLocked]=\"stateOf(step.analysis) === stepState.done\"\n [disabled]=\"stateOf(step.analysis) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.analysis, number: 2 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (analyzing()) {\n <!-- Cible de focus : ce texte EST le message d'etat, il est donc lu une fois et une\n seule (aucune region live en doublon). -->\n <span tabindex=\"-1\" #analysisStatus>{{ '_COMMON._IMPORT.ANALYSIS_IN_PROGRESS' | translate }}</span>\n }\n @else if (analysis(); as result) {\n <!-- Cible de focus a l'arrivee du resultat : libelle de section, pas une reprise du\n message, pour que la lecture enchaine sur l'alerte puis les compteurs. -->\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #analysisOutcome>\n {{ '_COMMON._IMPORT._A11Y.ANALYSIS_RESULT' | translate }}\n </h3>\n\n @if (result.valid) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.ANALYSIS_OK' | translate }}\n </cw-alert>\n\n <!-- scope=list : previsualisation chiffree. scope=singleConfig : le message ci-dessus suffit. -->\n @if (isList() && previewRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: previewRows() }\"></ng-container>\n }\n\n <div class=\"list-unstyled d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.START'\"\n [dataTest]=\"'btn-import-start'\"\n (btnAction)=\"startImport()\">\n </cw-button>\n </div>\n }\n @else {\n <cw-alert [type]=\"alertType.danger\">\n {{ '_COMMON._IMPORT.ANALYSIS_KO' | translate }}\n </cw-alert>\n\n @if (errorCountRow(); as errorCount) {\n <p class=\"mb-none\">\n <span class=\"fs-lg fw-bold\" [class]=\"'text-' + errorCount.tone\">{{ errorCount.count }}</span>\n {{ errorCount.key | translate }}\n </p>\n\n <!-- Une alerte par anomalie, et non une liste a puces : c'est le rendu de la\n maquette, et chaque message reste lisible isolement. -->\n @for (issue of errors(); track $index) {\n <cw-alert [type]=\"alertType.danger\">\n @if (issue.line !== undefined) {\n <strong>{{ '_COMMON._IMPORT.ERROR_LINE' | translate: { line: issue.line } }}</strong>\n }\n {{ issue.messageKey ? (issue.messageKey | translate: issue.messageParams) : issue.message }}\n </cw-alert>\n }\n }\n\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.BACK_TO_SELECTION'\"\n [dataTest]=\"'btn-import-back'\"\n (btnAction)=\"backToSelection()\">\n </cw-button>\n </div>\n }\n }\n </div>\n </cw-card>\n\n <!-- Etape 3 : import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.EXECUTION'\"\n [collapseId]=\"'import-step-execution'\"\n [collapsed]=\"!isPanelOpen(step.execution)\"\n [disabled]=\"stateOf(step.execution) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.execution, number: 3 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (isImporting) {\n <div class=\"d-flex-v gap-2xs\">\n <!-- Cible de focus. Le bouton \u00AB Arreter l'import \u00BB est le focusable suivant. -->\n <span tabindex=\"-1\" #executionStatus>{{ '_COMMON._IMPORT.IN_PROGRESS' | translate }}</span>\n @if (progress()?.remainingLabel; as remaining) {\n <span>{{ '_COMMON._IMPORT.REMAINING' | translate: { value: remaining } }}</span>\n }\n </div>\n\n <!-- Avancement connu => barre deterministe. Aucune progression recue => spinner\n indetermine, plutot qu'une barre figee a 0 %. La bascule se fait a la premiere\n emission `ImportProgress` : une source qui n'en emet jamais reste sur le spinner.\n `displayText` a false : le libelle \u00AB Import en cours \u00BB est deja au-dessus. -->\n @if (progress(); as current) {\n <cw-progress-bar\n [value]=\"current.percent\"\n [type]=\"progressType.information\"\n [label]=\"'_COMMON._IMPORT.IN_PROGRESS' | translate\">\n </cw-progress-bar>\n }\n @else {\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n }\n\n <!-- Pas de bouton d'arret quand l'objet declare l'import non interruptible : un\n bouton qui ne peut rien arreter serait pire que son absence. -->\n @if (canInterrupt()) {\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.dangerOutline\"\n [label]=\"'_COMMON._IMPORT.STOP'\"\n [dataTest]=\"'btn-import-stop'\"\n (btnAction)=\"stopImport()\">\n </cw-button>\n </div>\n }\n }\n @else if (cancelling()) {\n <!-- Annulation demandee au serveur, reponse en attente. Cible de focus. -->\n <span tabindex=\"-1\" #cancellingStatus>{{ '_COMMON._IMPORT.CANCELLING' | translate }}</span>\n\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n\n <!-- Sortie de secours : aucun delai n'etant defendable depuis la lib, c'est\n l'utilisateur qui decide d'arreter d'attendre. Conclut en \u00AB etat incertain \u00BB. -->\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.STOP_WAITING'\"\n [dataTest]=\"'btn-import-stop-waiting'\"\n (btnAction)=\"stopWaiting()\">\n </cw-button>\n </div>\n }\n @else if (interruptionMessageKey(); as interruptionMessage) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #interruptedOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n <cw-alert [type]=\"interruptionAlertType()\">\n {{ interruptionMessage | translate }}\n </cw-alert>\n }\n @else if (report(); as result) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #reportOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n @if (result.success) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.SUCCESS' | translate }}\n </cw-alert>\n\n @if (isList() && reportRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: reportRows() }\"></ng-container>\n }\n }\n @else {\n <!-- Le message generique ne promet l'absence d'ecriture que si l'atomicite est\n declaree (cf. `failureMessageKey`). -->\n <cw-alert [type]=\"alertType.danger\">\n {{ result.errorMessage ?? (failureMessageKey() | translate) }}\n </cw-alert>\n }\n }\n </div>\n </cw-card>\n\n</cw-accordion>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: AccordionComponent, selector: "cw-accordion", inputs: ["closeOthers", "allowMultiple", "destroyOnHide"], outputs: ["panelOpened", "panelClosed"] }, { kind: "component", type: AlertComponent, selector: "cw-alert", inputs: ["type", "title", "isDismissible", "cssClass", "dataTest"], outputs: ["close"] }, { kind: "component", type: ButtonComponent, selector: "cw-button", inputs: ["type", "label", "id", "link", "isDropdown", "hideCaret", "withIcon", "withOnlyIcon", "iconSize", "iconPosition", "dropdownPosition", "isMinWidth", "isDisabled", "isLoading", "loadingAnnounce", "cssClass", "dataTest", "skeletonMode", "withTooltip", "tooltipText", "tooltipTitle", "tooltipTrigger", "tooltipPosition", "tooltipType"], outputs: ["btnAction", "dropDownClicked"] }, { kind: "component", type: CardComponent, selector: "cw-card", inputs: ["title", "subtitle", "type", "titleRank", "cssClass", "collapsible", "collapsed", "disabled", "collapseLocked", "collapseStretched", "collapseId", "destroyOnHide"], outputs: ["collapsibleChange", "destroyOnHideChange", "collapseClicked"] }, { kind: "component", type: FileDropComponent, selector: "cw-file-drop", inputs: ["mode", "multiple", "maxFiles", "accept", "acceptLabel", "maxSizeMb", "recommendedDimensions", "label", "isDisabled", "errorMessage", "dataTest"], outputs: ["filesChange", "rejected", "retry"] }, { kind: "component", type: FileToDownloadComponent, selector: "cw-file-to-download", inputs: ["attachments"] }, { kind: "component", type: FormFieldComponent, selector: "cw-form-field", inputs: ["control", "formGroup", "type", "label", "displayLabel", "name", "placeholder", "autocomplete", "options", "rangeOptions", "validationOptions", "autocompleteOptions", "richTextOptions", "errorMessages", "isRequired", "help", "tooltip", "addonLeft", "addonRight", "isInAddon", "isReadonly", "isDisabled", "sliderValue", "sliderOptions", "cssClass", "skeletonMode", "clearable", "appendTo", "initialCountry", "indeterminate"], outputs: ["refreshEvent", "textChanged", "valueChange", "phoneRegionChange"] }, { kind: "component", type: IconComponent, selector: "cw-icon", inputs: ["icon", "label", "type", "iconSize", "iconCssClass", "iconUiType", "iconColorCustom", "spritePath"] }, { kind: "component", type: LoaderComponent, selector: "cw-loader", inputs: ["size", "displayText"] }, { kind: "component", type: ProgressBarComponent, selector: "cw-progress-bar", inputs: ["value", "max", "size", "type", "showValue", "label", "dataTest"] }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1714
1768
|
}
|
|
1715
1769
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.20", ngImport: i0, type: ImportWizardComponent, decorators: [{
|
|
1716
1770
|
type: Component,
|
|
@@ -1728,7 +1782,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.20", ngImpo
|
|
|
1728
1782
|
IconComponent,
|
|
1729
1783
|
LoaderComponent,
|
|
1730
1784
|
ProgressBarComponent
|
|
1731
|
-
], template: "<!-- Pastille d'etape : icone ronde verte (terminee) / rouge (echec), sinon le numero.\n Rendue dans [header-start] pour etre AVANT le titre, comme sur les maquettes.\n Le numero seul n'etant pas parlant a l'oreille, le statut est double en visually-hidden --\n et c'est LUI qui distingue \u00AB en cours \u00BB de \u00AB a venir \u00BB pour qui ne voit pas la couleur. -->\n<ng-template #badge let-stepKey=\"stepKey\" let-number=\"number\">\n @if (stateOf(stepKey) === stepState.done) {\n <cw-icon [icon]=\"spriteIcon.check\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.success\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else if (stateOf(stepKey) === stepState.error) {\n <cw-icon [icon]=\"spriteIcon.close\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.danger\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else {\n <div class=\"icon-circular icon-circular-sm\" [class]=\"'icon-circular-' + badgeVariant(stepKey)\">\n <span aria-hidden=\"true\">{{ number }}</span>\n <span class=\"visually-hidden\">{{ stepStatusLabel(stepKey) | translate }}</span>\n </div>\n }\n</ng-template>\n\n<ng-template #counts let-rows=\"rows\">\n <ul class=\"list-unstyled d-flex-h gap-md\">\n @for (row of rows; track row.key) {\n <li>\n <span class=\"fs-lg fw-bold\" [class]=\"'text-' + row.tone\">{{ row.count }}</span> {{ row.key | translate }}\n </li>\n }\n </ul>\n</ng-template>\n\n<cw-accordion [closeOthers]=\"true\" [destroyOnHide]=\"false\">\n\n <!-- Etape 1 : fichier + type d'import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.SELECT'\"\n [collapseId]=\"'import-step-select'\"\n [collapsed]=\"!isPanelOpen(step.select)\"\n [collapseLocked]=\"stateOf(step.select) === stepState.done\"\n [disabled]=\"stateOf(step.select) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.select, number: 1 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n <div class=\"row\">\n <div class=\"col-lg-6\">\n <!-- Retour depuis une analyse non conforme : cible de focus. Le bouton qui declenche\n ce retour disparait avec le repli du panneau 2, le focus retomberait sur <body>. -->\n @if (selectionReopened()) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #selectionTarget>\n {{ '_COMMON._IMPORT._A11Y.SELECTION' | translate }}\n </h3>\n }\n\n <cw-file-drop\n [formControl]=\"file\"\n [accept]=\"config().accept\"\n [acceptLabel]=\"config().acceptLabel\"\n [maxSizeMb]=\"maxSizeMb()\"\n [label]=\"'_COMMON._IMPORT.FILE_TO_IMPORT' | translate\">\n </cw-file-drop>\n </div>\n\n @if (config().templateFile) {\n <div class=\"col-lg-6\">\n <cw-card [type]=\"cardType.light\">\n <strong>{{ '_COMMON._IMPORT._TEMPLATE.TITLE' | translate }}</strong>\n <span>{{ '_COMMON._IMPORT._TEMPLATE.TEXT' | translate }}</span>\n <cw-file-to-download [attachments]=\"templateAttachments()\"></cw-file-to-download>\n </cw-card>\n </div>\n }\n </div>\n\n <!-- Choix impose : on informe sans champ de saisie. Choix libre : groupe radio,\n dont la selection est REQUISE (pas de preselection, cf. `selectedMode`). -->\n @if (isModeImposed()) {\n <div class=\"d-flex-v gap-2xs\">\n <p class=\"mb-none\">\n <strong>{{ '_COMMON._IMPORT._MODE.LABEL' | translate }}</strong>\n {{ imposedModeLabel() | translate }}\n </p>\n <!-- Champ cle affiche ici AUSSI : c'est le cas le plus courant, et le seul ou\n l'utilisateur ne voyait pas sur quoi portait le rapprochement. -->\n @if (selectedKeyFieldHint(); as keyFieldHint) {\n <span class=\"fs fs-xs text-light\">{{ keyFieldHint | translate }}</span>\n }\n </div>\n }\n @else {\n <cw-form-field\n [type]=\"fieldType.radio\"\n [control]=\"mode\"\n [name]=\"'importMode'\"\n [label]=\"'_COMMON._IMPORT._MODE.LABEL'\"\n [options]=\"modeOptions()\"\n [isRequired]=\"true\"\n [help]=\"selectedKeyFieldHint()\">\n </cw-form-field>\n }\n\n <!-- Portee du mode retenu (destructif ou non) : exigence SFD \u00AB l'utilisateur est\n informe du type d'import selectionne ou impose \u00BB. Rien tant qu'aucun mode n'est\n retenu : l'alerte ne decrit jamais un mode que l'utilisateur n'a pas choisi. -->\n @if (modeNoticeKey(); as noticeKey) {\n <cw-alert [type]=\"modeNoticeType()\">\n {{ noticeKey | translate }}\n </cw-alert>\n }\n\n <!-- Un bouton desactive muet est un cul-de-sac : ce texte dit ce qui manque, et il est\n place AVANT le bouton, seul chemin possible puisqu'un bouton desactive n'est pas\n focusable (donc pas d'aria-describedby exploitable). Pas de classe de couleur\n atenuee : c'est une information necessaire, elle garde le contraste du corps. -->\n <div class=\"d-flex-h justify-end gap-md\">\n @if (missingRequirementKey(); as missing) {\n <p class=\"mb-none fs-xs\">{{ missing | translate }}</p>\n }\n\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.CONTINUE'\"\n [dataTest]=\"'btn-import-continue'\"\n [isDisabled]=\"!canContinue()\"\n (btnAction)=\"continueToAnalysis()\">\n </cw-button>\n </div>\n </div>\n </cw-card>\n\n <!-- Etape 2 : analyse a blanc -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.ANALYSIS'\"\n [collapseId]=\"'import-step-analysis'\"\n [collapsed]=\"!isPanelOpen(step.analysis)\"\n [collapseLocked]=\"stateOf(step.analysis) === stepState.done\"\n [disabled]=\"stateOf(step.analysis) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.analysis, number: 2 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (analyzing()) {\n <!-- Cible de focus : ce texte EST le message d'etat, il est donc lu une fois et une\n seule (aucune region live en doublon). -->\n <span tabindex=\"-1\" #analysisStatus>{{ '_COMMON._IMPORT.ANALYSIS_IN_PROGRESS' | translate }}</span>\n }\n @else if (analysis(); as result) {\n <!-- Cible de focus a l'arrivee du resultat : libelle de section, pas une reprise du\n message, pour que la lecture enchaine sur l'alerte puis les compteurs. -->\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #analysisOutcome>\n {{ '_COMMON._IMPORT._A11Y.ANALYSIS_RESULT' | translate }}\n </h3>\n\n @if (result.valid) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.ANALYSIS_OK' | translate }}\n </cw-alert>\n\n <!-- scope=list : previsualisation chiffree. scope=singleConfig : le message ci-dessus suffit. -->\n @if (isList() && previewRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: previewRows() }\"></ng-container>\n }\n\n <div class=\"list-unstyled d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.START'\"\n [dataTest]=\"'btn-import-start'\"\n (btnAction)=\"startImport()\">\n </cw-button>\n </div>\n }\n @else {\n <cw-alert [type]=\"alertType.danger\">\n {{ '_COMMON._IMPORT.ANALYSIS_KO' | translate }}\n </cw-alert>\n\n @if (errors().length) {\n <ul class=\"mb-none\">\n @for (issue of errors(); track $index) {\n <li>\n @if (issue.line !== undefined) {\n <strong>{{ '_COMMON._IMPORT.ERROR_LINE' | translate: { line: issue.line } }}</strong>\n }\n {{ issue.messageKey ? (issue.messageKey | translate: issue.messageParams) : issue.message }}\n </li>\n }\n </ul>\n }\n\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.BACK_TO_SELECTION'\"\n [dataTest]=\"'btn-import-back'\"\n (btnAction)=\"backToSelection()\">\n </cw-button>\n </div>\n }\n }\n </div>\n </cw-card>\n\n <!-- Etape 3 : import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.EXECUTION'\"\n [collapseId]=\"'import-step-execution'\"\n [collapsed]=\"!isPanelOpen(step.execution)\"\n [disabled]=\"stateOf(step.execution) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.execution, number: 3 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (isImporting) {\n <div class=\"d-flex-v gap-2xs\">\n <!-- Cible de focus. Le bouton \u00AB Arreter l'import \u00BB est le focusable suivant. -->\n <span tabindex=\"-1\" #executionStatus>{{ '_COMMON._IMPORT.IN_PROGRESS' | translate }}</span>\n @if (progress()?.remainingLabel; as remaining) {\n <span>{{ '_COMMON._IMPORT.REMAINING' | translate: { value: remaining } }}</span>\n }\n </div>\n\n <!-- Avancement connu => barre deterministe. Aucune progression recue => spinner\n indetermine, plutot qu'une barre figee a 0 %. La bascule se fait a la premiere\n emission `ImportProgress` : une source qui n'en emet jamais reste sur le spinner.\n `displayText` a false : le libelle \u00AB Import en cours \u00BB est deja au-dessus. -->\n @if (progress(); as current) {\n <cw-progress-bar\n [value]=\"current.percent\"\n [type]=\"progressType.information\"\n [label]=\"'_COMMON._IMPORT.IN_PROGRESS' | translate\">\n </cw-progress-bar>\n }\n @else {\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n }\n\n <!-- Pas de bouton d'arret quand l'objet declare l'import non interruptible : un\n bouton qui ne peut rien arreter serait pire que son absence. -->\n @if (canInterrupt()) {\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.dangerOutline\"\n [label]=\"'_COMMON._IMPORT.STOP'\"\n [dataTest]=\"'btn-import-stop'\"\n (btnAction)=\"stopImport()\">\n </cw-button>\n </div>\n }\n }\n @else if (cancelling()) {\n <!-- Annulation demandee au serveur, reponse en attente. Cible de focus. -->\n <span tabindex=\"-1\" #cancellingStatus>{{ '_COMMON._IMPORT.CANCELLING' | translate }}</span>\n\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n\n <!-- Sortie de secours : aucun delai n'etant defendable depuis la lib, c'est\n l'utilisateur qui decide d'arreter d'attendre. Conclut en \u00AB etat incertain \u00BB. -->\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.STOP_WAITING'\"\n [dataTest]=\"'btn-import-stop-waiting'\"\n (btnAction)=\"stopWaiting()\">\n </cw-button>\n </div>\n }\n @else if (interruptionMessageKey(); as interruptionMessage) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #interruptedOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n <cw-alert [type]=\"interruptionAlertType()\">\n {{ interruptionMessage | translate }}\n </cw-alert>\n }\n @else if (report(); as result) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #reportOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n @if (result.success) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.SUCCESS' | translate }}\n </cw-alert>\n\n @if (isList() && reportRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: reportRows() }\"></ng-container>\n }\n }\n @else {\n <!-- Le message generique ne promet l'absence d'ecriture que si l'atomicite est\n declaree (cf. `failureMessageKey`). -->\n <cw-alert [type]=\"alertType.danger\">\n {{ result.errorMessage ?? (failureMessageKey() | translate) }}\n </cw-alert>\n }\n }\n </div>\n </cw-card>\n\n</cw-accordion>\n" }]
|
|
1785
|
+
], template: "<!-- Pastille d'etape : icone ronde verte (terminee) / rouge (echec), sinon le numero.\n Rendue dans [header-start] pour etre AVANT le titre, comme sur les maquettes.\n Le numero seul n'etant pas parlant a l'oreille, le statut est double en visually-hidden --\n et c'est LUI qui distingue \u00AB en cours \u00BB de \u00AB a venir \u00BB pour qui ne voit pas la couleur. -->\n<ng-template #badge let-stepKey=\"stepKey\" let-number=\"number\">\n @if (stateOf(stepKey) === stepState.done) {\n <cw-icon [icon]=\"spriteIcon.check\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.success\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else if (stateOf(stepKey) === stepState.error) {\n <cw-icon [icon]=\"spriteIcon.close\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.danger\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else {\n <div class=\"icon-circular icon-circular-sm\" [class]=\"'icon-circular-' + badgeVariant(stepKey)\">\n <span aria-hidden=\"true\">{{ number }}</span>\n <span class=\"visually-hidden\">{{ stepStatusLabel(stepKey) | translate }}</span>\n </div>\n }\n</ng-template>\n\n<ng-template #counts let-rows=\"rows\">\n <ul class=\"list-unstyled d-flex-h gap-md\">\n @for (row of rows; track row.key) {\n <li>\n <span class=\"fs-lg fw-bold\" [class]=\"'text-' + row.tone\">{{ row.count }}</span> {{ row.key | translate: row.params }}\n </li>\n }\n </ul>\n</ng-template>\n\n<cw-accordion [closeOthers]=\"true\" [destroyOnHide]=\"false\">\n\n <!-- Etape 1 : fichier + type d'import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.SELECT'\"\n [collapseId]=\"'import-step-select'\"\n [collapsed]=\"!isPanelOpen(step.select)\"\n [collapseLocked]=\"stateOf(step.select) === stepState.done\"\n [disabled]=\"stateOf(step.select) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.select, number: 1 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n <div class=\"row\">\n <div class=\"col-lg-6\">\n <!-- Retour depuis une analyse non conforme : cible de focus. Le bouton qui declenche\n ce retour disparait avec le repli du panneau 2, le focus retomberait sur <body>. -->\n @if (selectionReopened()) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #selectionTarget>\n {{ '_COMMON._IMPORT._A11Y.SELECTION' | translate }}\n </h3>\n }\n\n <cw-file-drop\n [formControl]=\"file\"\n [accept]=\"config().accept\"\n [acceptLabel]=\"config().acceptLabel\"\n [maxSizeMb]=\"maxSizeMb()\"\n [label]=\"'_COMMON._IMPORT.FILE_TO_IMPORT' | translate\">\n </cw-file-drop>\n </div>\n\n @if (config().templateFile) {\n <div class=\"col-lg-6\">\n <cw-card [type]=\"cardType.light\">\n <strong>{{ '_COMMON._IMPORT._TEMPLATE.TITLE' | translate }}</strong>\n <span>{{ '_COMMON._IMPORT._TEMPLATE.TEXT' | translate }}</span>\n <cw-file-to-download [attachments]=\"templateAttachments()\"></cw-file-to-download>\n </cw-card>\n </div>\n }\n </div>\n\n <!-- Mode IMPOSE : aucun champ, et pas davantage de ligne \u00AB Type d'import : ... \u00BB \u2014 la\n maquette ne rend que l'alerte de portee ci-dessous, qui informe deja du mode retenu.\n Le champ cle de rapprochement y est replie (voir l'alerte).\n Choix LIBRE : groupe radio, dont la selection est REQUISE (pas de preselection). -->\n @if (!isModeImposed()) {\n <cw-form-field\n [type]=\"fieldType.radio\"\n [control]=\"mode\"\n [name]=\"'importMode'\"\n [label]=\"'_COMMON._IMPORT._MODE.LABEL'\"\n [options]=\"modeOptions()\"\n [isRequired]=\"true\"\n [help]=\"selectedKeyFieldHint()\">\n </cw-form-field>\n }\n\n <!-- Portee du mode retenu (destructif ou non) : exigence SFD \u00AB l'utilisateur est\n informe du type d'import selectionne ou impose \u00BB. Rien tant qu'aucun mode n'est\n retenu : l'alerte ne decrit jamais un mode que l'utilisateur n'a pas choisi. -->\n @if (modeNotice(); as notice) {\n <cw-alert [type]=\"modeNoticeType()\">\n {{ notice.lead | translate }}\n @if (notice.items.length) {\n <ul class=\"mb-none\">\n @for (item of notice.items; track $index) {\n <li>{{ item | translate }}</li>\n }\n </ul>\n }\n <!-- Mode impose : l'alerte est le SEUL endroit qui reste pour dire sur quoi porte\n le rapprochement. En choix libre, c'est l'aide du groupe radio qui le porte. -->\n @if (isModeImposed() && selectedKeyFieldHint(); as keyFieldHint) {\n <p class=\"mb-none\">{{ keyFieldHint | translate }}</p>\n }\n </cw-alert>\n }\n\n <!-- Un bouton desactive muet est un cul-de-sac : ce texte dit ce qui manque, et il est\n place AVANT le bouton, seul chemin possible puisqu'un bouton desactive n'est pas\n focusable (donc pas d'aria-describedby exploitable). Pas de classe de couleur\n atenuee : c'est une information necessaire, elle garde le contraste du corps. -->\n <div class=\"d-flex-h justify-end gap-md\">\n @if (missingRequirementKey(); as missing) {\n <p class=\"mb-none fs-xs\">{{ missing | translate }}</p>\n }\n\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.CONTINUE'\"\n [dataTest]=\"'btn-import-continue'\"\n [isDisabled]=\"!canContinue()\"\n (btnAction)=\"continueToAnalysis()\">\n </cw-button>\n </div>\n </div>\n </cw-card>\n\n <!-- Etape 2 : analyse a blanc -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.ANALYSIS'\"\n [collapseId]=\"'import-step-analysis'\"\n [collapsed]=\"!isPanelOpen(step.analysis)\"\n [collapseLocked]=\"stateOf(step.analysis) === stepState.done\"\n [disabled]=\"stateOf(step.analysis) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.analysis, number: 2 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (analyzing()) {\n <!-- Cible de focus : ce texte EST le message d'etat, il est donc lu une fois et une\n seule (aucune region live en doublon). -->\n <span tabindex=\"-1\" #analysisStatus>{{ '_COMMON._IMPORT.ANALYSIS_IN_PROGRESS' | translate }}</span>\n }\n @else if (analysis(); as result) {\n <!-- Cible de focus a l'arrivee du resultat : libelle de section, pas une reprise du\n message, pour que la lecture enchaine sur l'alerte puis les compteurs. -->\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #analysisOutcome>\n {{ '_COMMON._IMPORT._A11Y.ANALYSIS_RESULT' | translate }}\n </h3>\n\n @if (result.valid) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.ANALYSIS_OK' | translate }}\n </cw-alert>\n\n <!-- scope=list : previsualisation chiffree. scope=singleConfig : le message ci-dessus suffit. -->\n @if (isList() && previewRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: previewRows() }\"></ng-container>\n }\n\n <div class=\"list-unstyled d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.START'\"\n [dataTest]=\"'btn-import-start'\"\n (btnAction)=\"startImport()\">\n </cw-button>\n </div>\n }\n @else {\n <cw-alert [type]=\"alertType.danger\">\n {{ '_COMMON._IMPORT.ANALYSIS_KO' | translate }}\n </cw-alert>\n\n @if (errorCountRow(); as errorCount) {\n <p class=\"mb-none\">\n <span class=\"fs-lg fw-bold\" [class]=\"'text-' + errorCount.tone\">{{ errorCount.count }}</span>\n {{ errorCount.key | translate }}\n </p>\n\n <!-- Une alerte par anomalie, et non une liste a puces : c'est le rendu de la\n maquette, et chaque message reste lisible isolement. -->\n @for (issue of errors(); track $index) {\n <cw-alert [type]=\"alertType.danger\">\n @if (issue.line !== undefined) {\n <strong>{{ '_COMMON._IMPORT.ERROR_LINE' | translate: { line: issue.line } }}</strong>\n }\n {{ issue.messageKey ? (issue.messageKey | translate: issue.messageParams) : issue.message }}\n </cw-alert>\n }\n }\n\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.BACK_TO_SELECTION'\"\n [dataTest]=\"'btn-import-back'\"\n (btnAction)=\"backToSelection()\">\n </cw-button>\n </div>\n }\n }\n </div>\n </cw-card>\n\n <!-- Etape 3 : import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.EXECUTION'\"\n [collapseId]=\"'import-step-execution'\"\n [collapsed]=\"!isPanelOpen(step.execution)\"\n [disabled]=\"stateOf(step.execution) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.execution, number: 3 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (isImporting) {\n <div class=\"d-flex-v gap-2xs\">\n <!-- Cible de focus. Le bouton \u00AB Arreter l'import \u00BB est le focusable suivant. -->\n <span tabindex=\"-1\" #executionStatus>{{ '_COMMON._IMPORT.IN_PROGRESS' | translate }}</span>\n @if (progress()?.remainingLabel; as remaining) {\n <span>{{ '_COMMON._IMPORT.REMAINING' | translate: { value: remaining } }}</span>\n }\n </div>\n\n <!-- Avancement connu => barre deterministe. Aucune progression recue => spinner\n indetermine, plutot qu'une barre figee a 0 %. La bascule se fait a la premiere\n emission `ImportProgress` : une source qui n'en emet jamais reste sur le spinner.\n `displayText` a false : le libelle \u00AB Import en cours \u00BB est deja au-dessus. -->\n @if (progress(); as current) {\n <cw-progress-bar\n [value]=\"current.percent\"\n [type]=\"progressType.information\"\n [label]=\"'_COMMON._IMPORT.IN_PROGRESS' | translate\">\n </cw-progress-bar>\n }\n @else {\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n }\n\n <!-- Pas de bouton d'arret quand l'objet declare l'import non interruptible : un\n bouton qui ne peut rien arreter serait pire que son absence. -->\n @if (canInterrupt()) {\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.dangerOutline\"\n [label]=\"'_COMMON._IMPORT.STOP'\"\n [dataTest]=\"'btn-import-stop'\"\n (btnAction)=\"stopImport()\">\n </cw-button>\n </div>\n }\n }\n @else if (cancelling()) {\n <!-- Annulation demandee au serveur, reponse en attente. Cible de focus. -->\n <span tabindex=\"-1\" #cancellingStatus>{{ '_COMMON._IMPORT.CANCELLING' | translate }}</span>\n\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n\n <!-- Sortie de secours : aucun delai n'etant defendable depuis la lib, c'est\n l'utilisateur qui decide d'arreter d'attendre. Conclut en \u00AB etat incertain \u00BB. -->\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.STOP_WAITING'\"\n [dataTest]=\"'btn-import-stop-waiting'\"\n (btnAction)=\"stopWaiting()\">\n </cw-button>\n </div>\n }\n @else if (interruptionMessageKey(); as interruptionMessage) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #interruptedOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n <cw-alert [type]=\"interruptionAlertType()\">\n {{ interruptionMessage | translate }}\n </cw-alert>\n }\n @else if (report(); as result) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #reportOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n @if (result.success) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.SUCCESS' | translate }}\n </cw-alert>\n\n @if (isList() && reportRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: reportRows() }\"></ng-container>\n }\n }\n @else {\n <!-- Le message generique ne promet l'absence d'ecriture que si l'atomicite est\n declaree (cf. `failureMessageKey`). -->\n <cw-alert [type]=\"alertType.danger\">\n {{ result.errorMessage ?? (failureMessageKey() | translate) }}\n </cw-alert>\n }\n }\n </div>\n </cw-card>\n\n</cw-accordion>\n" }]
|
|
1732
1786
|
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], completed: [{ type: i0.Output, args: ["completed"] }], panels: [{ type: i0.ViewChildren, args: ['stepPanel', { ...{ read: CardComponent }, isSignal: true }] }], selectionTarget: [{ type: i0.ViewChild, args: ['selectionTarget', { isSignal: true }] }], analysisStatus: [{ type: i0.ViewChild, args: ['analysisStatus', { isSignal: true }] }], analysisOutcome: [{ type: i0.ViewChild, args: ['analysisOutcome', { isSignal: true }] }], executionStatus: [{ type: i0.ViewChild, args: ['executionStatus', { isSignal: true }] }], cancellingStatus: [{ type: i0.ViewChild, args: ['cancellingStatus', { isSignal: true }] }], reportOutcome: [{ type: i0.ViewChild, args: ['reportOutcome', { isSignal: true }] }], interruptedOutcome: [{ type: i0.ViewChild, args: ['interruptedOutcome', { isSignal: true }] }] } });
|
|
1733
1787
|
|
|
1734
1788
|
/**
|