Plusieurs ajouts :
Some checks failed
Build & Push Images / build (brain) (push) Successful in 1m47s
Build & Push Images / build (core) (push) Failing after 1m56s
Build & Push Images / build-switcher (push) Successful in 20s
Build & Push Images / build (web) (push) Successful in 2m11s

- Possibilité de configurer des lieux dans une scène : permet de configurer un donjon par exemple avec les pièces, les trésors par pièce, la narration..... Mise en place également de conditions permettant de conditionner le déblocage des quêtes.
- Possibilité de transformé un arc en instance non linéaire afin de faire un hub. Permet de jouer de préparer des campagnes type Dragon of Icespire peak plus facilement.
- Configuration de partie : chaque partie va contenir les séances, ce qui permettra de suivre le déblocage des conditions pour les quêtes.

Passage en 0.9.1-beta
This commit is contained in:
2026-06-03 15:44:48 +02:00
parent 694f687fec
commit c3d3394ec7
147 changed files with 5347 additions and 392 deletions

View File

@@ -0,0 +1,21 @@
<div class="flags-manager">
<p class="flags-empty" *ngIf="!loading && rows.length === 0">
Aucun fait référencé. Ajoutez une condition « Quand un fait est vrai » sur
au moins une quête pour qu'il apparaisse ici.
</p>
<ul class="flag-list" *ngIf="rows.length > 0">
<li class="flag-row" *ngFor="let r of rows" [class.flag-on]="r.value">
<label class="flag-toggle">
<input type="checkbox" [checked]="r.value" (change)="toggle(r)" />
<span class="flag-toggle-slider"></span>
</label>
<div class="flag-info">
<span class="flag-name">{{ r.name }}</span>
</div>
<span class="flag-value">{{ r.value ? 'vrai' : 'faux' }}</span>
</li>
</ul>
</div>

View File

@@ -0,0 +1,133 @@
.flags-manager {
display: flex;
flex-direction: column;
gap: 0.85rem;
}
.flag-add-row {
display: flex;
gap: 0.5rem;
}
.flag-add-input {
flex: 1;
padding: 0.4rem 0.6rem;
border: 1px solid var(--color-border, #d8d8d8);
border-radius: 6px;
font-size: 0.9rem;
}
.flags-empty {
margin: 0;
font-size: 0.85rem;
color: var(--color-text-muted, #777);
font-style: italic;
}
.flag-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.flag-row {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.5rem 0.7rem;
border: 1px solid var(--color-border, #e2e2e2);
border-radius: 8px;
background: var(--color-surface, #fff);
}
.flag-row.flag-on {
border-color: rgba(52, 168, 83, 0.45);
background: rgba(52, 168, 83, 0.05);
}
.flag-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.15rem;
min-width: 0;
}
.flag-name {
font-family: 'JetBrains Mono', ui-monospace, Menlo, Consolas, monospace;
font-size: 0.88rem;
font-weight: 600;
}
.flag-desc {
font-size: 0.78rem;
color: var(--color-text-muted, #666);
}
.flag-value {
font-size: 0.78rem;
color: var(--color-text-muted, #777);
min-width: 40px;
text-align: right;
}
.flag-remove {
background: transparent;
border: none;
color: var(--color-danger, #c0392b);
cursor: pointer;
padding: 0.25rem;
border-radius: 6px;
display: inline-flex;
align-items: center;
justify-content: center;
&:hover { background: rgba(192, 57, 43, 0.08); }
}
// Toggle switch
.flag-toggle {
position: relative;
display: inline-block;
width: 36px;
height: 20px;
flex: 0 0 36px;
input {
opacity: 0;
width: 0;
height: 0;
&:checked + .flag-toggle-slider {
background: var(--color-primary, #34a853);
}
&:checked + .flag-toggle-slider::before {
transform: translateX(16px);
}
}
}
.flag-toggle-slider {
position: absolute;
inset: 0;
background: #ccc;
border-radius: 999px;
cursor: pointer;
transition: background 150ms ease;
&::before {
content: '';
position: absolute;
height: 16px;
width: 16px;
left: 2px;
top: 2px;
background: #fff;
border-radius: 50%;
transition: transform 150ms ease;
}
}

View File

@@ -0,0 +1,70 @@
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { LucideAngularModule } from 'lucide-angular';
import { CampaignFlagService } from '../../services/campaign-flag.service';
import { PlaythroughFlagService } from '../../services/playthrough-flag.service';
import { forkJoin } from 'rxjs';
/**
* Toggle de l'état des faits d'une Partie.
*
* <p>La liste des faits est déduite des conditions FLAG_SET référencées par les
* quêtes de la campagne. Pour chaque fait référencé, on affiche un toggle qui
* met à jour la valeur du Playthrough courant.</p>
*/
@Component({
selector: 'app-playthrough-flags-manager',
standalone: true,
imports: [CommonModule, FormsModule, LucideAngularModule],
templateUrl: './playthrough-flags-manager.component.html',
styleUrls: ['./playthrough-flags-manager.component.scss']
})
export class PlaythroughFlagsManagerComponent implements OnInit, OnChanges {
@Input() campaignId!: string;
@Input() playthroughId!: string;
rows: { name: string; value: boolean }[] = [];
loading = false;
constructor(
private campaignFlagService: CampaignFlagService,
private playthroughFlagService: PlaythroughFlagService
) {}
ngOnInit(): void { this.reload(); }
ngOnChanges(changes: SimpleChanges): void {
if (changes['campaignId'] || changes['playthroughId']) this.reload();
}
reload(): void {
if (!this.campaignId || !this.playthroughId) {
this.rows = [];
return;
}
this.loading = true;
forkJoin({
referenced: this.campaignFlagService.listReferenced(this.campaignId),
values: this.playthroughFlagService.list(this.playthroughId)
}).subscribe({
next: ({ referenced, values }) => {
const valueByName: Record<string, boolean> = {};
for (const v of values) valueByName[v.name] = v.value;
this.rows = referenced.map(name => ({
name,
value: valueByName[name] === true
}));
this.loading = false;
},
error: () => { this.loading = false; }
});
}
toggle(row: { name: string; value: boolean }): void {
const next = !row.value;
this.playthroughFlagService.setFlag(this.playthroughId, row.name, next).subscribe({
next: () => { row.value = next; }
});
}
}