Files
LoreMind/web/src/app/shared/prerequisite-editor/prerequisite-editor.component.ts
IETM_FIXE\ietm6 23878f1c63 Migration Angular 20 -> 21 : fin de la migration securite (0 vulnerabilite npm)
- ng update @angular/core@21 @angular/cli@21 (corrige GHSA-jrmj-c5cx-3cw6, XSS SVG)
- Migration automatique des templates vers le control flow natif (@if/@for, 138 fichiers)
- Correction des 5 `track` invalides generes par la migration (trackBy a 1 argument
  -> track $index) + suppression des fonctions trackBy mortes
- TypeScript 5.9, zone.js 0.15 ; npm audit : 0 vulnerabilite

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 14:55:36 +02:00

98 lines
3.8 KiB
TypeScript

import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { LucideAngularModule, Plus, Trash2, ChevronDown } from 'lucide-angular';
import { Chapter, Prerequisite } from '../../services/campaign.model';
/**
* Éditeur des prérequis (conditions de déblocage) d'une quête.
* Composant standalone & contrôlé : reçoit la liste, émet la nouvelle liste à chaque changement.
*
* Combinaison ET (MVP). UI : liste de lignes + bouton "Ajouter une condition" qui ouvre
* un menu à 3 entrées (quête, session, fait).
*/
@Component({
selector: 'app-prerequisite-editor',
imports: [FormsModule, LucideAngularModule],
templateUrl: './prerequisite-editor.component.html',
styleUrls: ['./prerequisite-editor.component.scss']
})
export class PrerequisiteEditorComponent {
/** Liste courante. */
@Input() prerequisites: Prerequisite[] = [];
/** Quêtes candidates pour QUEST_COMPLETED (typiquement les chapitres frères du Hub). */
@Input() availableQuests: Chapter[] = [];
/** Flags déjà connus de la campagne (autocomplete pour FLAG_SET). */
@Input() availableFlags: string[] = [];
/** Émis chaque fois que la liste change (toute édition / ajout / suppression). */
@Output() prerequisitesChange = new EventEmitter<Prerequisite[]>();
readonly Plus = Plus;
readonly Trash2 = Trash2;
readonly ChevronDown = ChevronDown;
/** Ouvre/ferme le menu d'ajout. */
addMenuOpen = false;
toggleAddMenu(): void { this.addMenuOpen = !this.addMenuOpen; }
addPrerequisite(kind: Prerequisite['kind']): void {
let next: Prerequisite;
switch (kind) {
case 'QUEST_COMPLETED':
next = { kind: 'QUEST_COMPLETED', questId: this.availableQuests[0]?.id ?? '' };
break;
case 'SESSION_REACHED':
next = { kind: 'SESSION_REACHED', minSessionNumber: 1 };
break;
case 'FLAG_SET':
next = { kind: 'FLAG_SET', flagName: this.availableFlags[0] ?? '' };
break;
}
this.prerequisites = [...this.prerequisites, next];
this.prerequisitesChange.emit(this.prerequisites);
this.addMenuOpen = false;
}
removeAt(index: number): void {
this.prerequisites = this.prerequisites.filter((_, i) => i !== index);
this.prerequisitesChange.emit(this.prerequisites);
}
/** Le champ binding change : on émet une nouvelle liste (immutable update). */
updateAt(index: number, patched: Prerequisite): void {
this.prerequisites = this.prerequisites.map((p, i) => (i === index ? patched : p));
this.prerequisitesChange.emit(this.prerequisites);
}
// === Handlers spécifiques par type (pour garder les templates simples) ===
onQuestIdChange(index: number, questId: string): void {
this.updateAt(index, { kind: 'QUEST_COMPLETED', questId });
}
onMinSessionChange(index: number, n: number): void {
this.updateAt(index, { kind: 'SESSION_REACHED', minSessionNumber: Number(n) || 1 });
}
onFlagNameChange(index: number, flagName: string): void {
this.updateAt(index, { kind: 'FLAG_SET', flagName });
}
/** trackBy stable pour *ngFor (sinon les inputs reset à chaque édition). */
// === Casts typés pour le template ===
// Angular strict templates ne fait pas le narrowing d'une union discriminée à travers
// *ngSwitchCase. Ces helpers castent vers la bonne variante pour rendre les inputs lisibles.
asQuestCompleted(p: Prerequisite): { kind: 'QUEST_COMPLETED'; questId: string } {
return p as { kind: 'QUEST_COMPLETED'; questId: string };
}
asSessionReached(p: Prerequisite): { kind: 'SESSION_REACHED'; minSessionNumber: number } {
return p as { kind: 'SESSION_REACHED'; minSessionNumber: number };
}
asFlagSet(p: Prerequisite): { kind: 'FLAG_SET'; flagName: string } {
return p as { kind: 'FLAG_SET'; flagName: string };
}
}