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>
This commit is contained in:
@@ -10,16 +10,17 @@
|
||||
<lucide-icon [img]="Drama" [size]="22"></lucide-icon>
|
||||
{{ npcId ? 'Éditer le PNJ' : 'Nouveau PNJ' }}
|
||||
</h1>
|
||||
<button
|
||||
*ngIf="npcId"
|
||||
type="button"
|
||||
class="btn-ai"
|
||||
(click)="toggleChat()"
|
||||
[class.active]="chatOpen"
|
||||
title="Ouvrir l'Assistant IA pour dialoguer autour de ce PNJ">
|
||||
<lucide-icon [img]="Sparkles" [size]="14"></lucide-icon>
|
||||
Assistant IA
|
||||
</button>
|
||||
@if (npcId) {
|
||||
<button
|
||||
type="button"
|
||||
class="btn-ai"
|
||||
(click)="toggleChat()"
|
||||
[class.active]="chatOpen"
|
||||
title="Ouvrir l'Assistant IA pour dialoguer autour de ce PNJ">
|
||||
<lucide-icon [img]="Sparkles" [size]="14"></lucide-icon>
|
||||
Assistant IA
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,85 +34,89 @@
|
||||
[(ngModel)]="name"
|
||||
name="name"
|
||||
placeholder="Ex: Borin le forgeron, Dame Elara, Kael l'aubergiste..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="npc-folder">Dossier</label>
|
||||
<input
|
||||
id="npc-folder"
|
||||
type="text"
|
||||
[(ngModel)]="folder"
|
||||
name="folder"
|
||||
list="npc-folders"
|
||||
placeholder="Ex: Bard's Gate, Faction des Pipers… (laisser vide = non classé)"
|
||||
/>
|
||||
<datalist id="npc-folders">
|
||||
<option *ngFor="let f of existingFolders" [value]="f"></option>
|
||||
</datalist>
|
||||
</div>
|
||||
|
||||
<div class="field-row image-row">
|
||||
<div class="field portrait-field">
|
||||
<label>Portrait</label>
|
||||
<app-single-image-picker
|
||||
[imageId]="portraitImageId"
|
||||
aspectRatio="1 / 1"
|
||||
hint="Carre conseille (400×400)."
|
||||
(imageIdChange)="portraitImageId = $event">
|
||||
</app-single-image-picker>
|
||||
/>
|
||||
</div>
|
||||
<div class="field header-field">
|
||||
<label>Bandeau / Header</label>
|
||||
<app-single-image-picker
|
||||
[imageId]="headerImageId"
|
||||
aspectRatio="3 / 1"
|
||||
hint="Format paysage conseille (1200×400)."
|
||||
(imageIdChange)="headerImageId = $event">
|
||||
</app-single-image-picker>
|
||||
|
||||
<div class="field">
|
||||
<label for="npc-folder">Dossier</label>
|
||||
<input
|
||||
id="npc-folder"
|
||||
type="text"
|
||||
[(ngModel)]="folder"
|
||||
name="folder"
|
||||
list="npc-folders"
|
||||
placeholder="Ex: Bard's Gate, Faction des Pipers… (laisser vide = non classé)"
|
||||
/>
|
||||
<datalist id="npc-folders">
|
||||
@for (f of existingFolders; track f) {
|
||||
<option [value]="f"></option>
|
||||
}
|
||||
</datalist>
|
||||
</div>
|
||||
|
||||
<div class="field-row image-row">
|
||||
<div class="field portrait-field">
|
||||
<label>Portrait</label>
|
||||
<app-single-image-picker
|
||||
[imageId]="portraitImageId"
|
||||
aspectRatio="1 / 1"
|
||||
hint="Carre conseille (400×400)."
|
||||
(imageIdChange)="portraitImageId = $event">
|
||||
</app-single-image-picker>
|
||||
</div>
|
||||
<div class="field header-field">
|
||||
<label>Bandeau / Header</label>
|
||||
<app-single-image-picker
|
||||
[imageId]="headerImageId"
|
||||
aspectRatio="3 / 1"
|
||||
hint="Format paysage conseille (1200×400)."
|
||||
(imageIdChange)="headerImageId = $event">
|
||||
</app-single-image-picker>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="template-fields">
|
||||
<app-dynamic-fields-form
|
||||
[fields]="templateFields"
|
||||
[values]="values"
|
||||
[imageValues]="imageValues"
|
||||
[keyValueValues]="keyValueValues"
|
||||
(valuesChange)="values = $event"
|
||||
(imageValuesChange)="imageValues = $event"
|
||||
(keyValueValuesChange)="keyValueValues = $event">
|
||||
</app-dynamic-fields-form>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="btn-primary" [disabled]="!name.trim()" (click)="submit()">
|
||||
<lucide-icon [img]="Save" [size]="16"></lucide-icon>
|
||||
{{ npcId ? 'Enregistrer' : 'Créer' }}
|
||||
</button>
|
||||
<button type="button" class="btn-secondary" (click)="back()">Annuler</button>
|
||||
<span class="spacer"></span>
|
||||
@if (npcId) {
|
||||
<button
|
||||
type="button"
|
||||
class="btn-danger"
|
||||
(click)="deleteNpc()">
|
||||
<lucide-icon [img]="Trash2" [size]="14"></lucide-icon>
|
||||
Supprimer
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="template-fields">
|
||||
<app-dynamic-fields-form
|
||||
[fields]="templateFields"
|
||||
[values]="values"
|
||||
[imageValues]="imageValues"
|
||||
[keyValueValues]="keyValueValues"
|
||||
(valuesChange)="values = $event"
|
||||
(imageValuesChange)="imageValues = $event"
|
||||
(keyValueValuesChange)="keyValueValues = $event">
|
||||
</app-dynamic-fields-form>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="btn-primary" [disabled]="!name.trim()" (click)="submit()">
|
||||
<lucide-icon [img]="Save" [size]="16"></lucide-icon>
|
||||
{{ npcId ? 'Enregistrer' : 'Créer' }}
|
||||
</button>
|
||||
<button type="button" class="btn-secondary" (click)="back()">Annuler</button>
|
||||
<span class="spacer"></span>
|
||||
<button
|
||||
*ngIf="npcId"
|
||||
type="button"
|
||||
class="btn-danger"
|
||||
(click)="deleteNpc()">
|
||||
<lucide-icon [img]="Trash2" [size]="14"></lucide-icon>
|
||||
Supprimer
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<app-ai-chat-drawer
|
||||
*ngIf="npcId && campaignId"
|
||||
[campaignId]="campaignId"
|
||||
entityType="npc"
|
||||
[entityId]="npcId"
|
||||
[isOpen]="chatOpen"
|
||||
welcomeMessage="Je vois cette fiche de PNJ. Demande-moi de proposer apparence, motivations, secrets, ou répliques signatures."
|
||||
[quickSuggestions]="chatQuickSuggestions"
|
||||
(close)="chatOpen = false">
|
||||
</app-ai-chat-drawer>
|
||||
@if (npcId && campaignId) {
|
||||
<app-ai-chat-drawer
|
||||
[campaignId]="campaignId"
|
||||
entityType="npc"
|
||||
[entityId]="npcId"
|
||||
[isOpen]="chatOpen"
|
||||
welcomeMessage="Je vois cette fiche de PNJ. Demande-moi de proposer apparence, motivations, secrets, ou répliques signatures."
|
||||
[quickSuggestions]="chatQuickSuggestions"
|
||||
(close)="chatOpen = false">
|
||||
</app-ai-chat-drawer>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { LucideAngularModule, Save, ArrowLeft, Drama, Trash2, Sparkles } from 'lucide-angular';
|
||||
@@ -21,7 +21,7 @@ import { ConfirmDialogService } from '../../../shared/confirm-dialog/confirm-dia
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-npc-edit',
|
||||
imports: [CommonModule, FormsModule, LucideAngularModule, AiChatDrawerComponent, DynamicFieldsFormComponent, SingleImagePickerComponent],
|
||||
imports: [FormsModule, LucideAngularModule, AiChatDrawerComponent, DynamicFieldsFormComponent, SingleImagePickerComponent],
|
||||
templateUrl: './npc-edit.component.html',
|
||||
styleUrls: ['./npc-edit.component.scss']
|
||||
})
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
Retour
|
||||
</button>
|
||||
<span class="spacer"></span>
|
||||
<button class="btn-ai" (click)="toggleChat()" [class.active]="chatOpen" *ngIf="npcId">
|
||||
<lucide-icon [img]="Sparkles" [size]="14"></lucide-icon>
|
||||
Assistant IA
|
||||
</button>
|
||||
@if (npcId) {
|
||||
<button class="btn-ai" (click)="toggleChat()" [class.active]="chatOpen">
|
||||
<lucide-icon [img]="Sparkles" [size]="14"></lucide-icon>
|
||||
Assistant IA
|
||||
</button>
|
||||
}
|
||||
<button class="btn-edit" (click)="edit()">
|
||||
<lucide-icon [img]="Edit3" [size]="14"></lucide-icon>
|
||||
Editer
|
||||
@@ -18,11 +20,12 @@
|
||||
<app-persona-view [persona]="npc" [templateFields]="templateFields"></app-persona-view>
|
||||
</div>
|
||||
|
||||
<app-ai-chat-drawer
|
||||
*ngIf="npcId && campaignId"
|
||||
[campaignId]="campaignId"
|
||||
entityType="npc"
|
||||
[entityId]="npcId"
|
||||
[isOpen]="chatOpen"
|
||||
(close)="chatOpen = false">
|
||||
</app-ai-chat-drawer>
|
||||
@if (npcId && campaignId) {
|
||||
<app-ai-chat-drawer
|
||||
[campaignId]="campaignId"
|
||||
entityType="npc"
|
||||
[entityId]="npcId"
|
||||
[isOpen]="chatOpen"
|
||||
(close)="chatOpen = false">
|
||||
</app-ai-chat-drawer>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { LucideAngularModule, ArrowLeft, Edit3, Sparkles } from 'lucide-angular';
|
||||
@@ -18,7 +18,7 @@ import { AiChatDrawerComponent } from '../../../shared/ai-chat-drawer/ai-chat-dr
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-npc-view',
|
||||
imports: [CommonModule, LucideAngularModule, PersonaViewComponent, AiChatDrawerComponent],
|
||||
imports: [LucideAngularModule, PersonaViewComponent, AiChatDrawerComponent],
|
||||
templateUrl: './npc-view.component.html',
|
||||
styleUrls: ['./npc-view.component.scss']
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user