Refonte du système JDR + système de personnage joueurs / non joueurs :
Some checks failed
E2E Tests / e2e (push) Failing after 21s

- Système de templating dans le game system : en effet, les templates sont liés au game system car les fiches personnages ne sont pas forcément les même selon les jeux (perso Dnd possède + de compétences que Nimble par exemple)
- changement des fiches personnages pour adapter le templating au niveau des campagnes et remplir des pages de perso
This commit is contained in:
2026-04-30 10:42:09 +02:00
parent efaf5a3794
commit 52e389db24
67 changed files with 1610 additions and 255 deletions

View File

@@ -90,6 +90,32 @@
</div>
</div>
<!-- Templates de fiches PJ/PNJ -->
<div class="templates-area">
<h2 class="sections-title">Fiches de personnages</h2>
<p class="sections-hint">
Definissez la structure des fiches PJ et PNJ pour ce systeme. Les champs
universels (nom, portrait, header) sont automatiques — ne rajoutez ici
que les champs specifiques au systeme (Histoire, PV, Stats…).
</p>
<app-template-fields-editor
label="Champs de la fiche PJ"
hint="Affiches lors de la creation/edition d'un personnage joueur."
[fields]="characterTemplate"
[suggestions]="characterFieldSuggestions"
(fieldsChange)="characterTemplate = $event">
</app-template-fields-editor>
<app-template-fields-editor
label="Champs de la fiche PNJ"
hint="Affiches lors de la creation/edition d'un personnage non-joueur."
[fields]="npcTemplate"
[suggestions]="npcFieldSuggestions"
(fieldsChange)="npcTemplate = $event">
</app-template-fields-editor>
</div>
<div class="actions">
<button type="button" class="btn-primary" [disabled]="!name.trim()" (click)="submit()">
<lucide-icon [img]="Save" [size]="16"></lucide-icon>

View File

@@ -5,6 +5,13 @@
margin: 0 auto;
}
.templates-area {
margin-top: 24px;
display: flex;
flex-direction: column;
gap: 16px;
}
.gse-header {
margin-bottom: 2rem;

View File

@@ -4,6 +4,8 @@ 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';
import { TemplateField } from '../../services/template-field.model';
import { TemplateFieldsEditorComponent } from '../../shared/template-fields-editor/template-fields-editor.component';
/**
* Éditeur plein écran d'un GameSystem. Rôle double création/édition :
@@ -31,10 +33,16 @@ const SUGGESTED_SECTIONS = [
'Combat', 'Classes', 'Stats', 'Magie', 'Monstres', 'Progression'
];
/** Suggestions de champs pour la fiche PJ — generiques (extension par template). */
const CHARACTER_FIELD_SUGGESTIONS = ['Histoire', 'Personnalite', 'Apparence', 'Notes'];
/** Suggestions de champs pour la fiche PNJ — focus sur les besoins MJ. */
const NPC_FIELD_SUGGESTIONS = ['Motivation', 'Apparence', 'Faction', 'Notes MJ'];
@Component({
selector: 'app-game-system-edit',
standalone: true,
imports: [CommonModule, FormsModule, LucideAngularModule],
imports: [CommonModule, FormsModule, LucideAngularModule, TemplateFieldsEditorComponent],
templateUrl: './game-system-edit.component.html',
styleUrls: ['./game-system-edit.component.scss']
})
@@ -53,8 +61,12 @@ export class GameSystemEditComponent implements OnInit {
description = '';
author = '';
sections: RuleSection[] = [];
characterTemplate: TemplateField[] = [];
npcTemplate: TemplateField[] = [];
readonly suggestedSections = SUGGESTED_SECTIONS;
readonly characterFieldSuggestions = CHARACTER_FIELD_SUGGESTIONS;
readonly npcFieldSuggestions = NPC_FIELD_SUGGESTIONS;
constructor(
private route: ActivatedRoute,
@@ -71,6 +83,8 @@ export class GameSystemEditComponent implements OnInit {
this.description = gs.description ?? '';
this.author = gs.author ?? '';
this.sections = this.parseMarkdown(gs.rulesMarkdown ?? '');
this.characterTemplate = gs.characterTemplate ? [...gs.characterTemplate] : [];
this.npcTemplate = gs.npcTemplate ? [...gs.npcTemplate] : [];
},
error: () => this.back()
});
@@ -104,11 +118,17 @@ export class GameSystemEditComponent implements OnInit {
submit(): void {
if (!this.name.trim()) return;
if (this.hasInvalidTemplateFields()) {
console.warn('Champs templates invalides (noms vides ou doublons) — sauvegarde bloquee.');
return;
}
const payload = {
name: this.name.trim(),
description: this.description.trim() || null,
author: this.author.trim() || null,
rulesMarkdown: this.serializeMarkdown(),
characterTemplate: this.characterTemplate,
npcTemplate: this.npcTemplate,
isPublic: false
};
const req = this.id
@@ -124,6 +144,22 @@ export class GameSystemEditComponent implements OnInit {
this.router.navigate(['/game-systems']);
}
/** Validation cote front : nom vide ou doublons (case-insensitive). */
private hasInvalidTemplateFields(): boolean {
return this.hasInvalidList(this.characterTemplate) || this.hasInvalidList(this.npcTemplate);
}
private hasInvalidList(fields: TemplateField[]): boolean {
const seen = new Set<string>();
for (const f of fields) {
const name = f.name?.trim().toLowerCase();
if (!name) return true;
if (seen.has(name)) return true;
seen.add(name);
}
return false;
}
// --- Parse / Serialize markdown ------------------------------------------
/**