import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Router } from '@angular/router'; import { Subject, debounceTime, distinctUntilChanged } from 'rxjs'; import { ImageRecord, ImageService } from '../services/image.service'; import { TagService } from '../services/tag.service'; @Component({ selector: 'app-library', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `

Reactbin

{{ tag }}

{{ activeFilters.length ? 'No images match these filters.' : 'No images yet. Upload your first!' }}

{{ tag }}
`, styles: [` .library { max-width: 1200px; margin: 0 auto; padding: 24px 16px; } header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .upload-btn { padding: 8px 20px; background: #4a9eff; color: #000; border: none; border-radius: 6px; cursor: pointer; font-weight: 600; } .filter-bar { position: relative; margin-bottom: 24px; } .filter-bar input { width: 100%; padding: 10px; background: #1a1a1a; border: 1px solid #444; color: #e0e0e0; border-radius: 6px; } .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; } .chip { background: #333; padding: 3px 10px; border-radius: 12px; font-size: 0.85rem; display: flex; align-items: center; gap: 4px; } .chip.small { font-size: 0.75rem; padding: 2px 8px; } .chip button { background: none; border: none; color: #aaa; cursor: pointer; padding: 0; font-size: 1rem; } .suggestions { position: absolute; z-index: 10; background: #1a1a1a; border: 1px solid #444; list-style: none; width: 100%; max-height: 200px; overflow-y: auto; border-radius: 0 0 6px 6px; } .suggestions li { padding: 8px 12px; cursor: pointer; } .suggestions li:hover { background: #2a2a2a; } .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; } .image-card { cursor: pointer; background: #1a1a1a; border-radius: 8px; overflow: hidden; } .image-card img { width: 100%; height: 160px; object-fit: cover; display: block; } .tag-row { padding: 6px; display: flex; flex-wrap: wrap; gap: 4px; } .empty-state { text-align: center; padding: 60px 0; color: #666; } .load-more { display: block; margin: 24px auto; padding: 10px 32px; background: #2a2a2a; color: #e0e0e0; border: 1px solid #444; border-radius: 6px; cursor: pointer; } `], }) export class LibraryComponent implements OnInit { images: ImageRecord[] = []; activeFilters: string[] = []; tagSearch = ''; suggestions: { name: string; image_count: number }[] = []; loading = false; hasMore = false; private offset = 0; private readonly limit = 50; private readonly filterChange$ = new Subject(); constructor( public imageService: ImageService, private tagService: TagService, public router: Router, private cdr: ChangeDetectorRef, ) {} ngOnInit(): void { this.loadImages(); this.filterChange$.pipe(debounceTime(300), distinctUntilChanged()).subscribe((q) => { if (q) { this.tagService.list(q, 10).subscribe((r) => { this.suggestions = r.items; this.cdr.markForCheck(); }); } else { this.suggestions = []; this.cdr.markForCheck(); } }); } onTagInput(event: Event): void { const val = (event.target as HTMLInputElement).value; this.tagSearch = val; this.filterChange$.next(val); } addFilter(tag: string): void { if (!this.activeFilters.includes(tag)) { this.activeFilters = [...this.activeFilters, tag]; } this.tagSearch = ''; this.suggestions = []; this.applyFilter(this.activeFilters); } removeFilter(tag: string): void { this.activeFilters = this.activeFilters.filter((t) => t !== tag); this.applyFilter(this.activeFilters); } applyFilter(tags: string[]): void { this.activeFilters = tags; this.offset = 0; this.images = []; this.loadImages(); } loadImages(): void { this.loading = true; this.imageService.list(this.activeFilters, this.limit, this.offset).subscribe((res) => { this.images = [...this.images, ...res.items]; this.offset += res.items.length; this.hasMore = this.offset < res.total; this.loading = false; this.cdr.markForCheck(); }); } loadMore(): void { this.loadImages(); } }