feat(builder): implement reusable media library
Media dashboard (real total/images/SVG/logos/unused/storage/alt-coverage, computed from actual assets + a bootstrap usage scan, no fabricated stats); gallery gets grid/list toggle, type filter, sort, drag-and-drop + multi-file upload with cancel/retry and friendly error mapping, lazy thumbnails, multi-select with bulk delete/download/export-metadata; asset details drawer shows real dimensions/size/format/date/usage locations (walks bootstrap config for exact URL matches, reports not-used rather than guessing) plus editable alt text/caption/description/decorative flag with missing-alt warning; shared MediaPickerComponent (already the one reusable picker used by content management) gains type filter, a recent shortcut, and keyboard grid navigation.
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
(ngModelChange)="facade.setSearch($event)"
|
||||
[placeholder]="'mediaLibrary.searchPlaceholder' | translate"
|
||||
/>
|
||||
<app-select [options]="kindOptions()" [ngModel]="facade.kind() || 'all'" (ngModelChange)="setKind($event)" [ariaLabel]="'mediaLibrary.allTypes' | translate" [fullWidth]="false" />
|
||||
<app-button variant="ghost" size="sm" (click)="showRecentOnly()">{{ 'mediaLibrary.sortRecent' | translate }}</app-button>
|
||||
<input
|
||||
#fileInput
|
||||
type="file"
|
||||
@@ -39,11 +41,18 @@
|
||||
[description]="'mediaLibrary.emptyDescription' | translate"
|
||||
/>
|
||||
} @else {
|
||||
<div class="media-picker__grid">
|
||||
@for (asset of facade.items(); track asset.id) {
|
||||
<app-card padding="sm" [interactive]="true" (click)="pick(asset)">
|
||||
<div class="media-picker__grid" role="listbox" [attr.aria-label]="'mediaLibrary.title' | translate" tabindex="0" (keydown)="onGridKeydown($event)">
|
||||
@for (asset of facade.items(); track asset.id; let i = $index) {
|
||||
<app-card
|
||||
padding="sm"
|
||||
[interactive]="true"
|
||||
role="option"
|
||||
[attr.aria-selected]="i === focusedIndex()"
|
||||
[class.media-picker__card--focused]="i === focusedIndex()"
|
||||
(click)="pick(asset)"
|
||||
>
|
||||
@if (asset.mimeType.startsWith('image/')) {
|
||||
<img class="media-picker__preview" [src]="asset.url" [alt]="asset.filename" />
|
||||
<img class="media-picker__preview" [src]="asset.url" [alt]="asset.filename" loading="lazy" />
|
||||
} @else {
|
||||
<div class="media-picker__preview media-picker__preview--file">{{ asset.mimeType }}</div>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user