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:
@@ -2,7 +2,9 @@
|
||||
|
||||
<div class="page-header">
|
||||
<h1>Créer une nouvelle scène</h1>
|
||||
<p class="chapter-ref" *ngIf="chapterName">Chapitre : {{ chapterName }}</p>
|
||||
@if (chapterName) {
|
||||
<p class="chapter-ref">Chapitre : {{ chapterName }}</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<form [formGroup]="form" (ngSubmit)="submit()" class="scene-form">
|
||||
@@ -15,7 +17,7 @@
|
||||
formControlName="name"
|
||||
placeholder="Ex: Arrivée au village"
|
||||
[class.invalid]="form.get('name')?.invalid && form.get('name')?.touched"
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { forkJoin } from 'rxjs';
|
||||
@@ -19,7 +19,7 @@ import { CAMPAIGN_ICON_OPTIONS } from '../../campaign-icons';
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-scene-create',
|
||||
imports: [CommonModule, ReactiveFormsModule, LucideAngularModule, IconPickerComponent],
|
||||
imports: [ReactiveFormsModule, LucideAngularModule, IconPickerComponent],
|
||||
templateUrl: './scene-create.component.html',
|
||||
styleUrls: ['./scene-create.component.scss']
|
||||
})
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button type="button" class="btn-ai"
|
||||
(click)="toggleChat()"
|
||||
[class.active]="chatOpen"
|
||||
title="Ouvrir l'Assistant IA pour dialoguer autour de cette scène">
|
||||
(click)="toggleChat()"
|
||||
[class.active]="chatOpen"
|
||||
title="Ouvrir l'Assistant IA pour dialoguer autour de cette scène">
|
||||
<lucide-icon [img]="Sparkles" [size]="14"></lucide-icon>
|
||||
Assistant IA
|
||||
</button>
|
||||
@@ -58,7 +58,7 @@
|
||||
formControlName="name"
|
||||
placeholder="Ex: Arrivée au village"
|
||||
[class.invalid]="form.get('name')?.invalid && form.get('name')?.touched"
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
@@ -136,60 +136,63 @@
|
||||
|
||||
<!-- Section : Branches narratives (graphe intra-chapitre) -->
|
||||
<app-expandable-section title="Branches narratives" icon="🌿">
|
||||
<div class="branches-hint" *ngIf="siblingScenes.length === 0">
|
||||
<small class="field-hint">
|
||||
💡 Il faut au moins une autre scène dans ce chapitre pour créer des branches.
|
||||
Créez d'abord d'autres scènes, puis revenez ici pour les connecter.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="branches-list" *ngIf="siblingScenes.length > 0">
|
||||
<div class="branch-item" *ngFor="let branch of branches; let i = index; trackBy: trackByIndex">
|
||||
<div class="field">
|
||||
<label>Libellé du choix</label>
|
||||
<input
|
||||
type="text"
|
||||
[value]="branch.label"
|
||||
(input)="updateBranchLabel(i, $any($event.target).value)"
|
||||
placeholder="Ex: Si les joueurs attaquent le garde" />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Scène de destination *</label>
|
||||
<select
|
||||
(change)="updateBranchTarget(i, $any($event.target).value)">
|
||||
<option value="" [selected]="!branch.targetSceneId">— Choisir une scène —</option>
|
||||
<option *ngFor="let s of siblingScenes"
|
||||
[value]="s.id"
|
||||
[selected]="s.id === branch.targetSceneId">{{ s.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Condition MJ (optionnel)</label>
|
||||
<input
|
||||
type="text"
|
||||
[value]="branch.condition || ''"
|
||||
(input)="updateBranchCondition(i, $any($event.target).value)"
|
||||
placeholder="Ex: Jet de Persuasion DD 15 réussi" />
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-remove-branch" (click)="removeBranch(i)"
|
||||
title="Supprimer cette branche">
|
||||
<lucide-icon [img]="Trash2" [size]="14"></lucide-icon>
|
||||
Retirer
|
||||
</button>
|
||||
@if (siblingScenes.length === 0) {
|
||||
<div class="branches-hint">
|
||||
<small class="field-hint">
|
||||
💡 Il faut au moins une autre scène dans ce chapitre pour créer des branches.
|
||||
Créez d'abord d'autres scènes, puis revenez ici pour les connecter.
|
||||
</small>
|
||||
</div>
|
||||
}
|
||||
|
||||
<button type="button" class="btn-add-branch" (click)="addBranch()">
|
||||
+ Ajouter une branche
|
||||
</button>
|
||||
|
||||
<small class="field-hint">
|
||||
Chaque branche représente une "sortie" possible depuis cette scène selon l'action des joueurs.
|
||||
Les cibles sont limitées aux scènes du même chapitre.
|
||||
</small>
|
||||
</div>
|
||||
@if (siblingScenes.length > 0) {
|
||||
<div class="branches-list">
|
||||
@for (branch of branches; track $index; let i = $index) {
|
||||
<div class="branch-item">
|
||||
<div class="field">
|
||||
<label>Libellé du choix</label>
|
||||
<input
|
||||
type="text"
|
||||
[value]="branch.label"
|
||||
(input)="updateBranchLabel(i, $any($event.target).value)"
|
||||
placeholder="Ex: Si les joueurs attaquent le garde" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Scène de destination *</label>
|
||||
<select
|
||||
(change)="updateBranchTarget(i, $any($event.target).value)">
|
||||
<option value="" [selected]="!branch.targetSceneId">— Choisir une scène —</option>
|
||||
@for (s of siblingScenes; track s) {
|
||||
<option
|
||||
[value]="s.id"
|
||||
[selected]="s.id === branch.targetSceneId">{{ s.name }}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Condition MJ (optionnel)</label>
|
||||
<input
|
||||
type="text"
|
||||
[value]="branch.condition || ''"
|
||||
(input)="updateBranchCondition(i, $any($event.target).value)"
|
||||
placeholder="Ex: Jet de Persuasion DD 15 réussi" />
|
||||
</div>
|
||||
<button type="button" class="btn-remove-branch" (click)="removeBranch(i)"
|
||||
title="Supprimer cette branche">
|
||||
<lucide-icon [img]="Trash2" [size]="14"></lucide-icon>
|
||||
Retirer
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<button type="button" class="btn-add-branch" (click)="addBranch()">
|
||||
+ Ajouter une branche
|
||||
</button>
|
||||
<small class="field-hint">
|
||||
Chaque branche représente une "sortie" possible depuis cette scène selon l'action des joueurs.
|
||||
Les cibles sont limitées aux scènes du même chapitre.
|
||||
</small>
|
||||
</div>
|
||||
}
|
||||
</app-expandable-section>
|
||||
|
||||
<!-- Section : Combat ou rencontre -->
|
||||
@@ -210,26 +213,30 @@
|
||||
</app-expandable-section>
|
||||
|
||||
<!-- Section : Pages Lore associées (B2 cross-context) -->
|
||||
<app-expandable-section title="Pages Lore associées" icon="🔗" *ngIf="loreId">
|
||||
@if (loreId) {
|
||||
<app-expandable-section title="Pages Lore associées" icon="🔗">
|
||||
<div class="field">
|
||||
<app-lore-link-picker
|
||||
[value]="relatedPageIds"
|
||||
[availablePages]="availablePages"
|
||||
[loreId]="loreId"
|
||||
(valueChange)="relatedPageIds = $event">
|
||||
</app-lore-link-picker>
|
||||
<small class="field-hint">
|
||||
Épinglez ici le lieu, les PNJ ou créatures de cette scène. Cliquez sur un chip pour ouvrir la page.
|
||||
</small>
|
||||
</div>
|
||||
</app-expandable-section>
|
||||
}
|
||||
|
||||
@if (!loreId) {
|
||||
<div class="field">
|
||||
<app-lore-link-picker
|
||||
[value]="relatedPageIds"
|
||||
[availablePages]="availablePages"
|
||||
[loreId]="loreId"
|
||||
(valueChange)="relatedPageIds = $event">
|
||||
</app-lore-link-picker>
|
||||
<small class="field-hint">
|
||||
Épinglez ici le lieu, les PNJ ou créatures de cette scène. Cliquez sur un chip pour ouvrir la page.
|
||||
💡 Cette campagne n'est associée à aucun univers. Associez-la à un Lore dans l'écran de la campagne
|
||||
pour pouvoir épingler des pages du Lore à cette scène.
|
||||
</small>
|
||||
</div>
|
||||
</app-expandable-section>
|
||||
|
||||
<div class="field" *ngIf="!loreId">
|
||||
<small class="field-hint">
|
||||
💡 Cette campagne n'est associée à aucun univers. Associez-la à un Lore dans l'écran de la campagne
|
||||
pour pouvoir épingler des pages du Lore à cette scène.
|
||||
</small>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Lieu explorable : pièces / donjon -->
|
||||
<app-expandable-section title="Lieu explorable (donjon, crypte…)" icon="🏰" [initiallyOpen]="rooms.length > 0">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { forkJoin, of } from 'rxjs';
|
||||
@@ -30,7 +30,7 @@ import { ConfirmDialogService } from '../../../shared/confirm-dialog/confirm-dia
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-scene-edit',
|
||||
imports: [CommonModule, ReactiveFormsModule, LucideAngularModule, ExpandableSectionComponent, LoreLinkPickerComponent, AiChatDrawerComponent, ImageGalleryComponent, IconPickerComponent, RoomsEditorComponent],
|
||||
imports: [ReactiveFormsModule, LucideAngularModule, ExpandableSectionComponent, LoreLinkPickerComponent, AiChatDrawerComponent, ImageGalleryComponent, IconPickerComponent, RoomsEditorComponent],
|
||||
templateUrl: './scene-edit.component.html',
|
||||
styleUrls: ['./scene-edit.component.scss']
|
||||
})
|
||||
@@ -218,7 +218,6 @@ export class SceneEditComponent implements OnInit, OnDestroy {
|
||||
|
||||
// ─────────────── Gestion des branches narratives ───────────────
|
||||
|
||||
trackByIndex = (i: number) => i;
|
||||
|
||||
addBranch(): void {
|
||||
this.branches.push({ label: '', targetSceneId: '', condition: '' });
|
||||
|
||||
@@ -1,147 +1,188 @@
|
||||
<div class="view-page" *ngIf="scene">
|
||||
|
||||
<header class="view-header">
|
||||
<div>
|
||||
<h1>
|
||||
<lucide-icon *ngIf="scene.icon" [img]="resolveCampaignIcon(scene.icon)" [size]="22" class="title-icon"></lucide-icon>
|
||||
{{ scene.name }}
|
||||
</h1>
|
||||
<p class="view-subtitle">Scène</p>
|
||||
</div>
|
||||
<div class="view-actions">
|
||||
<button type="button" class="btn-primary" (click)="editMode()">
|
||||
<lucide-icon [img]="Pencil" [size]="14"></lucide-icon>
|
||||
Modifier
|
||||
</button>
|
||||
<button type="button" class="btn-danger" (click)="deleteScene()" title="Supprimer la scène">
|
||||
<lucide-icon [img]="Trash2" [size]="14"></lucide-icon>
|
||||
Supprimer
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Illustrations (rendu editorial magazine) -->
|
||||
<section class="view-section" *ngIf="(scene.illustrationImageIds?.length ?? 0) > 0">
|
||||
<app-image-gallery [imageIds]="scene.illustrationImageIds ?? []" [layout]="'EDITORIAL'"></app-image-gallery>
|
||||
</section>
|
||||
|
||||
<!-- Cartes & plans -->
|
||||
<section class="view-section" *ngIf="(scene.mapImageIds?.length ?? 0) > 0">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">🗺️</span> Cartes & plans</h2>
|
||||
<app-image-gallery [imageIds]="scene.mapImageIds ?? []" [layout]="'MAPS'"></app-image-gallery>
|
||||
</section>
|
||||
|
||||
<!-- Description courte -->
|
||||
<section class="view-section">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">📝</span> Description</h2>
|
||||
<p class="view-section-body" *ngIf="scene.description?.trim(); else emptyDesc">{{ scene.description }}</p>
|
||||
<ng-template #emptyDesc><p class="view-section-empty">Non renseigné</p></ng-template>
|
||||
</section>
|
||||
|
||||
<!-- Contexte et ambiance -->
|
||||
<div class="view-row" *ngIf="scene.location?.trim() || scene.timing?.trim()">
|
||||
<section class="view-section" *ngIf="scene.location?.trim()">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">📍</span> Lieu</h2>
|
||||
<p class="view-section-body">{{ scene.location }}</p>
|
||||
</section>
|
||||
<section class="view-section" *ngIf="scene.timing?.trim()">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">⏰</span> Moment</h2>
|
||||
<p class="view-section-body">{{ scene.timing }}</p>
|
||||
@if (scene) {
|
||||
<div class="view-page">
|
||||
<header class="view-header">
|
||||
<div>
|
||||
<h1>
|
||||
@if (scene.icon) {
|
||||
<lucide-icon [img]="resolveCampaignIcon(scene.icon)" [size]="22" class="title-icon"></lucide-icon>
|
||||
}
|
||||
{{ scene.name }}
|
||||
</h1>
|
||||
<p class="view-subtitle">Scène</p>
|
||||
</div>
|
||||
<div class="view-actions">
|
||||
<button type="button" class="btn-primary" (click)="editMode()">
|
||||
<lucide-icon [img]="Pencil" [size]="14"></lucide-icon>
|
||||
Modifier
|
||||
</button>
|
||||
<button type="button" class="btn-danger" (click)="deleteScene()" title="Supprimer la scène">
|
||||
<lucide-icon [img]="Trash2" [size]="14"></lucide-icon>
|
||||
Supprimer
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<!-- Illustrations (rendu editorial magazine) -->
|
||||
@if ((scene.illustrationImageIds?.length ?? 0) > 0) {
|
||||
<section class="view-section">
|
||||
<app-image-gallery [imageIds]="scene.illustrationImageIds ?? []" [layout]="'EDITORIAL'"></app-image-gallery>
|
||||
</section>
|
||||
}
|
||||
<!-- Cartes & plans -->
|
||||
@if ((scene.mapImageIds?.length ?? 0) > 0) {
|
||||
<section class="view-section">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">🗺️</span> Cartes & plans</h2>
|
||||
<app-image-gallery [imageIds]="scene.mapImageIds ?? []" [layout]="'MAPS'"></app-image-gallery>
|
||||
</section>
|
||||
}
|
||||
<!-- Description courte -->
|
||||
<section class="view-section">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">📝</span> Description</h2>
|
||||
@if (scene.description?.trim()) {
|
||||
<p class="view-section-body">{{ scene.description }}</p>
|
||||
} @else {
|
||||
<p class="view-section-empty">Non renseigné</p>
|
||||
}
|
||||
</section>
|
||||
<!-- Contexte et ambiance -->
|
||||
@if (scene.location?.trim() || scene.timing?.trim()) {
|
||||
<div class="view-row">
|
||||
@if (scene.location?.trim()) {
|
||||
<section class="view-section">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">📍</span> Lieu</h2>
|
||||
<p class="view-section-body">{{ scene.location }}</p>
|
||||
</section>
|
||||
}
|
||||
@if (scene.timing?.trim()) {
|
||||
<section class="view-section">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">⏰</span> Moment</h2>
|
||||
<p class="view-section-body">{{ scene.timing }}</p>
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (scene.atmosphere?.trim()) {
|
||||
<section class="view-section">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">🌫️</span> Ambiance et atmosphère</h2>
|
||||
<p class="view-section-body">{{ scene.atmosphere }}</p>
|
||||
</section>
|
||||
}
|
||||
<!-- Narration pour les joueurs -->
|
||||
@if (scene.playerNarration?.trim()) {
|
||||
<section class="view-section">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">📖</span> Narration pour les joueurs</h2>
|
||||
<p class="view-section-body">{{ scene.playerNarration }}</p>
|
||||
</section>
|
||||
}
|
||||
<!-- Choix et conséquences -->
|
||||
@if (scene.choicesConsequences?.trim()) {
|
||||
<section class="view-section">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">🔀</span> Choix et conséquences</h2>
|
||||
<p class="view-section-body">{{ scene.choicesConsequences }}</p>
|
||||
</section>
|
||||
}
|
||||
<!-- Combat ou rencontre -->
|
||||
@if (scene.combatDifficulty?.trim() || scene.enemies?.trim()) {
|
||||
@if (scene.combatDifficulty?.trim()) {
|
||||
<section class="view-section">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">⚔️</span> Difficulté estimée</h2>
|
||||
<p class="view-section-body">{{ scene.combatDifficulty }}</p>
|
||||
</section>
|
||||
}
|
||||
@if (scene.enemies?.trim()) {
|
||||
<section class="view-section">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">🐲</span> Ennemis et créatures</h2>
|
||||
<p class="view-section-body">{{ scene.enemies }}</p>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
<!-- Notes et secrets du MJ (privé) -->
|
||||
@if (scene.gmSecretNotes?.trim()) {
|
||||
<section class="view-section view-section--private">
|
||||
<h2 class="view-section-title">
|
||||
<span class="view-section-icon">🔒</span>
|
||||
Notes et secrets du MJ
|
||||
</h2>
|
||||
<p class="view-section-body">{{ scene.gmSecretNotes }}</p>
|
||||
</section>
|
||||
}
|
||||
<!-- Pages Lore liées -->
|
||||
@if (loreId && (scene.relatedPageIds?.length ?? 0) > 0) {
|
||||
<section class="view-section">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">🔗</span> Pages Lore associées</h2>
|
||||
<div class="view-chips">
|
||||
@for (relId of scene.relatedPageIds; track relId) {
|
||||
<a class="view-chip"
|
||||
[routerLink]="['/lore', loreId, 'pages', relId]">
|
||||
{{ titleOfRelated(relId) }}
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
<!-- Lieu explorable : pièces (mode donjon) -->
|
||||
@if ((scene.rooms?.length ?? 0) > 0) {
|
||||
<section class="view-section">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">🏰</span> Pièces du lieu</h2>
|
||||
<div class="rooms-readonly">
|
||||
@for (r of scene.rooms; track r; let i = $index) {
|
||||
<article class="room-readonly">
|
||||
<header class="room-readonly-head">
|
||||
<span class="room-readonly-index">#{{ i + 1 }}</span>
|
||||
<span class="room-readonly-name">{{ r.name }}</span>
|
||||
@if (r.floor !== null && r.floor !== undefined) {
|
||||
<span class="room-readonly-floor">
|
||||
Étage {{ r.floor }}
|
||||
</span>
|
||||
}
|
||||
</header>
|
||||
@if (r.description?.trim()) {
|
||||
<p class="room-readonly-desc">{{ r.description }}</p>
|
||||
}
|
||||
<div class="room-readonly-grid">
|
||||
@if (r.enemies?.trim()) {
|
||||
<div>
|
||||
<strong>⚔️ Ennemis</strong>
|
||||
<p>{{ r.enemies }}</p>
|
||||
</div>
|
||||
}
|
||||
@if (r.loot?.trim()) {
|
||||
<div>
|
||||
<strong>💰 Loot</strong>
|
||||
<p>{{ r.loot }}</p>
|
||||
</div>
|
||||
}
|
||||
@if (r.traps?.trim()) {
|
||||
<div>
|
||||
<strong>⚠️ Pièges</strong>
|
||||
<p>{{ r.traps }}</p>
|
||||
</div>
|
||||
}
|
||||
@if (r.gmNotes?.trim()) {
|
||||
<div class="room-readonly-private">
|
||||
<strong>🔒 Notes MJ</strong>
|
||||
<p>{{ r.gmNotes }}</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if ((r.branches?.length ?? 0) > 0) {
|
||||
<div class="room-readonly-branches">
|
||||
<strong>→ Sorties</strong>
|
||||
<ul>
|
||||
@for (b of r.branches; track b) {
|
||||
<li>
|
||||
<em>{{ b.label }}</em> → {{ roomNameById(scene, b.targetRoomId) }}
|
||||
@if (b.condition?.trim()) {
|
||||
<span class="branch-cond">(si : {{ b.condition }})</span>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
|
||||
<section class="view-section" *ngIf="scene.atmosphere?.trim()">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">🌫️</span> Ambiance et atmosphère</h2>
|
||||
<p class="view-section-body">{{ scene.atmosphere }}</p>
|
||||
</section>
|
||||
|
||||
<!-- Narration pour les joueurs -->
|
||||
<section class="view-section" *ngIf="scene.playerNarration?.trim()">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">📖</span> Narration pour les joueurs</h2>
|
||||
<p class="view-section-body">{{ scene.playerNarration }}</p>
|
||||
</section>
|
||||
|
||||
<!-- Choix et conséquences -->
|
||||
<section class="view-section" *ngIf="scene.choicesConsequences?.trim()">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">🔀</span> Choix et conséquences</h2>
|
||||
<p class="view-section-body">{{ scene.choicesConsequences }}</p>
|
||||
</section>
|
||||
|
||||
<!-- Combat ou rencontre -->
|
||||
<ng-container *ngIf="scene.combatDifficulty?.trim() || scene.enemies?.trim()">
|
||||
<section class="view-section" *ngIf="scene.combatDifficulty?.trim()">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">⚔️</span> Difficulté estimée</h2>
|
||||
<p class="view-section-body">{{ scene.combatDifficulty }}</p>
|
||||
</section>
|
||||
<section class="view-section" *ngIf="scene.enemies?.trim()">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">🐲</span> Ennemis et créatures</h2>
|
||||
<p class="view-section-body">{{ scene.enemies }}</p>
|
||||
</section>
|
||||
</ng-container>
|
||||
|
||||
<!-- Notes et secrets du MJ (privé) -->
|
||||
<section class="view-section view-section--private" *ngIf="scene.gmSecretNotes?.trim()">
|
||||
<h2 class="view-section-title">
|
||||
<span class="view-section-icon">🔒</span>
|
||||
Notes et secrets du MJ
|
||||
</h2>
|
||||
<p class="view-section-body">{{ scene.gmSecretNotes }}</p>
|
||||
</section>
|
||||
|
||||
<!-- Pages Lore liées -->
|
||||
<section class="view-section" *ngIf="loreId && (scene.relatedPageIds?.length ?? 0) > 0">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">🔗</span> Pages Lore associées</h2>
|
||||
<div class="view-chips">
|
||||
<a class="view-chip"
|
||||
*ngFor="let relId of scene.relatedPageIds"
|
||||
[routerLink]="['/lore', loreId, 'pages', relId]">
|
||||
{{ titleOfRelated(relId) }}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Lieu explorable : pièces (mode donjon) -->
|
||||
<section class="view-section" *ngIf="(scene.rooms?.length ?? 0) > 0">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">🏰</span> Pièces du lieu</h2>
|
||||
<div class="rooms-readonly">
|
||||
<article class="room-readonly" *ngFor="let r of scene.rooms; let i = index">
|
||||
<header class="room-readonly-head">
|
||||
<span class="room-readonly-index">#{{ i + 1 }}</span>
|
||||
<span class="room-readonly-name">{{ r.name }}</span>
|
||||
<span class="room-readonly-floor" *ngIf="r.floor !== null && r.floor !== undefined">
|
||||
Étage {{ r.floor }}
|
||||
</span>
|
||||
</header>
|
||||
<p class="room-readonly-desc" *ngIf="r.description?.trim()">{{ r.description }}</p>
|
||||
<div class="room-readonly-grid">
|
||||
<div *ngIf="r.enemies?.trim()">
|
||||
<strong>⚔️ Ennemis</strong>
|
||||
<p>{{ r.enemies }}</p>
|
||||
</div>
|
||||
<div *ngIf="r.loot?.trim()">
|
||||
<strong>💰 Loot</strong>
|
||||
<p>{{ r.loot }}</p>
|
||||
</div>
|
||||
<div *ngIf="r.traps?.trim()">
|
||||
<strong>⚠️ Pièges</strong>
|
||||
<p>{{ r.traps }}</p>
|
||||
</div>
|
||||
<div *ngIf="r.gmNotes?.trim()" class="room-readonly-private">
|
||||
<strong>🔒 Notes MJ</strong>
|
||||
<p>{{ r.gmNotes }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="room-readonly-branches" *ngIf="(r.branches?.length ?? 0) > 0">
|
||||
<strong>→ Sorties</strong>
|
||||
<ul>
|
||||
<li *ngFor="let b of r.branches">
|
||||
<em>{{ b.label }}</em> → {{ roomNameById(scene, b.targetRoomId) }}
|
||||
<span class="branch-cond" *ngIf="b.condition?.trim()">(si : {{ b.condition }})</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||
import { forkJoin, of } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
@@ -24,7 +24,7 @@ import { ConfirmDialogService } from '../../../shared/confirm-dialog/confirm-dia
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-scene-view',
|
||||
imports: [CommonModule, RouterModule, LucideAngularModule, ImageGalleryComponent],
|
||||
imports: [RouterModule, LucideAngularModule, ImageGalleryComponent],
|
||||
templateUrl: './scene-view.component.html',
|
||||
styleUrls: ['./scene-view.component.scss']
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user