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:
@@ -13,7 +13,9 @@
|
||||
|
||||
<h1>{{ tableId ? 'Éditer la table' : 'Nouvelle table aléatoire' }}</h1>
|
||||
|
||||
<div class="error" *ngIf="errorMessage">{{ errorMessage }}</div>
|
||||
@if (errorMessage) {
|
||||
<div class="error">{{ errorMessage }}</div>
|
||||
}
|
||||
|
||||
<div class="form-row">
|
||||
<label for="rt-name">Nom *</label>
|
||||
@@ -28,7 +30,7 @@
|
||||
<div class="form-row">
|
||||
<label for="rt-formula">Formule du dé *</label>
|
||||
<input id="rt-formula" type="text" [(ngModel)]="diceFormula" placeholder="1d20, 2d6, d100…"
|
||||
[class.invalid]="diceFormula && !formulaValid">
|
||||
[class.invalid]="diceFormula && !formulaValid">
|
||||
<small class="hint" [class.bad]="diceFormula && !formulaValid">
|
||||
{{ formulaValid ? 'Formule valide' : 'Format attendu : NdM (ex. 1d20, 2d6, d100)' }}
|
||||
</small>
|
||||
@@ -41,13 +43,15 @@
|
||||
</div>
|
||||
<p class="ai-hint">Décris la table ; l'IA propose les entrées (revois-les avant d'enregistrer). Contextualisé avec ta campagne.</p>
|
||||
<textarea rows="2" [(ngModel)]="aiPrompt"
|
||||
placeholder="Ex: rencontres aléatoires dans une forêt hantée, ton sombre"></textarea>
|
||||
placeholder="Ex: rencontres aléatoires dans une forêt hantée, ton sombre"></textarea>
|
||||
<div class="ai-actions">
|
||||
<button class="btn-ai" (click)="generateWithAI()" [disabled]="generating">
|
||||
<lucide-icon [img]="Sparkles" [size]="14"></lucide-icon>
|
||||
{{ generating ? 'Génération…' : 'Générer (' + diceFormula + ')' }}
|
||||
</button>
|
||||
<span class="ai-error" *ngIf="aiError">{{ aiError }}</span>
|
||||
@if (aiError) {
|
||||
<span class="ai-error">{{ aiError }}</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -71,15 +75,19 @@
|
||||
<span class="c-del"></span>
|
||||
</div>
|
||||
|
||||
<div class="entry-row" *ngFor="let e of entries; let i = index">
|
||||
<input class="c-range" type="number" [(ngModel)]="e.minRoll">
|
||||
<input class="c-range" type="number" [(ngModel)]="e.maxRoll">
|
||||
<input class="c-label" type="text" [(ngModel)]="e.label" placeholder="Résultat">
|
||||
<input class="c-detail" type="text" [(ngModel)]="e.detail" placeholder="Détail (optionnel)">
|
||||
<button class="c-del btn-del" (click)="removeEntry(i)" title="Supprimer">
|
||||
<lucide-icon [img]="Trash2" [size]="14"></lucide-icon>
|
||||
</button>
|
||||
</div>
|
||||
@for (e of entries; track e; let i = $index) {
|
||||
<div class="entry-row">
|
||||
<input class="c-range" type="number" [(ngModel)]="e.minRoll">
|
||||
<input class="c-range" type="number" [(ngModel)]="e.maxRoll">
|
||||
<input class="c-label" type="text" [(ngModel)]="e.label" placeholder="Résultat">
|
||||
<input class="c-detail" type="text" [(ngModel)]="e.detail" placeholder="Détail (optionnel)">
|
||||
<button class="c-del btn-del" (click)="removeEntry(i)" title="Supprimer">
|
||||
<lucide-icon [img]="Trash2" [size]="14"></lucide-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<p class="empty-hint" *ngIf="entries.length === 0">Aucune entrée — clique « Ajouter ».</p>
|
||||
@if (entries.length === 0) {
|
||||
<p class="empty-hint">Aucune entrée — clique « Ajouter ».</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { LucideAngularModule, ArrowLeft, Save, Plus, Trash2, Wand2, Sparkles } from 'lucide-angular';
|
||||
@@ -15,7 +15,7 @@ import { DiceUtils } from '../../../shared/dice.utils';
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-random-table-edit',
|
||||
imports: [CommonModule, FormsModule, LucideAngularModule],
|
||||
imports: [FormsModule, LucideAngularModule],
|
||||
templateUrl: './random-table-edit.component.html',
|
||||
styleUrls: ['./random-table-edit.component.scss']
|
||||
})
|
||||
|
||||
@@ -1,57 +1,75 @@
|
||||
<div class="rt-page" *ngIf="table">
|
||||
<div class="rt-toolbar">
|
||||
<button class="btn-back" (click)="back()">
|
||||
<lucide-icon [img]="ArrowLeft" [size]="14"></lucide-icon>
|
||||
Retour
|
||||
</button>
|
||||
<span class="spacer"></span>
|
||||
<button class="btn-edit" (click)="edit()">
|
||||
<lucide-icon [img]="Edit3" [size]="14"></lucide-icon>
|
||||
Éditer
|
||||
</button>
|
||||
@if (table) {
|
||||
<div class="rt-page">
|
||||
<div class="rt-toolbar">
|
||||
<button class="btn-back" (click)="back()">
|
||||
<lucide-icon [img]="ArrowLeft" [size]="14"></lucide-icon>
|
||||
Retour
|
||||
</button>
|
||||
<span class="spacer"></span>
|
||||
<button class="btn-edit" (click)="edit()">
|
||||
<lucide-icon [img]="Edit3" [size]="14"></lucide-icon>
|
||||
Éditer
|
||||
</button>
|
||||
</div>
|
||||
<header class="rt-header">
|
||||
<h1><lucide-icon [img]="Dices" [size]="22"></lucide-icon> {{ table.name }}</h1>
|
||||
@if (table.description) {
|
||||
<p class="rt-desc">{{ table.description }}</p>
|
||||
}
|
||||
<span class="rt-formula">Dé : <code>{{ table.diceFormula }}</code></span>
|
||||
</header>
|
||||
<!-- Zone de jet -->
|
||||
<section class="rt-roll">
|
||||
<button class="btn-roll" (click)="roll()">
|
||||
<lucide-icon [img]="Dices" [size]="18"></lucide-icon>
|
||||
Lancer {{ table.diceFormula }}
|
||||
</button>
|
||||
@if (lastRoll) {
|
||||
<div class="rt-result">
|
||||
<span class="rt-total">{{ lastRoll.total }}</span>
|
||||
@if (lastRoll.rolls.length > 1) {
|
||||
<span class="rt-rolls">({{ lastRoll.rolls.join(' + ') }})</span>
|
||||
}
|
||||
<span class="rt-arrow">→</span>
|
||||
@if (matched) {
|
||||
<span class="rt-matched">{{ matched.label }}</span>
|
||||
}
|
||||
@if (!matched) {
|
||||
<span class="rt-nomatch">Aucune entrée pour ce résultat</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
@if (matched?.detail) {
|
||||
<div class="rt-detail">{{ matched?.detail }}</div>
|
||||
}
|
||||
<!-- Liste des entrées -->
|
||||
<section class="rt-entries">
|
||||
@if (table.entries.length === 0) {
|
||||
<div class="rt-empty">
|
||||
Aucune entrée — édite la table pour en ajouter.
|
||||
</div>
|
||||
}
|
||||
@if (table.entries.length > 0) {
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th class="col-range">Jet</th><th>Résultat</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (e of table.entries; track e) {
|
||||
<tr [class.matched]="isMatched(e)">
|
||||
<td class="col-range">{{ rangeLabel(e) }}</td>
|
||||
<td>
|
||||
<div class="entry-label">{{ e.label }}</div>
|
||||
@if (e.detail) {
|
||||
<div class="entry-detail">{{ e.detail }}</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<header class="rt-header">
|
||||
<h1><lucide-icon [img]="Dices" [size]="22"></lucide-icon> {{ table.name }}</h1>
|
||||
<p class="rt-desc" *ngIf="table.description">{{ table.description }}</p>
|
||||
<span class="rt-formula">Dé : <code>{{ table.diceFormula }}</code></span>
|
||||
</header>
|
||||
|
||||
<!-- Zone de jet -->
|
||||
<section class="rt-roll">
|
||||
<button class="btn-roll" (click)="roll()">
|
||||
<lucide-icon [img]="Dices" [size]="18"></lucide-icon>
|
||||
Lancer {{ table.diceFormula }}
|
||||
</button>
|
||||
<div class="rt-result" *ngIf="lastRoll">
|
||||
<span class="rt-total">{{ lastRoll.total }}</span>
|
||||
<span class="rt-rolls" *ngIf="lastRoll.rolls.length > 1">({{ lastRoll.rolls.join(' + ') }})</span>
|
||||
<span class="rt-arrow">→</span>
|
||||
<span class="rt-matched" *ngIf="matched">{{ matched.label }}</span>
|
||||
<span class="rt-nomatch" *ngIf="!matched">Aucune entrée pour ce résultat</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rt-detail" *ngIf="matched?.detail">{{ matched?.detail }}</div>
|
||||
|
||||
<!-- Liste des entrées -->
|
||||
<section class="rt-entries">
|
||||
<div class="rt-empty" *ngIf="table.entries.length === 0">
|
||||
Aucune entrée — édite la table pour en ajouter.
|
||||
</div>
|
||||
<table *ngIf="table.entries.length > 0">
|
||||
<thead>
|
||||
<tr><th class="col-range">Jet</th><th>Résultat</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let e of table.entries" [class.matched]="isMatched(e)">
|
||||
<td class="col-range">{{ rangeLabel(e) }}</td>
|
||||
<td>
|
||||
<div class="entry-label">{{ e.label }}</div>
|
||||
<div class="entry-detail" *ngIf="e.detail">{{ e.detail }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { LucideAngularModule, ArrowLeft, Edit3, Dices } from 'lucide-angular';
|
||||
import { RandomTableService } from '../../../services/random-table.service';
|
||||
@@ -14,7 +14,7 @@ import { DiceUtils, DiceRoll } from '../../../shared/dice.utils';
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-random-table-view',
|
||||
imports: [CommonModule, LucideAngularModule],
|
||||
imports: [LucideAngularModule],
|
||||
templateUrl: './random-table-view.component.html',
|
||||
styleUrls: ['./random-table-view.component.scss']
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user