Possibilité d'importer un template de monstre depuis le systeme qu'on utilise sur Foundry pour forcer le template. Lors de l'export, le monstre est automatiquement adapté comme ça.
All checks were successful
Build & Push Images / build (brain) (push) Successful in 1m25s
Build & Push Images / build (web) (push) Successful in 1m54s
Build & Push Images / build (core) (push) Successful in 3m29s
Build & Push Images / build-switcher (push) Successful in 17s

à l'import des monstres depuis foundry : on garde la structure originelle utilisée sur foundry également
This commit is contained in:
2026-06-25 16:50:27 +02:00
parent 9bd66613b6
commit b999aa54ad
45 changed files with 590 additions and 41 deletions

View File

@@ -1,20 +1,25 @@
<div class="picker">
<!-- Chips des ennemis déjà liés -->
@if (linkedEnemies.length > 0) {
<!-- Chips des ennemis déjà liés (avec quantité) -->
@if (linkedGroups.length > 0) {
<div class="linked-chips">
@for (e of linkedEnemies; track e.id) {
@for (g of linkedGroups; track g.enemy.id) {
<span class="chip">
<a
class="chip-label"
[href]="enemyUrl(e.id!)"
[href]="enemyUrl(g.enemy.id!)"
target="_blank"
rel="noopener"
[title]="'enemyLinkPicker.openInNewTab' | translate">
<lucide-icon [img]="Skull" [size]="12"></lucide-icon>
{{ label(e) }}
{{ label(g.enemy) }}
</a>
<button type="button" class="chip-remove" (click)="remove(e.id!)" [title]="'enemyLinkPicker.removeLink' | translate">
<span class="chip-qty">
<button type="button" class="chip-step" (click)="decrement(g.enemy.id!)" [title]="'enemyLinkPicker.decrement' | translate"></button>
<span class="chip-count">×{{ g.count }}</span>
<button type="button" class="chip-step" (click)="increment(g.enemy.id!)" [title]="'enemyLinkPicker.increment' | translate">+</button>
</span>
<button type="button" class="chip-remove" (click)="remove(g.enemy.id!)" [title]="'enemyLinkPicker.removeLink' | translate">
<lucide-icon [img]="X" [size]="12"></lucide-icon>
</button>
</span>

View File

@@ -33,6 +33,32 @@
&:hover { color: #fecdd3; }
}
.chip-qty {
display: inline-flex;
align-items: center;
border-left: 1px solid #2a2a3d;
.chip-step {
width: 1.4rem;
padding: 0.25rem 0;
background: transparent;
border: none;
color: inherit;
cursor: pointer;
font-size: 0.95rem;
line-height: 1;
opacity: 0.8;
&:hover { opacity: 1; background: #2a2a3d; color: #fecdd3; }
}
.chip-count {
min-width: 1.6rem;
text-align: center;
font-variant-numeric: tabular-nums;
opacity: 0.9;
}
}
.chip-remove {
display: inline-flex;
align-items: center;

View File

@@ -44,11 +44,20 @@ export class EnemyLinkPickerComponent {
/** true tant que l'input a le focus (pour afficher le dropdown). */
dropdownOpen = false;
/** Ennemis actuellement liés (résolus en objets complets pour affichage). */
get linkedEnemies(): Enemy[] {
return this.value
.map(id => this.availableEnemies.find(e => e.id === id))
.filter((e): e is Enemy => !!e);
/**
* Ennemis liés groupés AVEC leur quantité (le même id peut apparaître N fois
* dans `value` → N tokens à l'export). Ordre = première apparition.
*/
get linkedGroups(): { enemy: Enemy; count: number }[] {
const counts = new Map<string, number>();
const order: string[] = [];
for (const id of this.value) {
if (!counts.has(id)) order.push(id);
counts.set(id, (counts.get(id) ?? 0) + 1);
}
return order
.map(id => ({ enemy: this.availableEnemies.find(e => e.id === id), count: counts.get(id)! }))
.filter((g): g is { enemy: Enemy; count: number } => !!g.enemy);
}
/** Ennemis proposables dans le dropdown — filtrés par query, exclut les déjà liés. */
@@ -66,14 +75,28 @@ export class EnemyLinkPickerComponent {
return level ? `${enemy.name} (${level})` : enemy.name;
}
/** Ajoute un ennemi aux liens. */
/** Ajoute un ennemi aux liens (première occurrence). */
add(enemy: Enemy): void {
if (!enemy.id || this.value.includes(enemy.id)) return;
if (!enemy.id) return;
this.valueChange.emit([...this.value, enemy.id]);
this.query = '';
}
/** Retire un ennemi des liens. */
/** +1 du même ennemi (un token de plus à l'export). */
increment(enemyId: string): void {
this.valueChange.emit([...this.value, enemyId]);
}
/** 1 du même ennemi (retire UNE occurrence). */
decrement(enemyId: string): void {
const i = this.value.indexOf(enemyId);
if (i < 0) return;
const next = [...this.value];
next.splice(i, 1);
this.valueChange.emit(next);
}
/** Retire TOUTES les occurrences de cet ennemi. */
remove(enemyId: string): void {
this.valueChange.emit(this.value.filter(id => id !== enemyId));
}