Some checks failed
Qualité & Sécurité / MegaLinter (PMD · Ruff · Bandit · gitleaks · hadolint) (push) Successful in 1m9s
Build & Push Images / build (brain) (push) Successful in 1m10s
Qualité & Sécurité / Trivy (CVE dépendances Maven / pip / npm) (push) Failing after 2m36s
Build & Push Images / build (web) (push) Successful in 1m48s
Build & Push Images / build-switcher (push) Successful in 20s
Build & Push Images / build (core) (push) Successful in 3m9s
Qualité & Sécurité / Web (ESLint · angular-eslint + sonarjs) (push) Successful in 38s
CI (Gitea Actions) : - Nouveau workflow quality.yml (séparé de ci.yml, non bloquant pour la release) : MegaLinter v9 (PMD, Ruff, Bandit, gitleaks, hadolint), ng lint (web) et Trivy (CVE des dépendances Maven/pip/npm), sur main + beta + PR - Trivy : préchargement du cache Maven avant le scan — sans ça Maven Central rate-limite l'IP du runner (429) en résolvant les BOMs du parent Spring Boot - upload-artifact v4 → v3 : l'API artifacts v4 n'est pas supportée par Gitea (corrige aussi l'upload du rapport Playwright de e2e.yml, cassé depuis toujours) - Ruleset PMD projet (java-pmd-ruleset.xml, auto-détecté par MegaLinter) orienté bugs réels, sans le style Lombok-hostile du défaut : 11 337 → 65 findings ; PMD en mode rapport (JAVA_PMD_DISABLE_ERRORS) le temps de purger ce backlog Web : - angular-eslint 21 + eslint-plugin-sonarjs : les règles Sonar vivent désormais dans ESLint (config web/eslint.config.js, réglages justifiés en commentaire) - tsconfig : skipLibCheck + types:[] + node_modules ré-exclu — répare les builds desktop Windows/Linux cassés par le conflit @types/eslint-scope vs ESLint 9 - Purge du backlog lint : 114 erreurs → 0 sur 66 fichiers (ngOnDestroy vides supprimés, any typés avec les vrais DTOs, outputs (close) → (closed), regex super-linéaires désamorcées, ternaires/fonctions imbriqués dépliés, intention documentée sur les catch/error volontairement vides) - Bug latent corrigé au passage : license.service.disconnect() émettait undefined au lieu de true en cas de succès (masqué par un double cast)
188 lines
7.1 KiB
HTML
188 lines
7.1 KiB
HTML
<div class="edit-page">
|
|
|
|
<div class="page-header">
|
|
<div>
|
|
<h1>{{ arc?.name || ('arcEdit.fallbackTitle' | translate) }}</h1>
|
|
<p class="subtitle">{{ 'arcEdit.subtitle' | translate }}</p>
|
|
</div>
|
|
<div class="header-actions">
|
|
<button type="button" class="btn-ai"
|
|
(click)="toggleChat()"
|
|
[class.active]="chatOpen"
|
|
[title]="'arcEdit.aiAssistantTitle' | translate">
|
|
<lucide-icon [img]="Sparkles" [size]="14"></lucide-icon>
|
|
{{ 'arcEdit.aiAssistant' | translate }}
|
|
</button>
|
|
<button type="button" class="btn-secondary" (click)="cancel()">{{ 'common.cancel' | translate }}</button>
|
|
<button type="button" class="btn-danger" (click)="delete()">
|
|
<lucide-icon [img]="Trash2" [size]="14"></lucide-icon>
|
|
{{ 'common.delete' | translate }}
|
|
</button>
|
|
<button type="button" class="btn-primary" (click)="submit()" [disabled]="form.invalid">
|
|
{{ 'common.save' | translate }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<form [formGroup]="form" (ngSubmit)="submit()" class="edit-form">
|
|
|
|
<!-- Tout est optionnel sauf le titre : identité visible, le reste replié. -->
|
|
<p class="optional-hint">{{ 'arcEdit.optionalHint' | translate }}</p>
|
|
|
|
<!-- Co-création (Pilier A) : le co-MJ propose, validation champ par champ → remplit le formulaire. -->
|
|
<app-entity-assist-panel
|
|
[entityType]="'arc'" [entityId]="arcId" [campaignId]="campaignId"
|
|
(applied)="onAssistApplied($event)"></app-entity-assist-panel>
|
|
|
|
<!-- Identité -->
|
|
<div class="field">
|
|
<label for="arc-edit-name">{{ 'arcEdit.nameLabel' | translate }}</label>
|
|
<input
|
|
id="arc-edit-name"
|
|
type="text"
|
|
formControlName="name"
|
|
[placeholder]="'arcEdit.namePlaceholder' | translate"
|
|
[class.invalid]="form.get('name')?.invalid && form.get('name')?.touched"
|
|
/>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="arc-edit-description">{{ 'arcEdit.synopsisLabel' | translate }}</label>
|
|
<textarea
|
|
id="arc-edit-description"
|
|
formControlName="description"
|
|
[placeholder]="'arcEdit.synopsisPlaceholder' | translate"
|
|
rows="5">
|
|
</textarea>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>{{ 'arcEdit.structureLabel' | translate }}</label>
|
|
<div class="arc-type-choice">
|
|
<label class="arc-type-option" [class.selected]="form.get('type')?.value === 'LINEAR'">
|
|
<input type="radio" formControlName="type" value="LINEAR" />
|
|
<span class="arc-type-title">{{ 'arcEdit.linearTitle' | translate }}</span>
|
|
<span class="arc-type-desc">{{ 'arcEdit.linearDesc' | translate }}</span>
|
|
</label>
|
|
<label class="arc-type-option" [class.selected]="form.get('type')?.value === 'HUB'">
|
|
<input type="radio" formControlName="type" value="HUB" />
|
|
<span class="arc-type-title">{{ 'arcEdit.hubTitle' | translate }}</span>
|
|
<span class="arc-type-desc">{{ 'arcEdit.hubDesc' | translate }}</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>{{ 'arcEdit.iconLabel' | translate }}</label>
|
|
<app-icon-picker [options]="campaignIconOptions" [(selected)]="selectedIcon"></app-icon-picker>
|
|
</div>
|
|
|
|
<!-- Illustrations (galerie editable, rendu editorial) -->
|
|
<app-expandable-section [title]="'arcEdit.illustrationsLabel' | translate" icon="🖼️" [filled]="illustrationsFilled">
|
|
<app-image-gallery
|
|
[imageIds]="illustrationImageIds"
|
|
[editable]="true"
|
|
[layout]="'EDITORIAL'"
|
|
(imageIdsChange)="illustrationImageIds = $event">
|
|
</app-image-gallery>
|
|
<small class="field-hint">{{ 'arcEdit.illustrationsHint' | translate }}</small>
|
|
</app-expandable-section>
|
|
|
|
<!-- Thèmes et enjeux -->
|
|
<app-expandable-section [title]="'arcEdit.themesStakesSectionTitle' | translate" icon="🎭" [filled]="themesStakesFilled">
|
|
<div class="field-row">
|
|
<div class="field">
|
|
<label for="arc-edit-themes">{{ 'arcEdit.themesLabel' | translate }}</label>
|
|
<textarea
|
|
id="arc-edit-themes"
|
|
formControlName="themes"
|
|
[placeholder]="'arcEdit.themesPlaceholder' | translate"
|
|
rows="4">
|
|
</textarea>
|
|
</div>
|
|
<div class="field">
|
|
<label for="arc-edit-stakes">{{ 'arcEdit.stakesLabel' | translate }}</label>
|
|
<textarea
|
|
id="arc-edit-stakes"
|
|
formControlName="stakes"
|
|
[placeholder]="'arcEdit.stakesPlaceholder' | translate"
|
|
rows="4">
|
|
</textarea>
|
|
</div>
|
|
</div>
|
|
</app-expandable-section>
|
|
|
|
<!-- Notes du MJ (privé) -->
|
|
<app-expandable-section [title]="'arcEdit.gmNotesSectionTitle' | translate" icon="🔒" variant="private" [filled]="gmNotesFilled">
|
|
<div class="field">
|
|
<label for="arc-edit-gm-notes">{{ 'arcEdit.gmNotesLabel' | translate }}</label>
|
|
<textarea
|
|
id="arc-edit-gm-notes"
|
|
formControlName="gmNotes"
|
|
[placeholder]="'arcEdit.gmNotesPlaceholder' | translate"
|
|
rows="5">
|
|
</textarea>
|
|
<small class="field-hint">{{ 'arcEdit.gmNotesHint' | translate }}</small>
|
|
</div>
|
|
</app-expandable-section>
|
|
|
|
<!-- Récompenses et dénouement -->
|
|
<app-expandable-section [title]="'arcEdit.rewardsResolutionSectionTitle' | translate" icon="🎁" [filled]="rewardsResolutionFilled">
|
|
<div class="field">
|
|
<label for="arc-edit-rewards">{{ 'arcEdit.rewardsLabel' | translate }}</label>
|
|
<textarea
|
|
id="arc-edit-rewards"
|
|
formControlName="rewards"
|
|
[placeholder]="'arcEdit.rewardsPlaceholder' | translate"
|
|
rows="4">
|
|
</textarea>
|
|
</div>
|
|
<div class="field">
|
|
<label for="arc-edit-resolution">{{ 'arcEdit.resolutionLabel' | translate }}</label>
|
|
<textarea
|
|
id="arc-edit-resolution"
|
|
formControlName="resolution"
|
|
[placeholder]="'arcEdit.resolutionPlaceholder' | translate"
|
|
rows="4">
|
|
</textarea>
|
|
</div>
|
|
</app-expandable-section>
|
|
|
|
<!-- ===== Pages Lore associées (phase B2 cross-context) ===== -->
|
|
@if (loreId) {
|
|
<app-expandable-section [title]="'arcEdit.relatedPagesLabel' | translate" icon="🔗" [filled]="loreFilled">
|
|
<app-lore-link-picker
|
|
[value]="relatedPageIds"
|
|
[availablePages]="availablePages"
|
|
[loreId]="loreId"
|
|
(valueChange)="relatedPageIds = $event">
|
|
</app-lore-link-picker>
|
|
<small class="field-hint">
|
|
{{ 'arcEdit.relatedPagesHint' | translate }}
|
|
</small>
|
|
</app-expandable-section>
|
|
}
|
|
|
|
@if (!loreId) {
|
|
<div class="field lore-hint">
|
|
<small class="field-hint">
|
|
{{ 'arcEdit.noLoreHint' | translate }}
|
|
</small>
|
|
</div>
|
|
}
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
<!-- Drawer chat IA (hors .edit-page pour couvrir le viewport à droite) -->
|
|
<app-ai-chat-drawer
|
|
[campaignId]="campaignId"
|
|
entityType="arc"
|
|
[entityId]="arcId"
|
|
[isOpen]="chatOpen"
|
|
[welcomeMessage]="'arcEdit.chatWelcome' | translate"
|
|
[quickSuggestions]="chatQuickSuggestions"
|
|
(closed)="chatOpen = false">
|
|
</app-ai-chat-drawer>
|