diff --git a/src/app/shared/media/media-picker/media-picker.component.html b/src/app/shared/media/media-picker/media-picker.component.html new file mode 100644 index 0000000..0815d7b --- /dev/null +++ b/src/app/shared/media/media-picker/media-picker.component.html @@ -0,0 +1,56 @@ + +
+
+ + + + {{ (facade.uploading() ? 'mediaLibrary.uploading' : 'mediaLibrary.upload') | translate }} + +
+ + @if (facade.loading()) { +
+ @for (i of [1, 2, 3, 4]; track i) { + + } +
+ } @else if (facade.items().length === 0) { + + } @else { +
+ @for (asset of facade.items(); track asset.id) { + + @if (asset.mimeType.startsWith('image/')) { + + } @else { +
{{ asset.mimeType }}
+ } + {{ asset.filename }} +
+ } +
+ + @if (totalPages() > 1) { + + } + } +
+
diff --git a/src/app/shared/media/media-picker/media-picker.component.scss b/src/app/shared/media/media-picker/media-picker.component.scss new file mode 100644 index 0000000..a7b4d3e --- /dev/null +++ b/src/app/shared/media/media-picker/media-picker.component.scss @@ -0,0 +1,44 @@ +.media-picker { + display: grid; + gap: 14px; +} + +.media-picker__toolbar { + display: flex; + align-items: center; + gap: 8px; +} + +.media-picker__grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 10px; +} + +.media-picker__preview { + width: 100%; + height: 90px; + object-fit: cover; + border-radius: var(--radius-md, 6px); + background: var(--bg-secondary, #f4f4f5); + margin-bottom: 6px; +} + +.media-picker__preview--file { + display: flex; + align-items: center; + justify-content: center; + font-size: 0.6875rem; + color: var(--text-secondary, #6b7280); + text-align: center; + padding: 6px; +} + +.media-picker__filename { + display: block; + font-size: 0.75rem; + color: var(--text-primary, #1f322d); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/src/app/shared/media/media-picker/media-picker.component.ts b/src/app/shared/media/media-picker/media-picker.component.ts new file mode 100644 index 0000000..3d647e4 --- /dev/null +++ b/src/app/shared/media/media-picker/media-picker.component.ts @@ -0,0 +1,72 @@ +import { ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild, inject, input, output } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { TranslatePipe } from '../../../i18n/translate.pipe'; +import { MediaLibraryFacade } from '../../../features/backoffice/media/facade/media-library.facade'; +import { MediaAsset } from '../../../core/media/models/media-asset.model'; +import { ButtonComponent } from '../../ui/button/button.component'; +import { InputComponent } from '../../ui/input/input.component'; +import { CardComponent } from '../../ui/card/card.component'; +import { EmptyStateComponent } from '../../ui/empty-state/empty-state.component'; +import { DialogComponent } from '../../ui/dialog/dialog.component'; +import { PaginationComponent } from '../../ui/pagination/pagination.component'; +import { SkeletonComponent } from '../../ui/skeleton/skeleton.component'; + +const PAGE_SIZE = 24; + +@Component({ + selector: 'app-media-picker', + standalone: true, + imports: [ + FormsModule, + TranslatePipe, + ButtonComponent, + InputComponent, + CardComponent, + EmptyStateComponent, + DialogComponent, + PaginationComponent, + SkeletonComponent, + ], + templateUrl: './media-picker.component.html', + styleUrl: './media-picker.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MediaPickerComponent implements OnInit { + readonly open = input(false); + + readonly selected = output(); + readonly closed = output(); + + protected readonly facade = inject(MediaLibraryFacade); + + @ViewChild('fileInput') private fileInput?: ElementRef; + + protected readonly totalPages = () => Math.max(1, Math.ceil(this.facade.total() / PAGE_SIZE)); + + ngOnInit(): void { + void this.facade.load(); + } + + protected triggerFileInput(): void { + this.fileInput?.nativeElement.click(); + } + + protected async onFileSelected(event: Event): Promise { + const input = event.target as HTMLInputElement; + const file = input.files?.[0]; + input.value = ''; + if (!file) { + return; + } + await this.facade.upload(file); + } + + protected pick(asset: MediaAsset): void { + this.selected.emit(asset); + this.closed.emit(); + } + + protected requestClose(): void { + this.closed.emit(); + } +}