/** * @copyright 2026 NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan, Hikari */ import { Component, Input, Output, EventEmitter, OnInit, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { Art, CreateArtDto, UpdateArtDto, Link } from '@library/shared-types'; @Component({ selector: 'app-art-form', standalone: true, imports: [CommonModule, FormsModule], template: `

{{ mode === 'add' ? 'Add New Art' : 'Edit Art' }}

@if (imagePreview()) {
Artwork preview
} @if (imageError()) { {{ imageError() }} }
@for (tag of formData.tags; track tag; let i = $index) { {{ tag }} }
`, styles: [` .art-form { background: white; padding: 1.5rem; border-radius: 8px; border: 1px solid #e5e7eb; margin-bottom: 2rem; } .art-form h3 { margin: 0 0 1.5rem 0; color: #1f2937; font-size: 1.5rem; } .form-group { margin-bottom: 1rem; } .form-group label { display: block; margin-bottom: 0.5rem; font-weight: 600; color: #374151; } .form-group input[type="text"], .form-group input[type="url"], .form-group textarea { width: 100%; padding: 0.625rem; border: 1px solid #d1d5db; border-radius: 4px; font-size: 1rem; font-family: inherit; } .form-group input:focus, .form-group textarea:focus { outline: none; border-color: #ff6b6b; } .tags-input-container { display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0.625rem; border: 1px solid #d1d5db; border-radius: 4px; min-height: 44px; } .tags-input-container input { flex: 1; border: none; outline: none; font-size: 1rem; min-width: 150px; } .tag { background: #ff6b6b; color: white; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.875rem; display: flex; align-items: center; gap: 0.5rem; } .tag-remove { background: none; border: none; color: white; cursor: pointer; font-size: 1.25rem; line-height: 1; padding: 0; } .links-list { margin-bottom: 0.75rem; } .link-item { display: flex; justify-content: space-between; align-items: center; padding: 0.5rem; background: #f3f4f6; border-radius: 4px; margin-bottom: 0.5rem; } .link-add-form { display: grid; grid-template-columns: 1fr 2fr auto; gap: 0.5rem; } .image-preview { margin-top: 0.75rem; display: flex; align-items: center; gap: 1rem; } .image-preview img { max-width: 200px; max-height: 200px; border-radius: 4px; border: 1px solid #e5e7eb; } .error-text { color: #dc2626; font-size: 0.875rem; display: block; margin-top: 0.5rem; } .form-actions { display: flex; gap: 0.75rem; margin-top: 1.5rem; } .btn { padding: 0.625rem 1.25rem; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; transition: all 0.3s; font-weight: 500; } .btn:hover { opacity: 0.9; } .btn-primary { background: #ff6b6b; color: white; } .btn-secondary { background: #6b7280; color: white; } .btn-danger { background: #ef4444; color: white; } .btn-sm { padding: 0.375rem 0.75rem; font-size: 0.875rem; } `] }) export class ArtFormComponent implements OnInit { @Input() mode: 'add' | 'edit' = 'add'; @Input() art?: Art; @Input() initialData?: Partial; @Output() formSubmit = new EventEmitter(); @Output() formCancel = new EventEmitter(); formData: Partial = { tags: [], links: [] }; tagInput = ''; linkTitle = ''; linkUrl = ''; imagePreview = signal(null); imageError = signal(null); ngOnInit() { if (this.mode === 'edit' && this.art) { this.formData = { title: this.art.title, artist: this.art.artist, description: this.art.description, imageUrl: this.art.imageUrl, tags: [...(this.art.tags || [])], links: [...(this.art.links || [])] }; this.imagePreview.set(this.art.imageUrl || null); } else { this.formData = { tags: [], links: [], ...this.initialData }; if (this.initialData?.imageUrl) { this.imagePreview.set(this.initialData.imageUrl); } } } addTag() { if (this.tagInput.trim() && !this.formData.tags?.includes(this.tagInput.trim())) { this.formData.tags = [...(this.formData.tags || []), this.tagInput.trim()]; this.tagInput = ''; } } removeTag(index: number) { this.formData.tags = this.formData.tags?.filter((_, i) => i !== index) || []; } addLink() { if (this.linkTitle.trim() && this.linkUrl.trim()) { const newLink: Link = { title: this.linkTitle.trim(), url: this.linkUrl.trim() }; this.formData.links = [...(this.formData.links || []), newLink]; this.linkTitle = ''; this.linkUrl = ''; } } removeLink(index: number) { this.formData.links = this.formData.links?.filter((_, i) => i !== index) || []; } onImageSelected(event: Event) { const input = event.target as HTMLInputElement; const file = input.files?.[0]; if (!file) { return; } if (file.size > 500000) { this.imageError.set('Image must be under 500KB'); input.value = ''; return; } this.imageError.set(null); const reader = new FileReader(); reader.onload = () => { const base64String = reader.result as string; this.formData.imageUrl = base64String; this.imagePreview.set(base64String); }; reader.readAsDataURL(file); } clearImage() { this.formData.imageUrl = undefined; this.imagePreview.set(null); } onSubmit() { if (!this.formData.title || !this.formData.artist || !this.formData.imageUrl) { return; } const data: CreateArtDto | UpdateArtDto = { ...this.formData as CreateArtDto | UpdateArtDto }; this.formSubmit.emit(data); } onCancel() { this.formCancel.emit(); } }