Files
LoreMind/web/src/app/lore/folder-view/folder-view.component.ts
IETM_FIXE\ietm6 f24ef0891e
All checks were successful
Build & Push Images / build (brain) (push) Successful in 1m36s
Build & Push Images / build (core) (push) Successful in 2m53s
Build & Push Images / build (web) (push) Successful in 2m36s
Ajout de tests playwright et correction de tests non passant (pour les tests ajoutés : partie game system ).
Correction de plusieurs anomalies : problème de switch entre 2 templates (par exemple si on était sur un template 1 et qu'on voulait passer directement au 2, ce dernier ne chargeait pas) ;
correction du soucis d'apparition de la sidebar à gauche qui disparaissait sans explication ; problème de redirection : lorsqu'on terminait de créer un PJ / PNJ ; on arrivait sur l'accueil de la campagne au lieu de voir le résultat de la création.
Problème de redirection également lors du clique sur un PNJ / PJ sur le coté : on arrivait sur l'édition au lieu de la présentation. Correction de la première lettre stylisée : tout est au même style comme ça plus de probleme de lecture.

Nouveautées : stylisation des modales (notamment suppression, warning.....) avec en prime l'ajout d'un warning lors du changement de système pour avertir que les fiches persos ne sont pas conservées.
Ajout d'une option pour créer un game system directement à la création d'une campagne afin de faciliter la mise en place de cette dernière.
Ajout d'un bouton pour créer un nouveau template directement lorsqu'on créer une page : ça permet de créer un template et de revenir sur la page qu'on était en train de créer sans perdre le titre.

Passage en bêta 0.8.4
2026-05-19 13:37:22 +02:00

191 lines
7.1 KiB
TypeScript

import { Component, OnDestroy, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
import { forkJoin } from 'rxjs';
import { LucideAngularModule, LucideIconData, Folder, FileText, Pencil, Trash2, Plus, ChevronRight } from 'lucide-angular';
import { LoreService } from '../../services/lore.service';
import { TemplateService } from '../../services/template.service';
import { PageService } from '../../services/page.service';
import { LayoutService } from '../../services/layout.service';
import { PageTitleService } from '../../services/page-title.service';
import { Lore, LoreNode } from '../../services/lore.model';
import { Page } from '../../services/page.model';
import { loadLoreSidebarData, buildLoreSidebarConfig } from '../lore-sidebar.helper';
import { resolveIcon } from '../lore-icons';
import { ConfirmDialogService } from '../../shared/confirm-dialog/confirm-dialog.service';
/**
* Vue "détail" d'un dossier : affiche son contenu (sous-dossiers + pages) et
* expose les actions Modifier / Supprimer dans le header.
*
* L'édition du nom/icône/parent se fait dans l'écran séparé folder-edit
* (/folders/:folderId/edit). La suppression avec cascade déclenche le même
* dialogue d'impact que les autres écrans.
*/
@Component({
selector: 'app-folder-view',
standalone: true,
imports: [CommonModule, LucideAngularModule],
templateUrl: './folder-view.component.html',
styleUrls: ['./folder-view.component.scss']
})
export class FolderViewComponent implements OnInit, OnDestroy {
readonly Folder = Folder;
readonly FileText = FileText;
readonly Pencil = Pencil;
readonly Trash2 = Trash2;
readonly Plus = Plus;
readonly ChevronRight = ChevronRight;
loreId = '';
folderId = '';
lore: Lore | null = null;
node: LoreNode | null = null;
subfolders: LoreNode[] = [];
pages: Page[] = [];
/** Chaîne des dossiers ancêtres (du plus proche du racine vers le parent direct). */
ancestors: LoreNode[] = [];
constructor(
private route: ActivatedRoute,
private router: Router,
private loreService: LoreService,
private templateService: TemplateService,
private pageService: PageService,
private layoutService: LayoutService,
private pageTitleService: PageTitleService,
private confirmDialog: ConfirmDialogService
) {}
ngOnInit(): void {
this.loreId = this.route.snapshot.paramMap.get('loreId')!;
// Réagit aux changements de :folderId pour que la navigation d'un dossier
// à un autre via la sidebar ne démonte/remonte pas le composant à blanc.
this.route.paramMap.subscribe(pm => {
const next = pm.get('folderId')!;
if (next !== this.folderId) {
this.folderId = next;
this.load();
}
});
this.folderId = this.route.snapshot.paramMap.get('folderId')!;
this.load();
}
private load(): void {
forkJoin({
sidebar: loadLoreSidebarData(this.loreId, this.loreService, this.templateService, this.pageService),
node: this.loreService.getLoreNodeById(this.folderId)
}).subscribe(({ sidebar, node }) => {
this.layoutService.show(buildLoreSidebarConfig(sidebar));
this.lore = sidebar.lore;
this.node = node;
this.pageTitleService.set(node.name);
this.subfolders = sidebar.nodes.filter(n => n.parentId === this.folderId);
this.pages = sidebar.pages.filter(p => p.nodeId === this.folderId);
this.ancestors = this.buildAncestors(node, sidebar.nodes);
});
}
/**
* Remonte la chaîne parentId → parent en partant du dossier courant,
* sans s'inclure soi-même. Ordre : racine → parent direct.
* Garde-fou sur la longueur au cas où une boucle existerait en BDD
* (ne devrait pas, mais ceinture+bretelles).
*/
private buildAncestors(current: LoreNode, allNodes: LoreNode[]): LoreNode[] {
const byId = new Map(allNodes.map(n => [n.id!, n]));
const chain: LoreNode[] = [];
const seen = new Set<string>();
let parentId = current.parentId ?? null;
while (parentId && !seen.has(parentId) && chain.length < 32) {
const parent = byId.get(parentId);
if (!parent) break;
chain.push(parent);
seen.add(parent.id!);
parentId = parent.parentId ?? null;
}
return chain.reverse();
}
/** Icône du dossier courant, résolue depuis la clé lucide stockée sur le node. */
get folderIcon(): LucideIconData {
return resolveIcon(this.node?.icon ?? null);
}
navigateToSubfolder(id: string): void {
this.router.navigate(['/lore', this.loreId, 'folders', id]);
}
navigateToLoreRoot(): void {
this.router.navigate(['/lore', this.loreId]);
}
navigateToPage(id: string): void {
this.router.navigate(['/lore', this.loreId, 'pages', id]);
}
navigateToCreateSubfolder(): void {
this.router.navigate(['/lore', this.loreId, 'folders', this.folderId, 'create']);
}
navigateToCreatePage(): void {
this.router.navigate(['/lore', this.loreId, 'nodes', this.folderId, 'pages', 'create']);
}
navigateToEdit(): void {
this.router.navigate(['/lore', this.loreId, 'folders', this.folderId, 'edit']);
}
/**
* Suppression en cascade avec dialogue d'impact. On délègue au backend (transaction
* atomique), et au retour on remonte soit au dossier parent soit au Lore racine.
*/
delete(): void {
if (!this.node) return;
const node = this.node;
this.loreService.getLoreNodeDeletionImpact(this.folderId).subscribe({
next: impact => {
const parts: string[] = [];
if (impact.folders > 0) parts.push(`${impact.folders} sous-dossier${impact.folders > 1 ? 's' : ''}`);
if (impact.pages > 0) parts.push(`${impact.pages} page${impact.pages > 1 ? 's' : ''}`);
const details: string[] = [];
if (parts.length) {
details.push(`Cette action supprimera aussi : ${parts.join(', ')}.`);
}
details.push('Cette action est irréversible.');
this.confirmDialog.confirm({
title: 'Supprimer le dossier',
message: `Supprimer le dossier "${node.name}" ?`,
details,
confirmLabel: 'Supprimer',
variant: 'danger'
}).then(ok => {
if (!ok) return;
this.loreService.deleteLoreNode(this.folderId).subscribe({
next: () => {
// Remonte au dossier parent si présent, sinon au Lore.
if (node.parentId) {
this.router.navigate(['/lore', this.loreId, 'folders', node.parentId]);
} else {
this.router.navigate(['/lore', this.loreId]);
}
},
error: () => console.error('Erreur lors de la suppression du dossier')
});
});
},
error: () => console.error('Impossible de récupérer les dépendances du dossier')
});
}
ngOnDestroy(): void {
// Volontairement vide : la sidebar reste prise en charge par le composant
// suivant (autre sous-route ou le composant detail parent) qui appellera
// show(). Eviter d'appeler hide() ici previent le clignotement / la
// disparition de la sidebar lors des navigations internes a la section.
}
}