Mise en place de l'anglais comme deuxième langue pour l'application
This commit is contained in:
@@ -5,9 +5,9 @@
|
||||
@if (messages.length === 0 && !currentAssistantText && !error) {
|
||||
<div class="welcome-hint">
|
||||
<lucide-icon [img]="Sparkles" [size]="18"></lucide-icon>
|
||||
<p>Pose une question à l'IA pendant la partie.</p>
|
||||
<p>{{ 'sessionAiChatPanel.welcome' | translate }}</p>
|
||||
<p class="welcome-sub">
|
||||
Elle connaît ton univers, ta campagne, les règles du système et tout ce qui a été noté dans le journal.
|
||||
{{ 'sessionAiChatPanel.welcomeSub' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
@@ -20,10 +20,10 @@
|
||||
type="button"
|
||||
class="msg-action"
|
||||
[disabled]="!canSaveToJournal"
|
||||
[title]="canSaveToJournal ? 'Ajouter cette réponse au journal' : 'Session terminée'"
|
||||
[title]="(canSaveToJournal ? 'sessionAiChatPanel.saveReplyTitle' : 'sessionAiChatPanel.sessionEnded') | translate"
|
||||
(click)="onSaveToJournal(m.content)">
|
||||
<lucide-icon [img]="BookmarkPlus" [size]="12"></lucide-icon>
|
||||
Au journal
|
||||
{{ 'sessionAiChatPanel.toJournal' | translate }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@@ -47,7 +47,7 @@
|
||||
[(ngModel)]="input"
|
||||
name="aiChatInput"
|
||||
rows="2"
|
||||
[placeholder]="isStreaming ? 'L’IA répond…' : 'Demande une idée, un rebondissement, une description…'"
|
||||
[placeholder]="(isStreaming ? 'sessionAiChatPanel.replying' : 'sessionAiChatPanel.inputPlaceholder') | translate"
|
||||
[disabled]="isStreaming"
|
||||
(keydown.control.enter)="send()"></textarea>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
class="btn-link"
|
||||
[disabled]="messages.length === 0 && !currentAssistantText"
|
||||
(click)="clearConversation()"
|
||||
title="Effacer la conversation">
|
||||
[title]="'sessionAiChatPanel.clearConversation' | translate">
|
||||
<lucide-icon [img]="Trash2" [size]="12"></lucide-icon>
|
||||
</button>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
[disabled]="!input.trim()"
|
||||
(click)="send()">
|
||||
<lucide-icon [img]="Send" [size]="14"></lucide-icon>
|
||||
Envoyer
|
||||
{{ 'sessionAiChatPanel.send' | translate }}
|
||||
</button>
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
class="btn-secondary btn-send"
|
||||
(click)="cancelStream()">
|
||||
<lucide-icon [img]="Square" [size]="14"></lucide-icon>
|
||||
Stop
|
||||
{{ 'sessionAiChatPanel.stop' | translate }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
||||
import {
|
||||
LucideAngularModule, Send, Sparkles, Trash2, BookmarkPlus, Square
|
||||
} from 'lucide-angular';
|
||||
@@ -23,7 +24,7 @@ import { AiChatService, ChatMessage } from '../../services/ai-chat.service';
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-session-ai-chat-panel',
|
||||
imports: [FormsModule, LucideAngularModule],
|
||||
imports: [FormsModule, LucideAngularModule, TranslatePipe],
|
||||
templateUrl: './session-ai-chat-panel.component.html',
|
||||
styleUrls: ['./session-ai-chat-panel.component.scss']
|
||||
})
|
||||
@@ -50,7 +51,7 @@ export class SessionAiChatPanelComponent implements OnChanges, OnDestroy {
|
||||
|
||||
private streamSub: Subscription | null = null;
|
||||
|
||||
constructor(private aiChat: AiChatService) {}
|
||||
constructor(private aiChat: AiChatService, private translate: TranslateService) {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
// Reset complet si on change de session (changement d'instance jouée).
|
||||
@@ -87,8 +88,8 @@ export class SessionAiChatPanelComponent implements OnChanges, OnDestroy {
|
||||
}
|
||||
},
|
||||
error: (err: unknown) => {
|
||||
const message = err instanceof Error ? err.message : 'Erreur inconnue';
|
||||
this.error = `Erreur IA : ${message}`;
|
||||
const message = err instanceof Error ? err.message : this.translate.instant('sessionAiChatPanel.unknownError');
|
||||
this.error = this.translate.instant('sessionAiChatPanel.aiError', { message });
|
||||
this.isStreaming = false;
|
||||
this.streamSub = null;
|
||||
},
|
||||
@@ -115,7 +116,7 @@ export class SessionAiChatPanelComponent implements OnChanges, OnDestroy {
|
||||
}
|
||||
// On garde ce qui a déjà été streamé : utile si l'IA partait dans le mur.
|
||||
if (this.currentAssistantText.trim()) {
|
||||
this.messages = [...this.messages, { role: 'assistant', content: this.currentAssistantText + ' [interrompu]' }];
|
||||
this.messages = [...this.messages, { role: 'assistant', content: this.currentAssistantText + ' ' + this.translate.instant('sessionAiChatPanel.interrupted') }];
|
||||
}
|
||||
this.currentAssistantText = '';
|
||||
this.isStreaming = false;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="session-detail">
|
||||
<a class="back-link" [routerLink]="campaignId ? ['/campaigns', campaignId] : ['/campaigns']">
|
||||
<lucide-icon [img]="ArrowLeft" [size]="14"></lucide-icon>
|
||||
Retour à la campagne
|
||||
{{ 'sessionDetail.backToCampaign' | translate }}
|
||||
</a>
|
||||
<div class="detail-header">
|
||||
<div class="header-texts">
|
||||
@@ -12,7 +12,7 @@
|
||||
<lucide-icon [img]="Dices" [size]="24"></lucide-icon>
|
||||
{{ session.name }}
|
||||
</h1>
|
||||
<button type="button" class="btn-icon" (click)="startRename()" title="Renommer la session">
|
||||
<button type="button" class="btn-icon" (click)="startRename()" [title]="'sessionDetail.renameTitle' | translate">
|
||||
<lucide-icon [img]="Pencil" [size]="14"></lucide-icon>
|
||||
</button>
|
||||
</div>
|
||||
@@ -25,22 +25,22 @@
|
||||
(keydown.enter)="saveRename()"
|
||||
(keydown.escape)="cancelRename()"
|
||||
autofocus />
|
||||
<button type="button" class="btn-icon" (click)="saveRename()" [disabled]="!editName.trim()" title="Valider">
|
||||
<button type="button" class="btn-icon" (click)="saveRename()" [disabled]="!editName.trim()" [title]="'common.validate' | translate">
|
||||
<lucide-icon [img]="Check" [size]="14"></lucide-icon>
|
||||
</button>
|
||||
<button type="button" class="btn-icon" (click)="cancelRename()" title="Annuler">
|
||||
Annuler
|
||||
<button type="button" class="btn-icon" (click)="cancelRename()" [title]="'common.cancel' | translate">
|
||||
{{ 'common.cancel' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<div class="meta">
|
||||
<span class="badge" [class.badge-active]="session.active">
|
||||
{{ session.active ? 'En cours' : 'Terminée' }}
|
||||
{{ (session.active ? 'sessionDetail.statusActive' : 'sessionDetail.statusEnded') | translate }}
|
||||
</span>
|
||||
<span class="badge badge-muted">Démarrée le {{ session.startedAt | date:'dd/MM/yyyy HH:mm' }}</span>
|
||||
<span class="badge badge-muted">{{ 'sessionDetail.startedOn' | translate:{ date: (session.startedAt | date:'dd/MM/yyyy HH:mm') } }}</span>
|
||||
@if (session.endedAt) {
|
||||
<span class="badge badge-muted">
|
||||
Terminée le {{ session.endedAt | date:'dd/MM/yyyy HH:mm' }}
|
||||
{{ 'sessionDetail.endedOn' | translate:{ date: (session.endedAt | date:'dd/MM/yyyy HH:mm') } }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
@@ -49,12 +49,12 @@
|
||||
@if (session.active) {
|
||||
<button type="button" class="btn-secondary" (click)="endSession()">
|
||||
<lucide-icon [img]="Square" [size]="14"></lucide-icon>
|
||||
Terminer la session
|
||||
{{ 'sessionDetail.endSession' | translate }}
|
||||
</button>
|
||||
}
|
||||
<button type="button" class="btn-danger" (click)="deleteSession()">
|
||||
<lucide-icon [img]="Trash2" [size]="14"></lucide-icon>
|
||||
Supprimer
|
||||
{{ 'common.delete' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,7 +74,7 @@
|
||||
[style.--type-color]="entryTypeMeta[type].color"
|
||||
(click)="newEntryType = type">
|
||||
<lucide-icon [img]="typeIcons[type]" [size]="14"></lucide-icon>
|
||||
{{ entryTypeMeta[type].label }}
|
||||
{{ typeLabel(type) }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@@ -82,29 +82,29 @@
|
||||
[(ngModel)]="newEntryContent"
|
||||
name="newEntryContent"
|
||||
rows="3"
|
||||
[placeholder]="'Ajouter une ' + entryTypeMeta[newEntryType].label.toLowerCase() + '…'"
|
||||
[placeholder]="newEntryPlaceholder()"
|
||||
(keydown.control.enter)="submitNewEntry()"></textarea>
|
||||
<div class="entry-input-footer">
|
||||
<span class="hint">Ctrl + Entrée pour ajouter</span>
|
||||
<span class="hint">{{ 'sessionDetail.ctrlEnterHint' | translate }}</span>
|
||||
<button type="button"
|
||||
class="btn-primary"
|
||||
[disabled]="!newEntryContent.trim() || submittingEntry"
|
||||
(click)="submitNewEntry()">
|
||||
<lucide-icon [img]="Plus" [size]="14"></lucide-icon>
|
||||
Ajouter
|
||||
{{ 'common.add' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
<!-- Timeline -->
|
||||
<section class="detail-section timeline-section">
|
||||
<h2>Journal de session</h2>
|
||||
<h2>{{ 'sessionDetail.journalTitle' | translate }}</h2>
|
||||
@if (entries.length === 0) {
|
||||
<div class="empty-state">
|
||||
<p>Aucune entrée pour le moment.</p>
|
||||
<p>{{ 'sessionDetail.noEntries' | translate }}</p>
|
||||
@if (session.active) {
|
||||
<p class="hint">
|
||||
Saisis une note, un évènement ou un jet ci-dessus pour commencer le journal.
|
||||
{{ 'sessionDetail.noEntriesHint' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
@@ -121,13 +121,13 @@
|
||||
<!-- Mode lecture -->
|
||||
@if (editingEntryId !== entry.id) {
|
||||
<div class="entry-header">
|
||||
<span class="entry-type">{{ entryTypeMeta[entry.type].label }}</span>
|
||||
<span class="entry-type">{{ typeLabel(entry.type) }}</span>
|
||||
<span class="entry-time">{{ entry.occurredAt | date:'HH:mm — dd/MM/yyyy' }}</span>
|
||||
<div class="entry-actions">
|
||||
<button type="button" class="btn-icon" (click)="startEditEntry(entry)" title="Modifier">
|
||||
<button type="button" class="btn-icon" (click)="startEditEntry(entry)" [title]="'common.edit' | translate">
|
||||
<lucide-icon [img]="Pencil" [size]="12"></lucide-icon>
|
||||
</button>
|
||||
<button type="button" class="btn-icon btn-icon--danger" (click)="deleteEntry(entry)" title="Supprimer">
|
||||
<button type="button" class="btn-icon btn-icon--danger" (click)="deleteEntry(entry)" [title]="'common.delete' | translate">
|
||||
<lucide-icon [img]="Trash2" [size]="12"></lucide-icon>
|
||||
</button>
|
||||
</div>
|
||||
@@ -145,7 +145,7 @@
|
||||
[style.--type-color]="entryTypeMeta[type].color"
|
||||
(click)="editEntryType = type">
|
||||
<lucide-icon [img]="typeIcons[type]" [size]="12"></lucide-icon>
|
||||
{{ entryTypeMeta[type].label }}
|
||||
{{ typeLabel(type) }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@@ -158,14 +158,14 @@
|
||||
<div class="entry-input-footer">
|
||||
<button type="button" class="btn-secondary btn-sm" (click)="cancelEditEntry()">
|
||||
<lucide-icon [img]="X" [size]="12"></lucide-icon>
|
||||
Annuler
|
||||
{{ 'common.cancel' | translate }}
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn-primary btn-sm"
|
||||
[disabled]="!editEntryContent.trim()"
|
||||
(click)="saveEditEntry(entry)">
|
||||
<lucide-icon [img]="Check" [size]="12"></lucide-icon>
|
||||
Sauvegarder
|
||||
{{ 'common.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
||||
import {
|
||||
LucideAngularModule, LucideIconData,
|
||||
Dices, ArrowLeft, Square, Trash2, Pencil, Check,
|
||||
@@ -29,7 +30,7 @@ import { DiceRollResult } from '../session-dice-panel/session-dice-panel.compone
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-session-detail',
|
||||
imports: [CommonModule, FormsModule, LucideAngularModule, RouterLink, SessionReferencePanelComponent],
|
||||
imports: [CommonModule, FormsModule, LucideAngularModule, TranslatePipe, RouterLink, SessionReferencePanelComponent],
|
||||
templateUrl: './session-detail.component.html',
|
||||
styleUrls: ['./session-detail.component.scss']
|
||||
})
|
||||
@@ -80,9 +81,22 @@ export class SessionDetailComponent implements OnInit, OnDestroy {
|
||||
private entryService: SessionEntryService,
|
||||
private layoutService: LayoutService,
|
||||
private pageTitleService: PageTitleService,
|
||||
private confirmDialog: ConfirmDialogService
|
||||
private confirmDialog: ConfirmDialogService,
|
||||
private translate: TranslateService
|
||||
) {}
|
||||
|
||||
/** Libellé traduit d'un type d'entrée (le modèle partagé reste en FR pour la donnée). */
|
||||
typeLabel(type: EntryType): string {
|
||||
return this.translate.instant('sessionDetail.entryType.' + type);
|
||||
}
|
||||
|
||||
/** Placeholder de la zone de saisie, dépendant du type sélectionné. */
|
||||
newEntryPlaceholder(): string {
|
||||
return this.translate.instant('sessionDetail.addEntryPlaceholder', {
|
||||
type: this.typeLabel(this.newEntryType).toLowerCase()
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.layoutService.hide();
|
||||
this.route.paramMap.pipe(
|
||||
@@ -144,10 +158,10 @@ export class SessionDetailComponent implements OnInit, OnDestroy {
|
||||
if (!this.session || !this.session.active) return;
|
||||
const session = this.session;
|
||||
this.confirmDialog.confirm({
|
||||
title: 'Terminer la session ?',
|
||||
message: `Marquer la session "${session.name}" comme terminée ?`,
|
||||
details: ['Tu pourras toujours consulter son contenu après.'],
|
||||
confirmLabel: 'Terminer',
|
||||
title: this.translate.instant('sessionDetail.endConfirm.title'),
|
||||
message: this.translate.instant('sessionDetail.endConfirm.message', { name: session.name }),
|
||||
details: [this.translate.instant('sessionDetail.endConfirm.detail')],
|
||||
confirmLabel: this.translate.instant('sessionDetail.endConfirm.confirmLabel'),
|
||||
variant: 'warning'
|
||||
}).then(ok => {
|
||||
if (!ok) return;
|
||||
@@ -162,17 +176,20 @@ export class SessionDetailComponent implements OnInit, OnDestroy {
|
||||
if (!this.session) return;
|
||||
const session = this.session;
|
||||
const entryCount = this.entries.length;
|
||||
const entriesDetail = entryCount === 0
|
||||
? this.translate.instant('sessionDetail.deleteConfirm.noEntries')
|
||||
: entryCount === 1
|
||||
? this.translate.instant('sessionDetail.deleteConfirm.entriesOne')
|
||||
: this.translate.instant('sessionDetail.deleteConfirm.entriesMany', { n: entryCount });
|
||||
const details = [
|
||||
entryCount > 0
|
||||
? `${entryCount} entrée${entryCount > 1 ? 's' : ''} de journal sera également supprimée.`
|
||||
: 'Aucune entrée de journal pour cette session.',
|
||||
'Cette action est irréversible.'
|
||||
entriesDetail,
|
||||
this.translate.instant('sessionDetail.deleteConfirm.irreversible')
|
||||
];
|
||||
this.confirmDialog.confirm({
|
||||
title: 'Supprimer la session ?',
|
||||
message: `Supprimer définitivement la session "${session.name}" ?`,
|
||||
title: this.translate.instant('sessionDetail.deleteConfirm.title'),
|
||||
message: this.translate.instant('sessionDetail.deleteConfirm.message', { name: session.name }),
|
||||
details,
|
||||
confirmLabel: 'Supprimer',
|
||||
confirmLabel: this.translate.instant('common.delete'),
|
||||
variant: 'danger'
|
||||
}).then(ok => {
|
||||
if (!ok) return;
|
||||
@@ -284,9 +301,9 @@ export class SessionDetailComponent implements OnInit, OnDestroy {
|
||||
if (!this.session) return;
|
||||
const session = this.session;
|
||||
this.confirmDialog.confirm({
|
||||
title: 'Supprimer cette entrée ?',
|
||||
message: 'Cette entrée du journal sera définitivement supprimée.',
|
||||
confirmLabel: 'Supprimer',
|
||||
title: this.translate.instant('sessionDetail.deleteEntryConfirm.title'),
|
||||
message: this.translate.instant('sessionDetail.deleteEntryConfirm.message'),
|
||||
confirmLabel: this.translate.instant('common.delete'),
|
||||
variant: 'danger'
|
||||
}).then(ok => {
|
||||
if (!ok) return;
|
||||
|
||||
@@ -15,26 +15,26 @@
|
||||
|
||||
<div class="dice-inputs">
|
||||
<label class="input-group">
|
||||
<span>Nombre</span>
|
||||
<span>{{ 'sessionDicePanel.count' | translate }}</span>
|
||||
<input type="number" min="1" max="20" [(ngModel)]="count" />
|
||||
</label>
|
||||
<label class="input-group">
|
||||
<span>Modificateur</span>
|
||||
<span>{{ 'sessionDicePanel.modifier' | translate }}</span>
|
||||
<input type="number" [(ngModel)]="modifier" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-primary btn-roll" (click)="roll()">
|
||||
<lucide-icon [img]="Dices" [size]="16"></lucide-icon>
|
||||
Lancer {{ count }}d{{ selectedFace }}{{ modifier === 0 ? '' : (modifier > 0 ? '+' + modifier : modifier) }}
|
||||
{{ 'sessionDicePanel.roll' | translate }} {{ count }}d{{ selectedFace }}{{ modifier === 0 ? '' : (modifier > 0 ? '+' + modifier : modifier) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (history.length > 0) {
|
||||
<div class="dice-history">
|
||||
<div class="history-header">
|
||||
<span>Derniers jets</span>
|
||||
<button type="button" class="btn-link" (click)="clearHistory()" title="Vider l'historique local">
|
||||
<span>{{ 'sessionDicePanel.lastRolls' | translate }}</span>
|
||||
<button type="button" class="btn-link" (click)="clearHistory()" [title]="'sessionDicePanel.clearHistory' | translate">
|
||||
<lucide-icon [img]="Trash2" [size]="12"></lucide-icon>
|
||||
</button>
|
||||
</div>
|
||||
@@ -51,7 +51,7 @@
|
||||
<button type="button"
|
||||
class="btn-icon"
|
||||
[disabled]="!canAddToJournal"
|
||||
[title]="canAddToJournal ? 'Ajouter au journal' : 'Session terminée'"
|
||||
[title]="(canAddToJournal ? 'sessionDicePanel.addToJournal' : 'sessionDicePanel.sessionEnded') | translate"
|
||||
(click)="addToJournal(r)">
|
||||
<lucide-icon [img]="BookmarkPlus" [size]="12"></lucide-icon>
|
||||
</button>
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
@if (history.length === 0) {
|
||||
<p class="placeholder-hint">
|
||||
Choisis un dé et lance.
|
||||
{{ 'sessionDicePanel.placeholder' | translate }}
|
||||
</p>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { LucideAngularModule, Dices, BookmarkPlus, Trash2 } from 'lucide-angular';
|
||||
|
||||
/** Faces de dés supportées par le roller. */
|
||||
@@ -26,7 +27,7 @@ export interface DiceRollResult {
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-session-dice-panel',
|
||||
imports: [FormsModule, LucideAngularModule],
|
||||
imports: [FormsModule, LucideAngularModule, TranslatePipe],
|
||||
templateUrl: './session-dice-panel.component.html',
|
||||
styleUrls: ['./session-dice-panel.component.scss']
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="sic-panel">
|
||||
@if (loading) {
|
||||
<p class="loading-hint">Chargement…</p>
|
||||
<p class="loading-hint">{{ 'common.loading' | translate }}</p>
|
||||
}
|
||||
|
||||
<!-- Liste des catalogues -->
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="sic-list">
|
||||
@if (catalogs.length === 0) {
|
||||
<p class="empty-hint">
|
||||
Aucun catalogue d'objets dans cette campagne.
|
||||
{{ 'sessionItemCatalogsPanel.empty' | translate }}
|
||||
</p>
|
||||
}
|
||||
@for (c of catalogs; track c) {
|
||||
@@ -25,7 +25,7 @@
|
||||
@if (selected) {
|
||||
<div class="sic-detail">
|
||||
<button type="button" class="sic-back" (click)="backToList()">
|
||||
<lucide-icon [img]="ChevronLeft" [size]="13"></lucide-icon> Catalogues
|
||||
<lucide-icon [img]="ChevronLeft" [size]="13"></lucide-icon> {{ 'sessionItemCatalogsPanel.catalogs' | translate }}
|
||||
</button>
|
||||
<h4>{{ selected.name }}</h4>
|
||||
@for (g of groups; track g) {
|
||||
@@ -45,8 +45,8 @@
|
||||
<div class="sic-obj-desc">{{ it.description }}</div>
|
||||
}
|
||||
<button type="button" class="sic-note" (click)="note(it)" [disabled]="!canAddToJournal"
|
||||
title="Consigner au journal (ex. achat)">
|
||||
<lucide-icon [img]="BookmarkPlus" [size]="12"></lucide-icon> Journal
|
||||
[title]="'sessionItemCatalogsPanel.noteTitle' | translate">
|
||||
<lucide-icon [img]="BookmarkPlus" [size]="12"></lucide-icon> {{ 'sessionItemCatalogsPanel.journal' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { LucideAngularModule, Package, BookmarkPlus, ChevronLeft } from 'lucide-angular';
|
||||
import { catchError, of } from 'rxjs';
|
||||
import { ItemCatalogService } from '../../services/item-catalog.service';
|
||||
@@ -13,7 +14,7 @@ interface ItemGroup { category: string; items: CatalogItem[]; }
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-session-item-catalogs-panel',
|
||||
imports: [LucideAngularModule],
|
||||
imports: [LucideAngularModule, TranslatePipe],
|
||||
templateUrl: './session-item-catalogs-panel.component.html',
|
||||
styleUrls: ['./session-item-catalogs-panel.component.scss']
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="srt-panel">
|
||||
@if (loading) {
|
||||
<p class="loading-hint">Chargement…</p>
|
||||
<p class="loading-hint">{{ 'common.loading' | translate }}</p>
|
||||
}
|
||||
|
||||
<!-- Liste des tables -->
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="srt-list">
|
||||
@if (tables.length === 0) {
|
||||
<p class="empty-hint">
|
||||
Aucune table aléatoire dans cette campagne. Crée-en une depuis la sidebar.
|
||||
{{ 'sessionRandomTablesPanel.empty' | translate }}
|
||||
</p>
|
||||
}
|
||||
@for (t of tables; track t) {
|
||||
@@ -25,12 +25,12 @@
|
||||
@if (selected) {
|
||||
<div class="srt-detail">
|
||||
<button type="button" class="srt-back" (click)="backToList()">
|
||||
<lucide-icon [img]="ChevronLeft" [size]="13"></lucide-icon> Tables
|
||||
<lucide-icon [img]="ChevronLeft" [size]="13"></lucide-icon> {{ 'sessionRandomTablesPanel.tables' | translate }}
|
||||
</button>
|
||||
<h4>{{ selected.name }}</h4>
|
||||
<button type="button" class="srt-roll" (click)="roll()">
|
||||
<lucide-icon [img]="Dices" [size]="15"></lucide-icon>
|
||||
Lancer {{ selected.diceFormula }}
|
||||
{{ 'sessionRandomTablesPanel.roll' | translate }} {{ selected.diceFormula }}
|
||||
</button>
|
||||
@if (lastRoll) {
|
||||
<div class="srt-result">
|
||||
@@ -44,7 +44,7 @@
|
||||
<strong>{{ matched.label }}</strong>
|
||||
}
|
||||
@if (!matched) {
|
||||
<em>aucune entrée</em>
|
||||
<em>{{ 'sessionRandomTablesPanel.noEntry' | translate }}</em>
|
||||
}
|
||||
</div>
|
||||
@if (matched?.detail) {
|
||||
@@ -52,12 +52,12 @@
|
||||
}
|
||||
<div class="srt-actions">
|
||||
<button type="button" class="srt-act" (click)="addToJournal()" [disabled]="!canAddToJournal">
|
||||
<lucide-icon [img]="BookmarkPlus" [size]="13"></lucide-icon> Journal
|
||||
<lucide-icon [img]="BookmarkPlus" [size]="13"></lucide-icon> {{ 'sessionRandomTablesPanel.journal' | translate }}
|
||||
</button>
|
||||
<button type="button" class="srt-act srt-act--ai" (click)="improvise()"
|
||||
[disabled]="!matched || improvising || !canAddToJournal">
|
||||
<lucide-icon [img]="Sparkles" [size]="13"></lucide-icon>
|
||||
{{ improvising ? '…' : 'IA improvise' }}
|
||||
{{ improvising ? ('sessionRandomTablesPanel.improvising' | translate) : ('sessionRandomTablesPanel.aiImprovise' | translate) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
|
||||
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
||||
import { LucideAngularModule, Dices, BookmarkPlus, Sparkles, ChevronLeft } from 'lucide-angular';
|
||||
import { catchError, of } from 'rxjs';
|
||||
import { RandomTableService } from '../../services/random-table.service';
|
||||
@@ -18,7 +19,7 @@ import { DiceRollResult } from '../session-dice-panel/session-dice-panel.compone
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-session-random-tables-panel',
|
||||
imports: [LucideAngularModule],
|
||||
imports: [LucideAngularModule, TranslatePipe],
|
||||
templateUrl: './session-random-tables-panel.component.html',
|
||||
styleUrls: ['./session-random-tables-panel.component.scss']
|
||||
})
|
||||
@@ -41,7 +42,7 @@ export class SessionRandomTablesPanelComponent implements OnInit {
|
||||
matched: RandomTableEntry | null = null;
|
||||
improvising = false;
|
||||
|
||||
constructor(private service: RandomTableService) {}
|
||||
constructor(private service: RandomTableService, private translate: TranslateService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.campaignId) return;
|
||||
@@ -79,7 +80,7 @@ export class SessionRandomTablesPanelComponent implements OnInit {
|
||||
/** Consigne le tirage au journal (entrée DICE_ROLL via la sortie `rolled`). */
|
||||
addToJournal(): void {
|
||||
if (!this.canAddToJournal || !this.selected || !this.lastRoll) return;
|
||||
const label = this.matched?.label ?? 'aucun résultat';
|
||||
const label = this.matched?.label ?? this.translate.instant('sessionRandomTablesPanel.noResult');
|
||||
const result: DiceRollResult = {
|
||||
notation: this.selected.diceFormula,
|
||||
rolls: this.lastRoll.rolls,
|
||||
|
||||
@@ -6,42 +6,42 @@
|
||||
[class.ref-tab--active]="activeTab === 'ai'"
|
||||
(click)="selectTab('ai')">
|
||||
<lucide-icon [img]="Sparkles" [size]="14"></lucide-icon>
|
||||
IA
|
||||
{{ 'sessionReferencePanel.tabAi' | translate }}
|
||||
</button>
|
||||
<button type="button"
|
||||
class="ref-tab"
|
||||
[class.ref-tab--active]="activeTab === 'dice'"
|
||||
(click)="selectTab('dice')">
|
||||
<lucide-icon [img]="Dices" [size]="14"></lucide-icon>
|
||||
Dés
|
||||
{{ 'sessionReferencePanel.tabDice' | translate }}
|
||||
</button>
|
||||
<button type="button"
|
||||
class="ref-tab"
|
||||
[class.ref-tab--active]="activeTab === 'tables'"
|
||||
(click)="selectTab('tables')">
|
||||
<lucide-icon [img]="Table2" [size]="14"></lucide-icon>
|
||||
Tables
|
||||
{{ 'sessionReferencePanel.tabTables' | translate }}
|
||||
</button>
|
||||
<button type="button"
|
||||
class="ref-tab"
|
||||
[class.ref-tab--active]="activeTab === 'objects'"
|
||||
(click)="selectTab('objects')">
|
||||
<lucide-icon [img]="Package" [size]="14"></lucide-icon>
|
||||
Objets
|
||||
{{ 'sessionReferencePanel.tabObjects' | translate }}
|
||||
</button>
|
||||
<button type="button"
|
||||
class="ref-tab"
|
||||
[class.ref-tab--active]="activeTab === 'characters'"
|
||||
(click)="selectTab('characters')">
|
||||
<lucide-icon [img]="User" [size]="14"></lucide-icon>
|
||||
PJ/PNJ
|
||||
{{ 'sessionReferencePanel.tabCharacters' | translate }}
|
||||
</button>
|
||||
<button type="button"
|
||||
class="ref-tab"
|
||||
[class.ref-tab--active]="activeTab === 'scenes'"
|
||||
(click)="selectTab('scenes')">
|
||||
<lucide-icon [img]="Swords" [size]="14"></lucide-icon>
|
||||
Scènes
|
||||
{{ 'sessionReferencePanel.tabScenes' | translate }}
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
@if (activeTab === 'characters') {
|
||||
<div class="ref-list">
|
||||
@if (loadingChars) {
|
||||
<p class="loading-hint">Chargement…</p>
|
||||
<p class="loading-hint">{{ 'common.loading' | translate }}</p>
|
||||
}
|
||||
@if (!loadingChars) {
|
||||
<div>
|
||||
@@ -95,7 +95,7 @@
|
||||
<div class="ref-group">
|
||||
<h4>
|
||||
<lucide-icon [img]="User" [size]="13"></lucide-icon>
|
||||
Personnages joueurs
|
||||
{{ 'sessionReferencePanel.playerCharacters' | translate }}
|
||||
</h4>
|
||||
@for (c of characters; track c) {
|
||||
<button
|
||||
@@ -112,7 +112,7 @@
|
||||
<div class="ref-group">
|
||||
<h4>
|
||||
<lucide-icon [img]="Drama" [size]="13"></lucide-icon>
|
||||
Personnages non-joueurs
|
||||
{{ 'sessionReferencePanel.nonPlayerCharacters' | translate }}
|
||||
</h4>
|
||||
@for (n of npcs; track n) {
|
||||
<button
|
||||
@@ -127,7 +127,7 @@
|
||||
}
|
||||
@if (characters.length === 0 && npcs.length === 0) {
|
||||
<p class="empty-hint">
|
||||
Aucun personnage dans cette campagne.
|
||||
{{ 'sessionReferencePanel.emptyCharacters' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
@@ -139,12 +139,12 @@
|
||||
@if (activeTab === 'scenes') {
|
||||
<div class="ref-list">
|
||||
@if (loadingTree) {
|
||||
<p class="loading-hint">Chargement…</p>
|
||||
<p class="loading-hint">{{ 'common.loading' | translate }}</p>
|
||||
}
|
||||
@if (!loadingTree && treeData) {
|
||||
@if (treeData.arcs.length === 0) {
|
||||
<p class="empty-hint">
|
||||
Aucun arc narratif. Construis le scénario de ta campagne pour le retrouver ici.
|
||||
{{ 'sessionReferencePanel.emptyScenes' | translate }}
|
||||
</p>
|
||||
}
|
||||
@for (arc of treeData.arcs; track arc) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { LucideAngularModule, User, Drama, Swords, Dices, ExternalLink, Sparkles, Table2, Package } from 'lucide-angular';
|
||||
import { catchError, of } from 'rxjs';
|
||||
import { CampaignService } from '../../services/campaign.service';
|
||||
@@ -31,7 +32,7 @@ type TabId = 'dice' | 'tables' | 'objects' | 'characters' | 'scenes' | 'ai';
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-session-reference-panel',
|
||||
imports: [LucideAngularModule, SessionDicePanelComponent, SessionAiChatPanelComponent, SessionRandomTablesPanelComponent, SessionItemCatalogsPanelComponent],
|
||||
imports: [LucideAngularModule, TranslatePipe, SessionDicePanelComponent, SessionAiChatPanelComponent, SessionRandomTablesPanelComponent, SessionItemCatalogsPanelComponent],
|
||||
templateUrl: './session-reference-panel.component.html',
|
||||
styleUrls: ['./session-reference-panel.component.scss']
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user