Ajout de tableaux dans la partie templates / pages de lore : possibilité d'ajouter un tableau multiligne (par exemple pour faire des tableaux d'objets dans les boutiques) ; tableau type liste clé / valeur (pour des statistiques et ce genre de chose).
All checks were successful
All checks were successful
Ajout de la possibilité de lié un PNJ à une page de lore Ajout d'un graphe de liaison entre lore / PNJs Passage en v.0.12.3-beta
This commit is contained in:
@@ -87,6 +87,20 @@
|
||||
</app-dynamic-fields-form>
|
||||
</div>
|
||||
|
||||
<!-- Pages de Lore liées (uniquement si la campagne est rattachée à un Lore) -->
|
||||
@if (loreId) {
|
||||
<div class="field">
|
||||
<label>Pages de Lore liées</label>
|
||||
<app-lore-link-picker
|
||||
[value]="relatedPageIds"
|
||||
[availablePages]="lorePages"
|
||||
[loreId]="loreId"
|
||||
(valueChange)="relatedPageIds = $event">
|
||||
</app-lore-link-picker>
|
||||
<p class="hint">Reliez ce PNJ aux pages de l'univers (sa ville, sa faction, sa région…). Ces liens apparaissent dans le graphe du Lore.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="btn-primary" [disabled]="!name.trim()" (click)="submit()">
|
||||
<lucide-icon [img]="Save" [size]="16"></lucide-icon>
|
||||
|
||||
@@ -6,11 +6,14 @@ import { LucideAngularModule, Save, ArrowLeft, Drama, Trash2, Sparkles } from 'l
|
||||
import { NpcService } from '../../../services/npc.service';
|
||||
import { CampaignService } from '../../../services/campaign.service';
|
||||
import { GameSystemService } from '../../../services/game-system.service';
|
||||
import { PageService } from '../../../services/page.service';
|
||||
import { CampaignSidebarService } from '../../../services/campaign-sidebar.service';
|
||||
import { TemplateField } from '../../../services/template.model';
|
||||
import { Page } from '../../../services/page.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 { LoreLinkPickerComponent } from '../../../shared/lore-link-picker/lore-link-picker.component';
|
||||
import { ConfirmDialogService } from '../../../shared/confirm-dialog/confirm-dialog.service';
|
||||
|
||||
/**
|
||||
@@ -21,7 +24,7 @@ import { ConfirmDialogService } from '../../../shared/confirm-dialog/confirm-dia
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-npc-edit',
|
||||
imports: [FormsModule, LucideAngularModule, AiChatDrawerComponent, DynamicFieldsFormComponent, SingleImagePickerComponent],
|
||||
imports: [FormsModule, LucideAngularModule, AiChatDrawerComponent, DynamicFieldsFormComponent, SingleImagePickerComponent, LoreLinkPickerComponent],
|
||||
templateUrl: './npc-edit.component.html',
|
||||
styleUrls: ['./npc-edit.component.scss']
|
||||
})
|
||||
@@ -56,12 +59,20 @@ export class NpcEditComponent implements OnInit {
|
||||
templateFields: TemplateField[] = [];
|
||||
private order = 0;
|
||||
|
||||
/** Lore lié à la campagne (null = pas de lore → section liens masquée). */
|
||||
loreId: string | null = null;
|
||||
/** Pages du lore lié — référentiel du picker. */
|
||||
lorePages: Page[] = [];
|
||||
/** IDs des pages de lore référencées par ce PNJ. */
|
||||
relatedPageIds: string[] = [];
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private service: NpcService,
|
||||
private campaignService: CampaignService,
|
||||
private gameSystemService: GameSystemService,
|
||||
private pageService: PageService,
|
||||
private campaignSidebar: CampaignSidebarService,
|
||||
private confirmDialog: ConfirmDialogService
|
||||
) {}
|
||||
@@ -87,6 +98,7 @@ export class NpcEditComponent implements OnInit {
|
||||
this.values = n.values ?? {};
|
||||
this.imageValues = n.imageValues ?? {};
|
||||
this.keyValueValues = n.keyValueValues ?? {};
|
||||
this.relatedPageIds = [...(n.relatedPageIds ?? [])];
|
||||
this.order = n.order ?? 0;
|
||||
},
|
||||
error: () => this.back()
|
||||
@@ -108,6 +120,14 @@ export class NpcEditComponent implements OnInit {
|
||||
private loadTemplateForCampaign(campaignId: string): void {
|
||||
this.campaignService.getCampaignById(campaignId).subscribe({
|
||||
next: (campaign) => {
|
||||
// Lore lié → charge ses pages pour le picker de références.
|
||||
if (campaign.loreId) {
|
||||
this.loreId = campaign.loreId;
|
||||
this.pageService.getByLoreId(campaign.loreId).subscribe({
|
||||
next: (pages) => { this.lorePages = pages; },
|
||||
error: () => { this.lorePages = []; }
|
||||
});
|
||||
}
|
||||
if (!campaign.gameSystemId) {
|
||||
this.templateFields = [];
|
||||
return;
|
||||
@@ -132,7 +152,8 @@ export class NpcEditComponent implements OnInit {
|
||||
values: this.values,
|
||||
imageValues: this.imageValues,
|
||||
keyValueValues: this.keyValueValues,
|
||||
campaignId: this.campaignId
|
||||
campaignId: this.campaignId,
|
||||
relatedPageIds: this.relatedPageIds
|
||||
};
|
||||
const isCreation = !this.npcId;
|
||||
const req = this.npcId
|
||||
|
||||
@@ -18,6 +18,23 @@
|
||||
</div>
|
||||
|
||||
<app-persona-view [persona]="npc" [templateFields]="templateFields"></app-persona-view>
|
||||
|
||||
<!-- Pages de Lore liées à ce PNJ (sa ville, sa faction…) -->
|
||||
@if (loreId && (npc?.relatedPageIds?.length ?? 0) > 0) {
|
||||
<section class="nv-lore-links">
|
||||
<h2 class="nv-lore-links-title">
|
||||
<lucide-icon [img]="Link2" [size]="14"></lucide-icon>
|
||||
Pages de Lore liées
|
||||
</h2>
|
||||
<div class="nv-lore-chips">
|
||||
@for (pageId of npc!.relatedPageIds; track pageId) {
|
||||
<a class="nv-lore-chip" [routerLink]="['/lore', loreId, 'pages', pageId]">
|
||||
{{ titleOfPage(pageId) }}
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (npcId && campaignId) {
|
||||
|
||||
@@ -49,3 +49,46 @@
|
||||
border-color: rgba(168, 85, 247, 0.5);
|
||||
color: #d8b4fe;
|
||||
}
|
||||
|
||||
// Pages de Lore liées au PNJ — chips cliquables sous la fiche.
|
||||
.nv-lore-links {
|
||||
max-width: 1100px;
|
||||
margin: 24px auto 0;
|
||||
padding: 0 32px;
|
||||
|
||||
.nv-lore-links-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
color: #a5b4fc;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin: 0 0 0.6rem;
|
||||
}
|
||||
|
||||
.nv-lore-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.nv-lore-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.3rem 0.7rem;
|
||||
background: #1a1a2e;
|
||||
border: 1px solid #2a2a3d;
|
||||
border-radius: 999px;
|
||||
color: #d1d5db;
|
||||
font-size: 0.82rem;
|
||||
text-decoration: none;
|
||||
transition: border-color 0.15s, color 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: #6c63ff;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { LucideAngularModule, ArrowLeft, Edit3, Sparkles } from 'lucide-angular';
|
||||
import { LucideAngularModule, ArrowLeft, Edit3, Sparkles, Link2 } from 'lucide-angular';
|
||||
import { NpcService } from '../../../services/npc.service';
|
||||
import { CampaignService } from '../../../services/campaign.service';
|
||||
import { GameSystemService } from '../../../services/game-system.service';
|
||||
import { PageService } from '../../../services/page.service';
|
||||
import { CampaignSidebarService } from '../../../services/campaign-sidebar.service';
|
||||
import { TemplateField } from '../../../services/template.model';
|
||||
import { Npc } from '../../../services/npc.model';
|
||||
import { Page } from '../../../services/page.model';
|
||||
import { PersonaViewComponent } from '../../../shared/persona-view/persona-view.component';
|
||||
import { AiChatDrawerComponent } from '../../../shared/ai-chat-drawer/ai-chat-drawer.component';
|
||||
|
||||
@@ -18,7 +20,7 @@ import { AiChatDrawerComponent } from '../../../shared/ai-chat-drawer/ai-chat-dr
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-npc-view',
|
||||
imports: [LucideAngularModule, PersonaViewComponent, AiChatDrawerComponent],
|
||||
imports: [LucideAngularModule, RouterLink, PersonaViewComponent, AiChatDrawerComponent],
|
||||
templateUrl: './npc-view.component.html',
|
||||
styleUrls: ['./npc-view.component.scss']
|
||||
})
|
||||
@@ -26,12 +28,17 @@ export class NpcViewComponent implements OnInit, OnDestroy {
|
||||
readonly ArrowLeft = ArrowLeft;
|
||||
readonly Edit3 = Edit3;
|
||||
readonly Sparkles = Sparkles;
|
||||
readonly Link2 = Link2;
|
||||
|
||||
campaignId: string | null = null;
|
||||
npcId: string | null = null;
|
||||
|
||||
npc: Npc | null = null;
|
||||
templateFields: TemplateField[] = [];
|
||||
/** Lore lié à la campagne (résolution des chips de pages liées). */
|
||||
loreId: string | null = null;
|
||||
/** Pages du lore lié, indexées pour résoudre les titres des chips. */
|
||||
private lorePagesById = new Map<string, Page>();
|
||||
|
||||
chatOpen = false;
|
||||
toggleChat(): void { this.chatOpen = !this.chatOpen; }
|
||||
@@ -44,6 +51,7 @@ export class NpcViewComponent implements OnInit, OnDestroy {
|
||||
private service: NpcService,
|
||||
private campaignService: CampaignService,
|
||||
private gameSystemService: GameSystemService,
|
||||
private pageService: PageService,
|
||||
private campaignSidebar: CampaignSidebarService
|
||||
) {}
|
||||
|
||||
@@ -75,6 +83,13 @@ export class NpcViewComponent implements OnInit, OnDestroy {
|
||||
this.templateFields = gs.npcTemplate ?? [];
|
||||
});
|
||||
}
|
||||
// Lore lié → référentiel de pages pour résoudre les chips de liens.
|
||||
if (camp.loreId) {
|
||||
this.loreId = camp.loreId;
|
||||
this.pageService.getByLoreId(camp.loreId).subscribe(pages => {
|
||||
this.lorePagesById = new Map(pages.map(p => [p.id!, p]));
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (newCampaignId) {
|
||||
this.campaignId = newCampaignId;
|
||||
@@ -86,6 +101,11 @@ export class NpcViewComponent implements OnInit, OnDestroy {
|
||||
this.paramsSub?.unsubscribe();
|
||||
}
|
||||
|
||||
/** Titre d'une page de lore liée (pour les chips). */
|
||||
titleOfPage(pageId: string): string {
|
||||
return this.lorePagesById.get(pageId)?.title ?? '(page supprimée)';
|
||||
}
|
||||
|
||||
edit(): void {
|
||||
if (this.campaignId && this.npcId) {
|
||||
this.router.navigate(['/campaigns', this.campaignId, 'npcs', this.npcId, 'edit']);
|
||||
|
||||
Reference in New Issue
Block a user