generated from nhcarrigan/template
feat: support cover arts
This commit is contained in:
@@ -95,6 +95,26 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="coverArt">Album Art (max 500KB)</label>
|
||||
<input
|
||||
type="file"
|
||||
id="coverArt"
|
||||
name="coverArt"
|
||||
accept="image/*"
|
||||
(change)="onImageSelected($event, 'new')"
|
||||
>
|
||||
@if (newMusicImagePreview()) {
|
||||
<div class="image-preview">
|
||||
<img [src]="newMusicImagePreview()" alt="Album art preview">
|
||||
<button type="button" (click)="clearImage('new')" class="btn btn-danger btn-sm">Remove</button>
|
||||
</div>
|
||||
}
|
||||
@if (imageError()) {
|
||||
<span class="error-text">{{ imageError() }}</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Add Music</button>
|
||||
<button type="button" (click)="toggleAddForm()" class="btn btn-secondary">Cancel</button>
|
||||
@@ -170,6 +190,26 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-coverArt">Album Art (max 500KB)</label>
|
||||
<input
|
||||
type="file"
|
||||
id="edit-coverArt"
|
||||
name="coverArt"
|
||||
accept="image/*"
|
||||
(change)="onImageSelected($event, 'edit')"
|
||||
>
|
||||
@if (editMusicImagePreview()) {
|
||||
<div class="image-preview">
|
||||
<img [src]="editMusicImagePreview()" alt="Album art preview">
|
||||
<button type="button" (click)="clearImage('edit')" class="btn btn-danger btn-sm">Remove</button>
|
||||
</div>
|
||||
}
|
||||
@if (imageError()) {
|
||||
<span class="error-text">{{ imageError() }}</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
<button type="button" (click)="cancelEdit()" class="btn btn-secondary">Cancel</button>
|
||||
@@ -734,6 +774,39 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment
|
||||
color: var(--witch-mauve);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.image-preview {
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.image-preview img {
|
||||
max-width: 100px;
|
||||
max-height: 100px;
|
||||
border-radius: 4px;
|
||||
border: 2px solid var(--witch-lavender);
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: var(--witch-rose);
|
||||
font-size: 0.875rem;
|
||||
display: block;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
padding: 0.5rem;
|
||||
border: 2px dashed var(--witch-lavender);
|
||||
border-radius: 4px;
|
||||
background: var(--witch-moon);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="file"]:hover {
|
||||
border-color: var(--witch-rose);
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class MusicListComponent implements OnInit {
|
||||
@@ -754,6 +827,12 @@ export class MusicListComponent implements OnInit {
|
||||
expandedComments = signal<Record<string, boolean>>({});
|
||||
newCommentContent: Record<string, string> = {};
|
||||
|
||||
// Image upload state
|
||||
newMusicImagePreview = signal<string | null>(null);
|
||||
editMusicImagePreview = signal<string | null>(null);
|
||||
imageError = signal<string | null>(null);
|
||||
private readonly MAX_IMAGE_SIZE = 500 * 1024; // 500KB
|
||||
|
||||
// Expose enums to template
|
||||
MusicType = MusicType;
|
||||
MusicStatus = MusicStatus;
|
||||
@@ -850,8 +929,11 @@ export class MusicListComponent implements OnInit {
|
||||
type: MusicType.album,
|
||||
status: MusicStatus.wantToListen,
|
||||
rating: undefined,
|
||||
notes: ''
|
||||
notes: '',
|
||||
coverArt: undefined
|
||||
};
|
||||
this.newMusicImagePreview.set(null);
|
||||
this.imageError.set(null);
|
||||
}
|
||||
|
||||
addMusic() {
|
||||
@@ -863,7 +945,8 @@ export class MusicListComponent implements OnInit {
|
||||
type: this.newMusic.type,
|
||||
status: this.newMusic.status,
|
||||
rating: this.newMusic.rating,
|
||||
notes: this.newMusic.notes
|
||||
notes: this.newMusic.notes,
|
||||
coverArt: this.newMusic.coverArt
|
||||
};
|
||||
|
||||
this.musicService.createMusic(musicToAdd).subscribe(() => {
|
||||
@@ -888,14 +971,19 @@ export class MusicListComponent implements OnInit {
|
||||
type: music.type,
|
||||
status: music.status,
|
||||
rating: music.rating,
|
||||
notes: music.notes
|
||||
notes: music.notes,
|
||||
coverArt: music.coverArt
|
||||
};
|
||||
this.editMusicImagePreview.set(music.coverArt || null);
|
||||
this.showAddForm.set(false);
|
||||
this.imageError.set(null);
|
||||
}
|
||||
|
||||
cancelEdit() {
|
||||
this.editingMusic.set(null);
|
||||
this.editMusicData = {};
|
||||
this.editMusicImagePreview.set(null);
|
||||
this.imageError.set(null);
|
||||
}
|
||||
|
||||
saveEdit() {
|
||||
@@ -912,6 +1000,52 @@ export class MusicListComponent implements OnInit {
|
||||
return new Date(date).toLocaleDateString();
|
||||
}
|
||||
|
||||
// Image handling methods
|
||||
onImageSelected(event: Event, target: 'new' | 'edit') {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const file = input.files?.[0];
|
||||
|
||||
if (!file) return;
|
||||
|
||||
this.imageError.set(null);
|
||||
|
||||
if (file.size > this.MAX_IMAGE_SIZE) {
|
||||
this.imageError.set(`Image too large. Maximum size is ${this.MAX_IMAGE_SIZE / 1024}KB.`);
|
||||
input.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.type.startsWith('image/')) {
|
||||
this.imageError.set('Please select an image file.');
|
||||
input.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const base64 = reader.result as string;
|
||||
if (target === 'new') {
|
||||
this.newMusicImagePreview.set(base64);
|
||||
this.newMusic.coverArt = base64;
|
||||
} else {
|
||||
this.editMusicImagePreview.set(base64);
|
||||
this.editMusicData.coverArt = base64;
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
clearImage(target: 'new' | 'edit') {
|
||||
if (target === 'new') {
|
||||
this.newMusicImagePreview.set(null);
|
||||
this.newMusic.coverArt = undefined;
|
||||
} else {
|
||||
this.editMusicImagePreview.set(null);
|
||||
this.editMusicData.coverArt = undefined;
|
||||
}
|
||||
this.imageError.set(null);
|
||||
}
|
||||
|
||||
// Comments methods
|
||||
toggleComments(musicId: string) {
|
||||
const expanded = this.expandedComments();
|
||||
|
||||
Reference in New Issue
Block a user