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
167 lines
5.9 KiB
TypeScript
167 lines
5.9 KiB
TypeScript
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, User, Trash2, Sparkles } from 'lucide-angular';
|
|
import { CharacterService } from '../../../services/character.service';
|
|
import { CampaignService } from '../../../services/campaign.service';
|
|
import { GameSystemService } from '../../../services/game-system.service';
|
|
import { CampaignSidebarService } from '../../../services/campaign-sidebar.service';
|
|
import { TemplateField } from '../../../services/template.model';
|
|
import { AiChatDrawerComponent } from '../../../shared/ai-chat-drawer/ai-chat-drawer.component';
|
|
import { DynamicFieldsFormComponent } from '../../../shared/dynamic-fields-form/dynamic-fields-form.component';
|
|
import { SingleImagePickerComponent } from '../../../shared/single-image-picker/single-image-picker.component';
|
|
import { ConfirmDialogService } from '../../../shared/confirm-dialog/confirm-dialog.service';
|
|
|
|
/**
|
|
* Editeur plein ecran d'une fiche de personnage (PJ).
|
|
* Refonte 2026-04-30 : remplace le markdown libre par un formulaire dynamique
|
|
* pilote par le characterTemplate du GameSystem associe a la campagne.
|
|
*
|
|
* Comportements :
|
|
* - Si la campagne n'a pas de GameSystem ou si son template est vide, affiche
|
|
* uniquement les champs universels (nom, portrait, header).
|
|
* - Le picker d'images dedie portrait/header est hors scope MVP — pour l'instant
|
|
* saisie manuelle d'IDs d'images.
|
|
*/
|
|
@Component({
|
|
selector: 'app-character-edit',
|
|
standalone: true,
|
|
imports: [CommonModule, FormsModule, LucideAngularModule, AiChatDrawerComponent, DynamicFieldsFormComponent, SingleImagePickerComponent],
|
|
templateUrl: './character-edit.component.html',
|
|
styleUrls: ['./character-edit.component.scss']
|
|
})
|
|
export class CharacterEditComponent implements OnInit {
|
|
readonly Save = Save;
|
|
readonly ArrowLeft = ArrowLeft;
|
|
readonly User = User;
|
|
readonly Trash2 = Trash2;
|
|
readonly Sparkles = Sparkles;
|
|
|
|
chatOpen = false;
|
|
readonly chatQuickSuggestions = [
|
|
'Propose une backstory coherente avec l\'univers',
|
|
'Suggere 3 objectifs personnels pour ce personnage',
|
|
'Aide-moi a equilibrer les stats de combat'
|
|
];
|
|
|
|
toggleChat(): void { this.chatOpen = !this.chatOpen; }
|
|
|
|
campaignId: string | null = null;
|
|
characterId: string | null = null;
|
|
|
|
name = '';
|
|
portraitImageId: string | null = null;
|
|
headerImageId: string | null = null;
|
|
values: Record<string, string> = {};
|
|
imageValues: Record<string, string[]> = {};
|
|
keyValueValues: Record<string, Record<string, string>> = {};
|
|
templateFields: TemplateField[] = [];
|
|
private order = 0;
|
|
|
|
constructor(
|
|
private route: ActivatedRoute,
|
|
private router: Router,
|
|
private service: CharacterService,
|
|
private campaignService: CampaignService,
|
|
private gameSystemService: GameSystemService,
|
|
private campaignSidebar: CampaignSidebarService,
|
|
private confirmDialog: ConfirmDialogService
|
|
) {}
|
|
|
|
ngOnInit(): void {
|
|
const params = this.route.snapshot.paramMap;
|
|
this.campaignId = params.get('campaignId');
|
|
this.characterId = params.get('characterId');
|
|
|
|
if (this.campaignId) {
|
|
this.loadTemplateForCampaign(this.campaignId);
|
|
this.campaignSidebar.show(this.campaignId);
|
|
}
|
|
|
|
if (this.characterId) {
|
|
this.service.getById(this.characterId).subscribe({
|
|
next: (c) => {
|
|
this.name = c.name;
|
|
this.portraitImageId = c.portraitImageId ?? null;
|
|
this.headerImageId = c.headerImageId ?? null;
|
|
this.values = c.values ?? {};
|
|
this.imageValues = c.imageValues ?? {};
|
|
this.keyValueValues = c.keyValueValues ?? {};
|
|
this.order = c.order ?? 0;
|
|
},
|
|
error: () => this.back()
|
|
});
|
|
}
|
|
}
|
|
|
|
private loadTemplateForCampaign(campaignId: string): void {
|
|
this.campaignService.getCampaignById(campaignId).subscribe({
|
|
next: (campaign) => {
|
|
if (!campaign.gameSystemId) {
|
|
this.templateFields = [];
|
|
return;
|
|
}
|
|
this.gameSystemService.getById(campaign.gameSystemId).subscribe({
|
|
next: (gs) => { this.templateFields = gs.characterTemplate ?? []; },
|
|
error: () => { this.templateFields = []; }
|
|
});
|
|
},
|
|
error: () => { this.templateFields = []; }
|
|
});
|
|
}
|
|
|
|
|
|
submit(): void {
|
|
if (!this.name.trim() || !this.campaignId) return;
|
|
const payload = {
|
|
name: this.name.trim(),
|
|
portraitImageId: this.portraitImageId,
|
|
headerImageId: this.headerImageId,
|
|
values: this.values,
|
|
imageValues: this.imageValues,
|
|
keyValueValues: this.keyValueValues,
|
|
campaignId: this.campaignId
|
|
};
|
|
const isCreation = !this.characterId;
|
|
const req = this.characterId
|
|
? this.service.update(this.characterId, { ...payload, id: this.characterId, order: this.order })
|
|
: this.service.create(payload);
|
|
req.subscribe({
|
|
next: (saved) => {
|
|
if (isCreation && saved.id) {
|
|
this.router.navigate(['/campaigns', this.campaignId, 'characters', saved.id]);
|
|
} else {
|
|
this.back();
|
|
}
|
|
},
|
|
error: () => console.error('Erreur sauvegarde Character')
|
|
});
|
|
}
|
|
|
|
deleteCharacter(): void {
|
|
if (!this.characterId) return;
|
|
this.confirmDialog.confirm({
|
|
title: 'Supprimer la fiche ?',
|
|
message: `Supprimer la fiche de "${this.name}" ?`,
|
|
details: ['Cette action est irreversible.'],
|
|
confirmLabel: 'Supprimer',
|
|
variant: 'danger'
|
|
}).then(ok => {
|
|
if (!ok || !this.characterId) return;
|
|
this.service.delete(this.characterId).subscribe({
|
|
next: () => this.back(),
|
|
error: () => console.error('Erreur suppression Character')
|
|
});
|
|
});
|
|
}
|
|
|
|
back(): void {
|
|
if (this.campaignId) {
|
|
this.router.navigate(['/campaigns', this.campaignId]);
|
|
} else {
|
|
this.router.navigate(['/campaigns']);
|
|
}
|
|
}
|
|
}
|