Export/import par campagne + espace de jeu (v2), fix du nombre de joueurs, et ergonomie de jeu
Export / import de contenu (format v2) - Export SÉLECTIF : on peut exporter une seule campagne (sa clôture : arcs → chapitres → scènes, PNJ, ennemis, catalogues, tables, système de jeu lié) au lieu de tout exporter. Options par export : inclure le lore, l'espace de jeu, les images. L'export global devient « Sauvegarde complète ». - ESPACE DE JEU désormais exporté/importé : parties (Playthrough), séances, journal, flags, progression de quêtes, et feuilles de perso (Character.playthroughId préservé au lieu d'être annulé). Remapping complet des références à l'import. - Format v2 rétro-compatible : un export v1 (sans section jeu) s'importe toujours (champs absents → null → traités comme vides). - Correction d'un bug latent : collectReferencedStorageKeys traitait les références d'images comme des clés de stockage alors que ce sont des IDs → les binaires d'images n'étaient en réalité pas embarqués dans le zip. Résolution ID → clé. - Images des Rooms d'une scène (illustration + plan) désormais collectées (elles étaient oubliées des deux collecteurs). - Endpoint : GET /api/admin/data/export?campaignId=&includeLore=&includePlay=&includeImages= ; écran « Sauvegarde des données » avec choix campagne + cases à cocher. - Tests : round-trip étendu au jeu + binaire image réel, export sélectif par campagne, import d'archive v1, et DataTransferController. Correction : nombre de joueurs non persisté - playerCount était saisi à la création mais jeté par le backend (absent du domaine/entité/DTO/mapper) → affichage « 0 joueurs ». Ajouté sur toute la stack + migration V7 (défaut 4 pour les campagnes existantes) + intégré à l'export. Ergonomie de l'espace de jeu et des listes - Description de campagne repliable (clamp 3 lignes + « Voir plus / moins »). - Dossiers repliables (chevron + compteur) dans les listes PNJ et Ennemis, sur la page campagne ET les pages dédiées. - Renommage en place des parties (« Mes parties ») via un crayon. - Panneau de session : onglets sur 2 rangs (« Scènes » n'est plus rogné) ; scènes triées par ordre manuel ; arcs repliables (repliés par défaut) ; PJ/PNJ repliables et PNJ regroupés par dossier.
This commit is contained in:
@@ -5,7 +5,14 @@
|
||||
<div class="detail-header">
|
||||
<div class="header-texts">
|
||||
<h1>{{ campaign.name }}</h1>
|
||||
<p class="description">{{ campaign.description }}</p>
|
||||
@if (campaign.description) {
|
||||
<p class="description" [class.description--clamped]="!descExpanded">{{ campaign.description }}</p>
|
||||
@if (campaign.description.length > 180) {
|
||||
<button type="button" class="desc-toggle" (click)="descExpanded = !descExpanded">
|
||||
{{ (descExpanded ? 'campaignDetail.showLess' : 'campaignDetail.showMore') | translate }}
|
||||
</button>
|
||||
}
|
||||
}
|
||||
<div class="meta">
|
||||
<span class="badge">{{ 'campaignDetail.players' | translate:{ n: campaign.playerCount || 0 } }}</span>
|
||||
<!-- Badge "Univers" : lien vers le Lore associé si présent -->
|
||||
@@ -141,22 +148,26 @@
|
||||
</div>
|
||||
@if (npcs.length > 0) {
|
||||
@for (g of npcGroups; track g.folder) {
|
||||
@if (g.folder) {
|
||||
<h4 class="persona-folder">{{ g.folder.split('/').join(' / ') }}</h4>
|
||||
} @else if (npcGroups.length > 1) {
|
||||
<h4 class="persona-folder persona-folder--none">{{ 'campaignTree.unclassified' | translate }}</h4>
|
||||
@if (g.folder || npcGroups.length > 1) {
|
||||
<button type="button" class="persona-folder" [class.persona-folder--none]="!g.folder" (click)="toggleNpcFolder(g.folder)">
|
||||
<lucide-icon [img]="isNpcFolderCollapsed(g.folder) ? ChevronRight : ChevronDown" [size]="14"></lucide-icon>
|
||||
{{ g.folder ? g.folder.split('/').join(' / ') : ('campaignTree.unclassified' | translate) }}
|
||||
<span class="persona-folder-count">{{ g.items.length }}</span>
|
||||
</button>
|
||||
}
|
||||
<div class="characters-grid">
|
||||
@for (npc of g.items; track npc.id) {
|
||||
<div class="character-card" (click)="viewNpc(npc)">
|
||||
<lucide-icon [img]="Drama" [size]="20" class="character-icon character-icon--npc"></lucide-icon>
|
||||
<div class="character-info">
|
||||
<span class="character-name">{{ npc.name }}</span>
|
||||
<span class="character-snippet">{{ personaSnippet(npc) }}</span>
|
||||
@if (!isNpcFolderCollapsed(g.folder)) {
|
||||
<div class="characters-grid">
|
||||
@for (npc of g.items; track npc.id) {
|
||||
<div class="character-card" (click)="viewNpc(npc)">
|
||||
<lucide-icon [img]="Drama" [size]="20" class="character-icon character-icon--npc"></lucide-icon>
|
||||
<div class="character-info">
|
||||
<span class="character-name">{{ npc.name }}</span>
|
||||
<span class="character-snippet">{{ personaSnippet(npc) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
@if (npcs.length === 0) {
|
||||
@@ -222,15 +233,34 @@
|
||||
@if (playthroughs.length > 0) {
|
||||
<div class="playthroughs-grid">
|
||||
@for (p of playthroughs; track p) {
|
||||
<div class="playthrough-card"
|
||||
(click)="openPlaythrough(p)">
|
||||
<div class="playthrough-card" (click)="openPlaythrough(p)">
|
||||
<lucide-icon [img]="Dices" [size]="22" class="playthrough-icon"></lucide-icon>
|
||||
<div class="playthrough-info">
|
||||
<span class="playthrough-name">{{ p.name }}</span>
|
||||
@if (p.description) {
|
||||
<span class="playthrough-meta">{{ p.description }}</span>
|
||||
@if (editingPlaythroughId === p.id) {
|
||||
<input class="playthrough-rename-input"
|
||||
[(ngModel)]="editPlaythroughName"
|
||||
(click)="$event.stopPropagation()"
|
||||
(keydown.enter)="savePlaythroughRename(p)"
|
||||
(keydown.escape)="cancelPlaythroughRename()" />
|
||||
} @else {
|
||||
<span class="playthrough-name">{{ p.name }}</span>
|
||||
@if (p.description) {
|
||||
<span class="playthrough-meta">{{ p.description }}</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (editingPlaythroughId === p.id) {
|
||||
<button type="button" class="btn-icon" (click)="savePlaythroughRename(p); $event.stopPropagation()" [title]="'common.validate' | translate">
|
||||
<lucide-icon [img]="Check" [size]="15"></lucide-icon>
|
||||
</button>
|
||||
<button type="button" class="btn-icon" (click)="cancelPlaythroughRename(); $event.stopPropagation()" [title]="'common.cancel' | translate">
|
||||
<lucide-icon [img]="X" [size]="15"></lucide-icon>
|
||||
</button>
|
||||
} @else {
|
||||
<button type="button" class="btn-icon playthrough-edit" (click)="startRenamePlaythrough(p, $event)" [title]="'common.rename' | translate">
|
||||
<lucide-icon [img]="Pencil" [size]="15"></lucide-icon>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user