Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e3c8232e38 | |||
| a4df9fc759 |
@@ -40,7 +40,7 @@ from app.infrastructure.onemin_adapter import OneMinAiLLMProvider
|
|||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="LoreMind Brain",
|
title="LoreMind Brain",
|
||||||
description="Backend IA pour la génération de contenu narratif.",
|
description="Backend IA pour la génération de contenu narratif.",
|
||||||
version="0.6.0",
|
version="0.6.1",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<groupId>com.loremind</groupId>
|
<groupId>com.loremind</groupId>
|
||||||
<artifactId>loremind-core</artifactId>
|
<artifactId>loremind-core</artifactId>
|
||||||
<version>0.6.0</version>
|
<version>0.6.1</version>
|
||||||
<name>LoreMind Core</name>
|
<name>LoreMind Core</name>
|
||||||
<description>Backend Core - Architecture Hexagonale</description>
|
<description>Backend Core - Architecture Hexagonale</description>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "loremind-web",
|
"name": "loremind-web",
|
||||||
"version": "0.6.0",
|
"version": "0.6.1",
|
||||||
"description": "LoreMind Frontend - Angular",
|
"description": "LoreMind Frontend - Angular",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { forkJoin } from 'rxjs';
|
import { forkJoin } from 'rxjs';
|
||||||
import { LucideAngularModule, BookOpen } from 'lucide-angular';
|
import { LucideAngularModule, BookOpen } from 'lucide-angular';
|
||||||
import { CampaignService } from '../../services/campaign.service';
|
import { CampaignService } from '../../services/campaign.service';
|
||||||
|
import { CharacterService } from '../../services/character.service';
|
||||||
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
||||||
import { Campaign } from '../../services/campaign.model';
|
import { Campaign } from '../../services/campaign.model';
|
||||||
import { loadCampaignTreeData, buildCampaignTree } from '../campaign-tree.helper';
|
import { loadCampaignTreeData, buildCampaignTree } from '../campaign-tree.helper';
|
||||||
@@ -33,6 +34,7 @@ export class ArcCreateComponent implements OnInit, OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private campaignService: CampaignService,
|
private campaignService: CampaignService,
|
||||||
|
private characterService: CharacterService,
|
||||||
private layoutService: LayoutService
|
private layoutService: LayoutService
|
||||||
) {
|
) {
|
||||||
this.form = this.fb.group({
|
this.form = this.fb.group({
|
||||||
@@ -50,7 +52,7 @@ export class ArcCreateComponent implements OnInit, OnDestroy {
|
|||||||
forkJoin({
|
forkJoin({
|
||||||
campaign: this.campaignService.getCampaignById(this.campaignId),
|
campaign: this.campaignService.getCampaignById(this.campaignId),
|
||||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId)
|
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService)
|
||||||
}).subscribe(({ campaign, allCampaigns, treeData }) => {
|
}).subscribe(({ campaign, allCampaigns, treeData }) => {
|
||||||
this.existingArcCount = treeData.arcs.length;
|
this.existingArcCount = treeData.arcs.length;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { forkJoin, of } from 'rxjs';
|
|||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { LucideAngularModule, Trash2, Sparkles } from 'lucide-angular';
|
import { LucideAngularModule, Trash2, Sparkles } from 'lucide-angular';
|
||||||
import { CampaignService } from '../../services/campaign.service';
|
import { CampaignService } from '../../services/campaign.service';
|
||||||
|
import { CharacterService } from '../../services/character.service';
|
||||||
import { PageService } from '../../services/page.service';
|
import { PageService } from '../../services/page.service';
|
||||||
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
||||||
import { PageTitleService } from '../../services/page-title.service';
|
import { PageTitleService } from '../../services/page-title.service';
|
||||||
@@ -68,6 +69,7 @@ export class ArcEditComponent implements OnInit, OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private campaignService: CampaignService,
|
private campaignService: CampaignService,
|
||||||
|
private characterService: CharacterService,
|
||||||
private pageService: PageService,
|
private pageService: PageService,
|
||||||
private layoutService: LayoutService,
|
private layoutService: LayoutService,
|
||||||
private pageTitleService: PageTitleService
|
private pageTitleService: PageTitleService
|
||||||
@@ -105,7 +107,7 @@ export class ArcEditComponent implements OnInit, OnDestroy {
|
|||||||
campaign: this.campaignService.getCampaignById(this.campaignId),
|
campaign: this.campaignService.getCampaignById(this.campaignId),
|
||||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||||
arc: this.campaignService.getArcById(this.arcId),
|
arc: this.campaignService.getArcById(this.arcId),
|
||||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId)
|
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService)
|
||||||
}).pipe(
|
}).pipe(
|
||||||
switchMap(data => {
|
switchMap(data => {
|
||||||
const lid = data.campaign.loreId ?? null;
|
const lid = data.campaign.loreId ?? null;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { forkJoin, of } from 'rxjs';
|
|||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { LucideAngularModule, Pencil, Trash2 } from 'lucide-angular';
|
import { LucideAngularModule, Pencil, Trash2 } from 'lucide-angular';
|
||||||
import { CampaignService } from '../../services/campaign.service';
|
import { CampaignService } from '../../services/campaign.service';
|
||||||
|
import { CharacterService } from '../../services/character.service';
|
||||||
import { PageService } from '../../services/page.service';
|
import { PageService } from '../../services/page.service';
|
||||||
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
||||||
import { PageTitleService } from '../../services/page-title.service';
|
import { PageTitleService } from '../../services/page-title.service';
|
||||||
@@ -42,6 +43,7 @@ export class ArcViewComponent implements OnInit, OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private campaignService: CampaignService,
|
private campaignService: CampaignService,
|
||||||
|
private characterService: CharacterService,
|
||||||
private pageService: PageService,
|
private pageService: PageService,
|
||||||
private layoutService: LayoutService,
|
private layoutService: LayoutService,
|
||||||
private pageTitleService: PageTitleService
|
private pageTitleService: PageTitleService
|
||||||
@@ -64,7 +66,7 @@ export class ArcViewComponent implements OnInit, OnDestroy {
|
|||||||
campaign: this.campaignService.getCampaignById(this.campaignId),
|
campaign: this.campaignService.getCampaignById(this.campaignId),
|
||||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||||
arc: this.campaignService.getArcById(this.arcId),
|
arc: this.campaignService.getArcById(this.arcId),
|
||||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId)
|
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService)
|
||||||
}).pipe(
|
}).pipe(
|
||||||
switchMap(data => {
|
switchMap(data => {
|
||||||
const lid = data.campaign.loreId ?? null;
|
const lid = data.campaign.loreId ?? null;
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="detail-section characters-section">
|
<section class="detail-section characters-section" *ngIf="!editing">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>Personnages joueurs</h2>
|
<h2>Personnages joueurs</h2>
|
||||||
<button class="btn-add" (click)="createCharacter()">
|
<button class="btn-add" (click)="createCharacter()">
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="detail-section arcs-section">
|
<section class="detail-section arcs-section" *ngIf="!editing">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>Arcs narratifs</h2>
|
<h2>Arcs narratifs</h2>
|
||||||
<button class="btn-add" (click)="createArc()">
|
<button class="btn-add" (click)="createArc()">
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ export class CampaignDetailComponent implements OnInit, OnDestroy {
|
|||||||
switchMap(id => forkJoin({
|
switchMap(id => forkJoin({
|
||||||
campaign: this.campaignService.getCampaignById(id),
|
campaign: this.campaignService.getCampaignById(id),
|
||||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||||
treeData: loadCampaignTreeData(this.campaignService, id).pipe(
|
treeData: loadCampaignTreeData(this.campaignService, id, this.characterService).pipe(
|
||||||
catchError(() => of({ arcs: [], chaptersByArc: {}, scenesByChapter: {} } as CampaignTreeData))
|
catchError(() => of({ arcs: [], chaptersByArc: {}, scenesByChapter: {}, characters: [] } as CampaignTreeData))
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
).subscribe(({ campaign, allCampaigns, treeData }) => {
|
).subscribe(({ campaign, allCampaigns, treeData }) => {
|
||||||
@@ -111,8 +111,8 @@ export class CampaignDetailComponent implements OnInit, OnDestroy {
|
|||||||
forkJoin({
|
forkJoin({
|
||||||
campaign: this.campaignService.getCampaignById(id),
|
campaign: this.campaignService.getCampaignById(id),
|
||||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||||
treeData: loadCampaignTreeData(this.campaignService, id).pipe(
|
treeData: loadCampaignTreeData(this.campaignService, id, this.characterService).pipe(
|
||||||
catchError(() => of({ arcs: [], chaptersByArc: {}, scenesByChapter: {} } as CampaignTreeData))
|
catchError(() => of({ arcs: [], chaptersByArc: {}, scenesByChapter: {}, characters: [] } as CampaignTreeData))
|
||||||
)
|
)
|
||||||
}).subscribe(({ campaign, allCampaigns, treeData }) => {
|
}).subscribe(({ campaign, allCampaigns, treeData }) => {
|
||||||
this.campaign = campaign;
|
this.campaign = campaign;
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Observable, forkJoin, of } from 'rxjs';
|
import { Observable, forkJoin, of } from 'rxjs';
|
||||||
import { switchMap, map } from 'rxjs/operators';
|
import { switchMap, map } from 'rxjs/operators';
|
||||||
import { CampaignService } from '../services/campaign.service';
|
import { CampaignService } from '../services/campaign.service';
|
||||||
|
import { CharacterService } from '../services/character.service';
|
||||||
import { TreeItem } from '../services/layout.service';
|
import { TreeItem } from '../services/layout.service';
|
||||||
import { Arc, Chapter, Scene } from '../services/campaign.model';
|
import { Arc, Chapter, Scene } from '../services/campaign.model';
|
||||||
|
import { Character } from '../services/character.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper — charge l'arborescence complète d'une campagne (arcs -> chapitres -> scènes)
|
* Helper — charge l'arborescence complète d'une campagne (arcs -> chapitres -> scènes)
|
||||||
@@ -16,16 +18,21 @@ export interface CampaignTreeData {
|
|||||||
arcs: Arc[];
|
arcs: Arc[];
|
||||||
chaptersByArc: Record<string, Chapter[]>;
|
chaptersByArc: Record<string, Chapter[]>;
|
||||||
scenesByChapter: Record<string, Scene[]>;
|
scenesByChapter: Record<string, Scene[]>;
|
||||||
|
characters: Character[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadCampaignTreeData(
|
export function loadCampaignTreeData(
|
||||||
service: CampaignService,
|
service: CampaignService,
|
||||||
campaignId: string
|
campaignId: string,
|
||||||
|
characterService: CharacterService
|
||||||
): Observable<CampaignTreeData> {
|
): Observable<CampaignTreeData> {
|
||||||
return service.getArcs(campaignId).pipe(
|
return forkJoin({
|
||||||
switchMap(arcs => {
|
arcs: service.getArcs(campaignId),
|
||||||
|
characters: characterService.getByCampaign(campaignId)
|
||||||
|
}).pipe(
|
||||||
|
switchMap(({ arcs, characters }) => {
|
||||||
if (arcs.length === 0) {
|
if (arcs.length === 0) {
|
||||||
return of({ arcs, chaptersByArc: {}, scenesByChapter: {} });
|
return of({ arcs, chaptersByArc: {}, scenesByChapter: {}, characters });
|
||||||
}
|
}
|
||||||
const chapterCalls = arcs.map(a =>
|
const chapterCalls = arcs.map(a =>
|
||||||
service.getChapters(a.id!).pipe(map(chapters => ({ arcId: a.id!, chapters })))
|
service.getChapters(a.id!).pipe(map(chapters => ({ arcId: a.id!, chapters })))
|
||||||
@@ -40,7 +47,7 @@ export function loadCampaignTreeData(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (allChapters.length === 0) {
|
if (allChapters.length === 0) {
|
||||||
return of({ arcs, chaptersByArc, scenesByChapter: {} });
|
return of({ arcs, chaptersByArc, scenesByChapter: {}, characters });
|
||||||
}
|
}
|
||||||
const sceneCalls = allChapters.map(c =>
|
const sceneCalls = allChapters.map(c =>
|
||||||
service.getScenes(c.id!).pipe(map(scenes => ({ chapterId: c.id!, scenes })))
|
service.getScenes(c.id!).pipe(map(scenes => ({ chapterId: c.id!, scenes })))
|
||||||
@@ -49,7 +56,7 @@ export function loadCampaignTreeData(
|
|||||||
map(sceneResults => {
|
map(sceneResults => {
|
||||||
const scenesByChapter: Record<string, Scene[]> = {};
|
const scenesByChapter: Record<string, Scene[]> = {};
|
||||||
sceneResults.forEach(r => { scenesByChapter[r.chapterId] = r.scenes; });
|
sceneResults.forEach(r => { scenesByChapter[r.chapterId] = r.scenes; });
|
||||||
return { arcs, chaptersByArc, scenesByChapter };
|
return { arcs, chaptersByArc, scenesByChapter, characters };
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@@ -67,9 +74,33 @@ export function buildCampaignTree(campaignId: string, data: CampaignTreeData): T
|
|||||||
// IDs préfixés par type pour éviter les collisions dans LayoutService.expanded
|
// IDs préfixés par type pour éviter les collisions dans LayoutService.expanded
|
||||||
// (chaque entité a sa propre séquence IDENTITY en base → arc.id=1 et chapter.id=1
|
// (chaque entité a sa propre séquence IDENTITY en base → arc.id=1 et chapter.id=1
|
||||||
// peuvent coexister et se marchaient sur les pieds dans le Set<string> global).
|
// peuvent coexister et se marchaient sur les pieds dans le Set<string> global).
|
||||||
|
const sortedCharacters = [...data.characters].sort(byName);
|
||||||
|
const characterItems: TreeItem[] = sortedCharacters.map(ch => ({
|
||||||
|
id: `character-${ch.id}`,
|
||||||
|
label: ch.name,
|
||||||
|
route: `/campaigns/${campaignId}/characters/${ch.id}/edit`
|
||||||
|
}));
|
||||||
|
|
||||||
|
const charactersNode: TreeItem = {
|
||||||
|
id: 'characters-root',
|
||||||
|
label: 'Personnages',
|
||||||
|
iconKey: 'users',
|
||||||
|
children: characterItems,
|
||||||
|
meta: characterItems.length ? String(characterItems.length) : undefined,
|
||||||
|
sectionHeaderBefore: 'Personnages',
|
||||||
|
// Note : si pas d'arcs, le filet au-dessus de "Personnages" est masqué par CSS
|
||||||
|
// (:first-child), ce qui est voulu — on ne veut pas de ligne seule en haut.
|
||||||
|
createActions: [{
|
||||||
|
id: 'new-character',
|
||||||
|
label: 'Nouveau PJ',
|
||||||
|
route: `/campaigns/${campaignId}/characters/create`,
|
||||||
|
actionIcon: 'plus'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
const sortedArcs = [...data.arcs].sort(byName);
|
const sortedArcs = [...data.arcs].sort(byName);
|
||||||
|
|
||||||
return sortedArcs.map(arc => {
|
const arcNodes: TreeItem[] = sortedArcs.map((arc, idx) => {
|
||||||
const sortedChapters = [...(data.chaptersByArc[arc.id!] ?? [])].sort(byName);
|
const sortedChapters = [...(data.chaptersByArc[arc.id!] ?? [])].sort(byName);
|
||||||
|
|
||||||
const chapterItems: TreeItem[] = sortedChapters.map(ch => {
|
const chapterItems: TreeItem[] = sortedChapters.map(ch => {
|
||||||
@@ -98,6 +129,8 @@ export function buildCampaignTree(campaignId: string, data: CampaignTreeData): T
|
|||||||
label: arc.name,
|
label: arc.name,
|
||||||
children: chapterItems,
|
children: chapterItems,
|
||||||
route: `/campaigns/${campaignId}/arcs/${arc.id}`,
|
route: `/campaigns/${campaignId}/arcs/${arc.id}`,
|
||||||
|
sectionHeaderBefore: idx === 0 ? 'Narration' : undefined,
|
||||||
|
|
||||||
createActions: [{
|
createActions: [{
|
||||||
id: `new-chapter-${arc.id}`,
|
id: `new-chapter-${arc.id}`,
|
||||||
label: 'Nouveau chapitre',
|
label: 'Nouveau chapitre',
|
||||||
@@ -106,4 +139,6 @@ export function buildCampaignTree(campaignId: string, data: CampaignTreeData): T
|
|||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return [...arcNodes, charactersNode];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { forkJoin } from 'rxjs';
|
import { forkJoin } from 'rxjs';
|
||||||
import { LucideAngularModule } from 'lucide-angular';
|
import { LucideAngularModule } from 'lucide-angular';
|
||||||
import { CampaignService } from '../../services/campaign.service';
|
import { CampaignService } from '../../services/campaign.service';
|
||||||
|
import { CharacterService } from '../../services/character.service';
|
||||||
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
||||||
import { Campaign } from '../../services/campaign.model';
|
import { Campaign } from '../../services/campaign.model';
|
||||||
import { loadCampaignTreeData, buildCampaignTree } from '../campaign-tree.helper';
|
import { loadCampaignTreeData, buildCampaignTree } from '../campaign-tree.helper';
|
||||||
@@ -32,6 +33,7 @@ export class ChapterCreateComponent implements OnInit, OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private campaignService: CampaignService,
|
private campaignService: CampaignService,
|
||||||
|
private characterService: CharacterService,
|
||||||
private layoutService: LayoutService
|
private layoutService: LayoutService
|
||||||
) {
|
) {
|
||||||
this.form = this.fb.group({
|
this.form = this.fb.group({
|
||||||
@@ -50,7 +52,7 @@ export class ChapterCreateComponent implements OnInit, OnDestroy {
|
|||||||
forkJoin({
|
forkJoin({
|
||||||
campaign: this.campaignService.getCampaignById(this.campaignId),
|
campaign: this.campaignService.getCampaignById(this.campaignId),
|
||||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId)
|
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService)
|
||||||
}).subscribe(({ campaign, allCampaigns, treeData }) => {
|
}).subscribe(({ campaign, allCampaigns, treeData }) => {
|
||||||
const currentArc = treeData.arcs.find(a => a.id === this.arcId);
|
const currentArc = treeData.arcs.find(a => a.id === this.arcId);
|
||||||
this.arcName = currentArc?.name ?? '';
|
this.arcName = currentArc?.name ?? '';
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { forkJoin, of } from 'rxjs';
|
|||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { LucideAngularModule, Trash2, Sparkles } from 'lucide-angular';
|
import { LucideAngularModule, Trash2, Sparkles } from 'lucide-angular';
|
||||||
import { CampaignService } from '../../services/campaign.service';
|
import { CampaignService } from '../../services/campaign.service';
|
||||||
|
import { CharacterService } from '../../services/character.service';
|
||||||
import { PageService } from '../../services/page.service';
|
import { PageService } from '../../services/page.service';
|
||||||
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
||||||
import { PageTitleService } from '../../services/page-title.service';
|
import { PageTitleService } from '../../services/page-title.service';
|
||||||
@@ -61,6 +62,7 @@ export class ChapterEditComponent implements OnInit, OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private campaignService: CampaignService,
|
private campaignService: CampaignService,
|
||||||
|
private characterService: CharacterService,
|
||||||
private pageService: PageService,
|
private pageService: PageService,
|
||||||
private layoutService: LayoutService,
|
private layoutService: LayoutService,
|
||||||
private pageTitleService: PageTitleService
|
private pageTitleService: PageTitleService
|
||||||
@@ -98,7 +100,7 @@ export class ChapterEditComponent implements OnInit, OnDestroy {
|
|||||||
campaign: this.campaignService.getCampaignById(this.campaignId),
|
campaign: this.campaignService.getCampaignById(this.campaignId),
|
||||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||||
chapter: this.campaignService.getChapterById(this.chapterId),
|
chapter: this.campaignService.getChapterById(this.chapterId),
|
||||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId)
|
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService)
|
||||||
}).pipe(
|
}).pipe(
|
||||||
switchMap(data => {
|
switchMap(data => {
|
||||||
const lid = data.campaign.loreId ?? null;
|
const lid = data.campaign.loreId ?? null;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
|||||||
import { forkJoin } from 'rxjs';
|
import { forkJoin } from 'rxjs';
|
||||||
import { LucideAngularModule, ArrowLeft } from 'lucide-angular';
|
import { LucideAngularModule, ArrowLeft } from 'lucide-angular';
|
||||||
import { CampaignService } from '../../services/campaign.service';
|
import { CampaignService } from '../../services/campaign.service';
|
||||||
|
import { CharacterService } from '../../services/character.service';
|
||||||
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
||||||
import { PageTitleService } from '../../services/page-title.service';
|
import { PageTitleService } from '../../services/page-title.service';
|
||||||
import { Campaign, Chapter, Scene } from '../../services/campaign.model';
|
import { Campaign, Chapter, Scene } from '../../services/campaign.model';
|
||||||
@@ -48,6 +49,7 @@ export class ChapterGraphComponent implements OnInit, OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private campaignService: CampaignService,
|
private campaignService: CampaignService,
|
||||||
|
private characterService: CharacterService,
|
||||||
private layoutService: LayoutService,
|
private layoutService: LayoutService,
|
||||||
private pageTitleService: PageTitleService
|
private pageTitleService: PageTitleService
|
||||||
) {}
|
) {}
|
||||||
@@ -67,7 +69,7 @@ export class ChapterGraphComponent implements OnInit, OnDestroy {
|
|||||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||||
chapter: this.campaignService.getChapterById(this.chapterId),
|
chapter: this.campaignService.getChapterById(this.chapterId),
|
||||||
scenes: this.campaignService.getScenes(this.chapterId),
|
scenes: this.campaignService.getScenes(this.chapterId),
|
||||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId)
|
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService)
|
||||||
}).subscribe(({ campaign, allCampaigns, chapter, scenes, treeData }) => {
|
}).subscribe(({ campaign, allCampaigns, chapter, scenes, treeData }) => {
|
||||||
this.chapter = chapter;
|
this.chapter = chapter;
|
||||||
this.scenes = scenes;
|
this.scenes = scenes;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { forkJoin, of } from 'rxjs';
|
|||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { LucideAngularModule, Pencil, Network, Trash2 } from 'lucide-angular';
|
import { LucideAngularModule, Pencil, Network, Trash2 } from 'lucide-angular';
|
||||||
import { CampaignService } from '../../services/campaign.service';
|
import { CampaignService } from '../../services/campaign.service';
|
||||||
|
import { CharacterService } from '../../services/character.service';
|
||||||
import { PageService } from '../../services/page.service';
|
import { PageService } from '../../services/page.service';
|
||||||
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
||||||
import { PageTitleService } from '../../services/page-title.service';
|
import { PageTitleService } from '../../services/page-title.service';
|
||||||
@@ -41,6 +42,7 @@ export class ChapterViewComponent implements OnInit, OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private campaignService: CampaignService,
|
private campaignService: CampaignService,
|
||||||
|
private characterService: CharacterService,
|
||||||
private pageService: PageService,
|
private pageService: PageService,
|
||||||
private layoutService: LayoutService,
|
private layoutService: LayoutService,
|
||||||
private pageTitleService: PageTitleService
|
private pageTitleService: PageTitleService
|
||||||
@@ -67,7 +69,7 @@ export class ChapterViewComponent implements OnInit, OnDestroy {
|
|||||||
campaign: this.campaignService.getCampaignById(this.campaignId),
|
campaign: this.campaignService.getCampaignById(this.campaignId),
|
||||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||||
chapter: this.campaignService.getChapterById(this.chapterId),
|
chapter: this.campaignService.getChapterById(this.chapterId),
|
||||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId)
|
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService)
|
||||||
}).pipe(
|
}).pipe(
|
||||||
switchMap(data => {
|
switchMap(data => {
|
||||||
const lid = data.campaign.loreId ?? null;
|
const lid = data.campaign.loreId ?? null;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { forkJoin } from 'rxjs';
|
import { forkJoin } from 'rxjs';
|
||||||
import { LucideAngularModule } from 'lucide-angular';
|
import { LucideAngularModule } from 'lucide-angular';
|
||||||
import { CampaignService } from '../../services/campaign.service';
|
import { CampaignService } from '../../services/campaign.service';
|
||||||
|
import { CharacterService } from '../../services/character.service';
|
||||||
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
||||||
import { Campaign } from '../../services/campaign.model';
|
import { Campaign } from '../../services/campaign.model';
|
||||||
import { loadCampaignTreeData, buildCampaignTree } from '../campaign-tree.helper';
|
import { loadCampaignTreeData, buildCampaignTree } from '../campaign-tree.helper';
|
||||||
@@ -33,6 +34,7 @@ export class SceneCreateComponent implements OnInit, OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private campaignService: CampaignService,
|
private campaignService: CampaignService,
|
||||||
|
private characterService: CharacterService,
|
||||||
private layoutService: LayoutService
|
private layoutService: LayoutService
|
||||||
) {
|
) {
|
||||||
this.form = this.fb.group({
|
this.form = this.fb.group({
|
||||||
@@ -52,7 +54,7 @@ export class SceneCreateComponent implements OnInit, OnDestroy {
|
|||||||
forkJoin({
|
forkJoin({
|
||||||
campaign: this.campaignService.getCampaignById(this.campaignId),
|
campaign: this.campaignService.getCampaignById(this.campaignId),
|
||||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId)
|
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService)
|
||||||
}).subscribe(({ campaign, allCampaigns, treeData }) => {
|
}).subscribe(({ campaign, allCampaigns, treeData }) => {
|
||||||
const currentChapter = (treeData.chaptersByArc[this.arcId] ?? []).find(c => c.id === this.chapterId);
|
const currentChapter = (treeData.chaptersByArc[this.arcId] ?? []).find(c => c.id === this.chapterId);
|
||||||
this.chapterName = currentChapter?.name ?? '';
|
this.chapterName = currentChapter?.name ?? '';
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { forkJoin, of } from 'rxjs';
|
|||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { LucideAngularModule, Trash2, Sparkles } from 'lucide-angular';
|
import { LucideAngularModule, Trash2, Sparkles } from 'lucide-angular';
|
||||||
import { CampaignService } from '../../services/campaign.service';
|
import { CampaignService } from '../../services/campaign.service';
|
||||||
|
import { CharacterService } from '../../services/character.service';
|
||||||
import { PageService } from '../../services/page.service';
|
import { PageService } from '../../services/page.service';
|
||||||
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
||||||
import { PageTitleService } from '../../services/page-title.service';
|
import { PageTitleService } from '../../services/page-title.service';
|
||||||
@@ -65,6 +66,7 @@ export class SceneEditComponent implements OnInit, OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private campaignService: CampaignService,
|
private campaignService: CampaignService,
|
||||||
|
private characterService: CharacterService,
|
||||||
private pageService: PageService,
|
private pageService: PageService,
|
||||||
private layoutService: LayoutService,
|
private layoutService: LayoutService,
|
||||||
private pageTitleService: PageTitleService
|
private pageTitleService: PageTitleService
|
||||||
@@ -116,7 +118,7 @@ export class SceneEditComponent implements OnInit, OnDestroy {
|
|||||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||||
scene: this.campaignService.getSceneById(this.sceneId),
|
scene: this.campaignService.getSceneById(this.sceneId),
|
||||||
chapterScenes: this.campaignService.getScenes(this.chapterId),
|
chapterScenes: this.campaignService.getScenes(this.chapterId),
|
||||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId)
|
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService)
|
||||||
}).pipe(
|
}).pipe(
|
||||||
switchMap(data => {
|
switchMap(data => {
|
||||||
const lid = data.campaign.loreId ?? null;
|
const lid = data.campaign.loreId ?? null;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { forkJoin, of } from 'rxjs';
|
|||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { LucideAngularModule, Pencil, Trash2 } from 'lucide-angular';
|
import { LucideAngularModule, Pencil, Trash2 } from 'lucide-angular';
|
||||||
import { CampaignService } from '../../services/campaign.service';
|
import { CampaignService } from '../../services/campaign.service';
|
||||||
|
import { CharacterService } from '../../services/character.service';
|
||||||
import { PageService } from '../../services/page.service';
|
import { PageService } from '../../services/page.service';
|
||||||
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
import { LayoutService, GlobalItem } from '../../services/layout.service';
|
||||||
import { PageTitleService } from '../../services/page-title.service';
|
import { PageTitleService } from '../../services/page-title.service';
|
||||||
@@ -41,6 +42,7 @@ export class SceneViewComponent implements OnInit, OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private campaignService: CampaignService,
|
private campaignService: CampaignService,
|
||||||
|
private characterService: CharacterService,
|
||||||
private pageService: PageService,
|
private pageService: PageService,
|
||||||
private layoutService: LayoutService,
|
private layoutService: LayoutService,
|
||||||
private pageTitleService: PageTitleService
|
private pageTitleService: PageTitleService
|
||||||
@@ -70,7 +72,7 @@ export class SceneViewComponent implements OnInit, OnDestroy {
|
|||||||
campaign: this.campaignService.getCampaignById(this.campaignId),
|
campaign: this.campaignService.getCampaignById(this.campaignId),
|
||||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||||
scene: this.campaignService.getSceneById(this.sceneId),
|
scene: this.campaignService.getSceneById(this.sceneId),
|
||||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId)
|
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService)
|
||||||
}).pipe(
|
}).pipe(
|
||||||
switchMap(data => {
|
switchMap(data => {
|
||||||
const lid = data.campaign.loreId ?? null;
|
const lid = data.campaign.loreId ?? null;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ============ Grille des dossiers racine ============ -->
|
<!-- ============ Grille des dossiers racine ============ -->
|
||||||
<section class="detail-section nodes-section">
|
<section class="detail-section nodes-section" *ngIf="!editing">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>Dossiers</h2>
|
<h2>Dossiers</h2>
|
||||||
<button class="btn-add" (click)="navigateToCreateNode()">
|
<button class="btn-add" (click)="navigateToCreateNode()">
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ export interface TreeItem {
|
|||||||
iconKey?: string;
|
iconKey?: string;
|
||||||
/** Petit badge affiché à droite (ex: "3" pour compter les pages d'un dossier). */
|
/** Petit badge affiché à droite (ex: "3" pour compter les pages d'un dossier). */
|
||||||
meta?: string;
|
meta?: string;
|
||||||
|
/**
|
||||||
|
* Libellé de section affiché AU-DESSUS du nœud, avec un filet de séparation.
|
||||||
|
* Utilisé pour grouper visuellement des nœuds racines (ex: "Personnages" vs "Narration").
|
||||||
|
*/
|
||||||
|
sectionHeaderBefore?: string;
|
||||||
/**
|
/**
|
||||||
* Actions de creation contextuelles (ex: "+ Nouveau chapitre" sur un arc).
|
* Actions de creation contextuelles (ex: "+ Nouveau chapitre" sur un arc).
|
||||||
* Affichees comme boutons icone au survol du noeud (repli visuel), et en
|
* Affichees comme boutons icone au survol du noeud (repli visuel), et en
|
||||||
|
|||||||
@@ -29,6 +29,9 @@
|
|||||||
|
|
||||||
<!-- Template récursif : un noeud d'arbre rend son bouton, puis ses enfants via ce même template -->
|
<!-- Template récursif : un noeud d'arbre rend son bouton, puis ses enfants via ce même template -->
|
||||||
<ng-template #treeNode let-item let-level="level">
|
<ng-template #treeNode let-item let-level="level">
|
||||||
|
<div class="tree-section-header" *ngIf="level === 0 && item.sectionHeaderBefore">
|
||||||
|
{{ item.sectionHeaderBefore }}
|
||||||
|
</div>
|
||||||
<div class="tree-item" [style.padding-left.px]="level * 12">
|
<div class="tree-item" [style.padding-left.px]="level * 12">
|
||||||
<div class="tree-row">
|
<div class="tree-row">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -110,6 +110,23 @@
|
|||||||
padding: 0.25rem 0;
|
padding: 0.25rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// En-tête de section — groupe visuellement les nœuds racines (ex: Personnages / Narration).
|
||||||
|
// Un filet au-dessus crée la séparation ; pas de filet pour la première section
|
||||||
|
// (le titre suffit) — on cible ça via :not(:first-child).
|
||||||
|
.tree-section-header {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #9ca3af;
|
||||||
|
padding: 0.6rem 0.5rem 0.3rem;
|
||||||
|
}
|
||||||
|
.tree > .tree-section-header:not(:first-child) {
|
||||||
|
border-top: 1px solid #374151;
|
||||||
|
margin-top: 0.35rem;
|
||||||
|
padding-top: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
.tree-btn {
|
.tree-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user