Mise en ligne de la version 0.2.0
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
<div class="node-create-page">
|
||||
|
||||
<div class="page-header">
|
||||
<h1>Créer un nouveau dossier</h1>
|
||||
<p class="subtitle">Les dossiers permettent d'organiser vos pages par catégorie</p>
|
||||
</div>
|
||||
|
||||
<form [formGroup]="form" (ngSubmit)="submit()" class="node-form">
|
||||
|
||||
<div class="field">
|
||||
<label>Nom du dossier *</label>
|
||||
<input
|
||||
type="text"
|
||||
formControlName="name"
|
||||
placeholder="Ex: Personnages, Créatures..."
|
||||
[class.invalid]="form.get('name')?.invalid && form.get('name')?.touched"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Dossier parent <span class="optional">(optionnel)</span></label>
|
||||
<select formControlName="parentId">
|
||||
<option value="">— Racine du Lore —</option>
|
||||
<option *ngFor="let parent of availableParents" [value]="parent.id">{{ parent.name }}</option>
|
||||
</select>
|
||||
<p class="hint">Laissez vide pour créer un dossier à la racine du lore</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Icône</label>
|
||||
<div class="icon-grid">
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn"
|
||||
*ngFor="let option of iconOptions"
|
||||
[class.selected]="selectedIcon === option.key"
|
||||
(click)="selectIcon(option.key)">
|
||||
<lucide-icon [img]="option.icon" [size]="18"></lucide-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Description <span class="optional">(optionnel)</span></label>
|
||||
<textarea
|
||||
formControlName="description"
|
||||
placeholder="Décrivez le type de contenu que ce dossier contiendra..."
|
||||
rows="4">
|
||||
</textarea>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Adresse</label>
|
||||
<input
|
||||
type="text"
|
||||
formControlName="address"
|
||||
placeholder="nom-du-dossier"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn-primary" [disabled]="form.invalid">
|
||||
<lucide-icon [img]="getIcon(selectedIcon)" [size]="16"></lucide-icon>
|
||||
Créer le dossier
|
||||
</button>
|
||||
<button type="button" class="btn-secondary" (click)="cancel()">Annuler</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,116 @@
|
||||
.node-create-page {
|
||||
padding: 2.5rem 2rem;
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 2.5rem;
|
||||
|
||||
h1 { font-size: 1.5rem; font-weight: 700; color: white; margin-bottom: 0.4rem; }
|
||||
.subtitle { color: #6b7280; font-size: 0.9rem; }
|
||||
}
|
||||
|
||||
.node-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
label {
|
||||
font-size: 0.875rem;
|
||||
color: #d1d5db;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.optional { color: #6b7280; font-weight: 400; }
|
||||
|
||||
input, textarea, select {
|
||||
background: #1f2937;
|
||||
border: 1px solid #374151;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
color: white;
|
||||
font-size: 0.9rem;
|
||||
outline: none;
|
||||
resize: none;
|
||||
transition: border-color 0.2s;
|
||||
|
||||
&::placeholder { color: #4b5563; }
|
||||
&:focus { border-color: #6c63ff; }
|
||||
&.invalid { border-color: #ef4444; }
|
||||
}
|
||||
}
|
||||
|
||||
.icon-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
background: #1f2937;
|
||||
border: 1px solid #374151;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover { background: #374151; color: white; }
|
||||
|
||||
&.selected {
|
||||
background: #1e1b4b;
|
||||
border-color: #6c63ff;
|
||||
color: #a5b4fc;
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: #6c63ff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:hover:not(:disabled) { background: #5b52e0; }
|
||||
&:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: #1f2937;
|
||||
color: #d1d5db;
|
||||
border: 1px solid #374151;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:hover { background: #374151; }
|
||||
}
|
||||
104
web/src/app/lore/lore-node-create/lore-node-create.component.ts
Normal file
104
web/src/app/lore/lore-node-create/lore-node-create.component.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { LucideAngularModule, LucideIconData } from 'lucide-angular';
|
||||
import { LoreService } from '../../services/lore.service';
|
||||
import { TemplateService } from '../../services/template.service';
|
||||
import { PageService } from '../../services/page.service';
|
||||
import { LayoutService } from '../../services/layout.service';
|
||||
import { LoreNode } from '../../services/lore.model';
|
||||
import { loadLoreSidebarData, buildLoreSidebarConfig } from '../lore-sidebar.helper';
|
||||
import { LORE_ICON_OPTIONS, IconOption, resolveIcon } from '../lore-icons';
|
||||
|
||||
@Component({
|
||||
selector: 'app-lore-node-create',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ReactiveFormsModule, LucideAngularModule],
|
||||
templateUrl: './lore-node-create.component.html',
|
||||
styleUrls: ['./lore-node-create.component.scss']
|
||||
})
|
||||
export class LoreNodeCreateComponent implements OnInit, OnDestroy {
|
||||
|
||||
readonly iconOptions: IconOption[] = LORE_ICON_OPTIONS;
|
||||
|
||||
form: FormGroup;
|
||||
loreId = '';
|
||||
/** parentId optionnel depuis la route — si présent, pré-remplit le champ. */
|
||||
preselectedParentId: string | null = null;
|
||||
/** Liste des dossiers existants pour le select "Dossier parent". */
|
||||
availableParents: LoreNode[] = [];
|
||||
selectedIcon = this.iconOptions[0].key;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private loreService: LoreService,
|
||||
private templateService: TemplateService,
|
||||
private pageService: PageService,
|
||||
private layoutService: LayoutService
|
||||
) {
|
||||
this.form = this.fb.group({
|
||||
name: ['', Validators.required],
|
||||
description: [''],
|
||||
address: ['', Validators.required],
|
||||
parentId: [''] // '' = racine
|
||||
});
|
||||
|
||||
// Auto-génère l'adresse depuis le nom
|
||||
this.form.get('name')!.valueChanges.subscribe(name => {
|
||||
const slug = (name as string).toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
|
||||
this.form.get('address')!.setValue(slug, { emitEvent: false });
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loreId = this.route.snapshot.paramMap.get('loreId')!;
|
||||
this.preselectedParentId = this.route.snapshot.paramMap.get('parentId');
|
||||
this.loadLayout();
|
||||
}
|
||||
|
||||
private loadLayout(): void {
|
||||
loadLoreSidebarData(this.loreId, this.loreService, this.templateService, this.pageService)
|
||||
.subscribe(data => {
|
||||
this.availableParents = data.nodes;
|
||||
if (this.preselectedParentId) {
|
||||
this.form.patchValue({ parentId: this.preselectedParentId });
|
||||
}
|
||||
this.layoutService.show(buildLoreSidebarConfig(data));
|
||||
});
|
||||
}
|
||||
|
||||
selectIcon(key: string): void {
|
||||
this.selectedIcon = key;
|
||||
}
|
||||
|
||||
getIcon(key: string): LucideIconData {
|
||||
return this.iconOptions.find(o => o.key === key)!.icon;
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
if (this.form.invalid) return;
|
||||
const raw = this.form.value;
|
||||
this.loreService.createLoreNode({
|
||||
name: raw.name,
|
||||
description: raw.description,
|
||||
address: raw.address,
|
||||
icon: this.selectedIcon,
|
||||
parentId: raw.parentId && raw.parentId !== '' ? raw.parentId : null,
|
||||
loreId: this.loreId
|
||||
}).subscribe({
|
||||
next: () => this.router.navigate(['/lore', this.loreId]),
|
||||
error: () => console.error('Erreur lors de la création du dossier')
|
||||
});
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.router.navigate(['/lore', this.loreId]);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.layoutService.hide();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user