Ajout de la partie "Système de jeu" avec toute la partie stockage de règles de notre jeu.
Ajout de possibilité de stocker des fiches de personnages associés à une campagne également (personnages joueurs pour le moment)
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
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, Dices, Plus, Trash2, ChevronDown, ChevronRight } from 'lucide-angular';
|
||||
import { GameSystemService } from '../../services/game-system.service';
|
||||
|
||||
/**
|
||||
* Éditeur plein écran d'un GameSystem. Rôle double création/édition :
|
||||
* - `/game-systems/create` → POST au submit
|
||||
* - `/game-systems/:id/edit` → PUT au submit
|
||||
*
|
||||
* UI par sections : au lieu d'un gros textarea de markdown, on présente
|
||||
* une liste de cartes repliables (une par section H2). Au load on parse
|
||||
* le markdown existant, au submit on le reconstruit. Les noms suggérés
|
||||
* (Combat, Classes…) guident sans imposer — c'est le même mapping que
|
||||
* le backend utilise pour filtrer les sections à injecter dans l'IA.
|
||||
*/
|
||||
interface RuleSection {
|
||||
title: string;
|
||||
content: string;
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sections suggérées : mécaniques de jeu uniquement. Lore/factions/PNJ
|
||||
* appartiennent au module Lore de LoreMind (décrit l'univers de l'utilisateur),
|
||||
* pas au GameSystem (décrit les règles). Séparation volontaire.
|
||||
*/
|
||||
const SUGGESTED_SECTIONS = [
|
||||
'Combat', 'Classes', 'Stats', 'Magie', 'Monstres', 'Progression'
|
||||
];
|
||||
|
||||
@Component({
|
||||
selector: 'app-game-system-edit',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, LucideAngularModule],
|
||||
templateUrl: './game-system-edit.component.html',
|
||||
styleUrls: ['./game-system-edit.component.scss']
|
||||
})
|
||||
export class GameSystemEditComponent implements OnInit {
|
||||
readonly Save = Save;
|
||||
readonly ArrowLeft = ArrowLeft;
|
||||
readonly Dices = Dices;
|
||||
readonly Plus = Plus;
|
||||
readonly Trash2 = Trash2;
|
||||
readonly ChevronDown = ChevronDown;
|
||||
readonly ChevronRight = ChevronRight;
|
||||
|
||||
id: string | null = null;
|
||||
|
||||
name = '';
|
||||
description = '';
|
||||
author = '';
|
||||
sections: RuleSection[] = [];
|
||||
|
||||
readonly suggestedSections = SUGGESTED_SECTIONS;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private service: GameSystemService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.id = this.route.snapshot.paramMap.get('id');
|
||||
if (this.id) {
|
||||
this.service.getById(this.id).subscribe({
|
||||
next: (gs) => {
|
||||
this.name = gs.name;
|
||||
this.description = gs.description ?? '';
|
||||
this.author = gs.author ?? '';
|
||||
this.sections = this.parseMarkdown(gs.rulesMarkdown ?? '');
|
||||
},
|
||||
error: () => this.back()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Noms de section déjà utilisés — pour désactiver les suggestions en doublon. */
|
||||
isSectionUsed(title: string): boolean {
|
||||
const lower = title.toLowerCase();
|
||||
return this.sections.some(s => s.title.toLowerCase() === lower);
|
||||
}
|
||||
|
||||
/** Ajoute une section avec un nom suggéré. */
|
||||
addSuggested(title: string): void {
|
||||
if (this.isSectionUsed(title)) return;
|
||||
this.sections.push({ title, content: '', collapsed: false });
|
||||
}
|
||||
|
||||
/** Ajoute une section vierge (titre à remplir). */
|
||||
addBlank(): void {
|
||||
this.sections.push({ title: '', content: '', collapsed: false });
|
||||
}
|
||||
|
||||
removeSection(index: number): void {
|
||||
this.sections.splice(index, 1);
|
||||
}
|
||||
|
||||
toggleCollapse(section: RuleSection): void {
|
||||
section.collapsed = !section.collapsed;
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
if (!this.name.trim()) return;
|
||||
const payload = {
|
||||
name: this.name.trim(),
|
||||
description: this.description.trim() || null,
|
||||
author: this.author.trim() || null,
|
||||
rulesMarkdown: this.serializeMarkdown(),
|
||||
isPublic: false
|
||||
};
|
||||
const req = this.id
|
||||
? this.service.update(this.id, payload)
|
||||
: this.service.create(payload);
|
||||
req.subscribe({
|
||||
next: () => this.back(),
|
||||
error: () => console.error('Erreur sauvegarde GameSystem')
|
||||
});
|
||||
}
|
||||
|
||||
back(): void {
|
||||
this.router.navigate(['/game-systems']);
|
||||
}
|
||||
|
||||
// --- Parse / Serialize markdown ------------------------------------------
|
||||
|
||||
/**
|
||||
* Découpe un markdown par titres H2 en sections. Doit rester cohérent avec
|
||||
* le parser Java côté backend (regex `^##\s+…$`). Le texte avant le premier
|
||||
* H2 est volontairement perdu — le backend l'ignore aussi pour l'injection IA.
|
||||
*/
|
||||
private parseMarkdown(markdown: string): RuleSection[] {
|
||||
if (!markdown) return [];
|
||||
const sections: RuleSection[] = [];
|
||||
const lines = markdown.split('\n');
|
||||
let current: RuleSection | null = null;
|
||||
const buffer: string[] = [];
|
||||
|
||||
const flush = () => {
|
||||
if (current) {
|
||||
current.content = buffer.join('\n').trim();
|
||||
sections.push(current);
|
||||
}
|
||||
buffer.length = 0;
|
||||
};
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^##\s+(.+?)\s*$/);
|
||||
if (match) {
|
||||
flush();
|
||||
current = { title: match[1].trim(), content: '', collapsed: false };
|
||||
} else if (current) {
|
||||
buffer.push(line);
|
||||
}
|
||||
}
|
||||
flush();
|
||||
return sections;
|
||||
}
|
||||
|
||||
/** Reconstruit le markdown à partir des sections (ignore les sections vides). */
|
||||
private serializeMarkdown(): string | null {
|
||||
const valid = this.sections.filter(s => s.title.trim() || s.content.trim());
|
||||
if (valid.length === 0) return null;
|
||||
return valid
|
||||
.map(s => `## ${s.title.trim() || 'Sans titre'}\n${s.content.trim()}`)
|
||||
.join('\n\n');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user