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:
@@ -1152,6 +1152,71 @@ export const en: Translations = {
|
||||
confirm: 'Delete',
|
||||
emptyTitle: 'No files yet',
|
||||
emptyDescription: 'Upload an image or document to get started.',
|
||||
allFolders: 'All folders',
|
||||
newFolder: 'New folder',
|
||||
editTags: 'Edit tags',
|
||||
totalAssets: 'Total assets',
|
||||
logos: 'Logos',
|
||||
unused: 'Unused',
|
||||
storageUsed: 'Storage used',
|
||||
altCoverage: 'Alt text coverage',
|
||||
allTypes: 'All types',
|
||||
typeImage: 'Images',
|
||||
typeSvg: 'SVG',
|
||||
typePdf: 'PDF',
|
||||
sortRecent: 'Recent',
|
||||
sortName: 'Name',
|
||||
sortSize: 'Size',
|
||||
viewMode: 'View',
|
||||
viewGrid: 'Grid',
|
||||
viewList: 'List',
|
||||
dropHint: 'Drag and drop images here, or use Upload above.',
|
||||
cancelUpload: 'Cancel',
|
||||
retry: 'Retry',
|
||||
errorTooLarge: 'That file is too large. Try a smaller file.',
|
||||
errorUnsupportedType: 'That file type is not supported. Use JPG, PNG, WebP, GIF, SVG, or PDF.',
|
||||
errorGeneric: 'Upload failed. Please try again.',
|
||||
selectedCount: 'selected',
|
||||
download: 'Download',
|
||||
exportMetadata: 'Export metadata',
|
||||
bulkDelete: 'Delete selected',
|
||||
bulkDeleteConfirm: 'Delete the selected files? This cannot be undone.',
|
||||
clearSelection: 'Clear selection',
|
||||
guideCopy: 'Reusing an existing asset avoids duplicate uploads and keeps your storage tidy. For best results upload images around 1200×1200px for hero/banner use and compress large photos before uploading.',
|
||||
missingAlt: 'Missing alt text',
|
||||
usageCount: 'uses',
|
||||
detailsTitle: 'Asset details',
|
||||
filename: 'Filename',
|
||||
format: 'Format',
|
||||
fileSize: 'File size',
|
||||
dimensions: 'Dimensions',
|
||||
uploadDate: 'Uploaded',
|
||||
lastUsed: 'Last used',
|
||||
usageCountLabel: 'Usage count',
|
||||
usedIn: 'Used in',
|
||||
notUsed: 'Not currently used anywhere.',
|
||||
unknownValue: 'Unknown',
|
||||
altText: 'Alt text',
|
||||
altTextHelp: 'Describes the image for screen readers and search engines.',
|
||||
decorative: 'Decorative (no alt text needed)',
|
||||
missingAltWarning: 'This image has no alt text and is not marked decorative — screen reader users won’t know what it shows.',
|
||||
caption: 'Caption',
|
||||
description: 'Description',
|
||||
usage: {
|
||||
brand: 'Logo',
|
||||
theme: 'Theme',
|
||||
header: 'Header',
|
||||
footer: 'Footer',
|
||||
staticPage: 'Static page',
|
||||
homepage: 'Homepage',
|
||||
catalog: 'Catalog',
|
||||
productPage: 'Product page',
|
||||
widget: 'Widget',
|
||||
company: 'Company info',
|
||||
seo: 'SEO',
|
||||
navigation: 'Navigation',
|
||||
other: 'Other',
|
||||
},
|
||||
},
|
||||
adminCategories: {
|
||||
chooseImage: 'Choose image',
|
||||
|
||||
@@ -1147,6 +1147,71 @@ export const hy: Translations = {
|
||||
confirm: 'Ջնջել',
|
||||
emptyTitle: 'Ֆայլեր դեռ չկան',
|
||||
emptyDescription: 'Վերբեռնեք պատկեր կամ փաստաթուղթ սկսելու համար։',
|
||||
allFolders: 'Բոլոր պանակները',
|
||||
newFolder: 'Նոր պանակ',
|
||||
editTags: 'Խմբագրել պիտակները',
|
||||
totalAssets: 'Ընդհանուր ֆայլեր',
|
||||
logos: 'Լոգոներ',
|
||||
unused: 'Չօգտագործվող',
|
||||
storageUsed: 'Օգտագործված տարածք',
|
||||
altCoverage: 'Alt տեքստի ծածկույթ',
|
||||
allTypes: 'Բոլոր տեսակները',
|
||||
typeImage: 'Պատկերներ',
|
||||
typeSvg: 'SVG',
|
||||
typePdf: 'PDF',
|
||||
sortRecent: 'Վերջին',
|
||||
sortName: 'Անուն',
|
||||
sortSize: 'Չափ',
|
||||
viewMode: 'Տեսք',
|
||||
viewGrid: 'Ցանց',
|
||||
viewList: 'Ցուցակ',
|
||||
dropHint: 'Քաշեք և գցեք պատկերները այստեղ, կամ օգտագործեք վերևի «Վերբեռնել» կոճակը։',
|
||||
cancelUpload: 'Չեղարկել',
|
||||
retry: 'Կրկնել',
|
||||
errorTooLarge: 'Ֆայլը չափազանց մեծ է։ Փորձեք ավելի փոքր ֆայլ։',
|
||||
errorUnsupportedType: 'Այս ֆայլի տեսակը չի աջակցվում։ Օգտագործեք JPG, PNG, WebP, GIF, SVG կամ PDF։',
|
||||
errorGeneric: 'Վերբեռնումը ձախողվեց։ Փորձեք կրկին։',
|
||||
selectedCount: 'ընտրված է',
|
||||
download: 'Ներբեռնել',
|
||||
exportMetadata: 'Արտահանել մետատվյալները',
|
||||
bulkDelete: 'Ջնջել ընտրվածները',
|
||||
bulkDeleteConfirm: 'Ջնջե՞լ ընտրված ֆայլերը։ Հնարավոր չէ հետարկել։',
|
||||
clearSelection: 'Չեղարկել ընտրությունը',
|
||||
guideCopy: 'Առկա ֆայլի կրկնակի օգտագործումը խուսափում է կրկնօրինակումներից և խնայում է տարածք։ Հերոս/բաններ նկարների համար խորհուրդ ենք տալիս մոտ 1200×1200px չափս և սեղմել մեծ լուսանկարները վերբեռնելուց առաջ։',
|
||||
missingAlt: 'Alt տեքստը բացակայում է',
|
||||
usageCount: 'օգտ.',
|
||||
detailsTitle: 'Ֆայլի մանրամասներ',
|
||||
filename: 'Ֆայլի անուն',
|
||||
format: 'Ձևաչափ',
|
||||
fileSize: 'Ֆայլի չափ',
|
||||
dimensions: 'Չափսեր',
|
||||
uploadDate: 'Վերբեռնված է',
|
||||
lastUsed: 'Վերջին օգտագործում',
|
||||
usageCountLabel: 'Օգտագործումների քանակ',
|
||||
usedIn: 'Օգտագործվում է',
|
||||
notUsed: 'Այս պահին ոչ մի տեղ չի օգտագործվում։',
|
||||
unknownValue: 'Անհայտ',
|
||||
altText: 'Alt տեքստ',
|
||||
altTextHelp: 'Նկարագրում է պատկերը էկրանընթերցողների և որոնողական համակարգերի համար։',
|
||||
decorative: 'Դեկորատիվ (alt տեքստ պետք չէ)',
|
||||
missingAltWarning: 'Այս պատկերը չունի alt տեքստ և նշված չէ որպես դեկորատիվ․ էկրանընթերցող օգտագործողները չեն իմանա, թե ինչ է պատկերված։',
|
||||
caption: 'Ենթագիր',
|
||||
description: 'Նկարագրություն',
|
||||
usage: {
|
||||
brand: 'Լոգո',
|
||||
theme: 'Թեմա',
|
||||
header: 'Վերնագիր',
|
||||
footer: 'Ստորագիր',
|
||||
staticPage: 'Ստատիկ էջ',
|
||||
homepage: 'Գլխավոր էջ',
|
||||
catalog: 'Կատալոգ',
|
||||
productPage: 'Ապրանքի էջ',
|
||||
widget: 'Վիջեթ',
|
||||
company: 'Ընկերության տվյալներ',
|
||||
seo: 'SEO',
|
||||
navigation: 'Նավիգացիա',
|
||||
other: 'Այլ',
|
||||
},
|
||||
},
|
||||
adminCategories: {
|
||||
chooseImage: 'Ընտրել պատկեր',
|
||||
|
||||
@@ -1147,6 +1147,71 @@ export const ru: Translations = {
|
||||
confirm: 'Удалить',
|
||||
emptyTitle: 'Пока нет файлов',
|
||||
emptyDescription: 'Загрузите изображение или документ, чтобы начать.',
|
||||
allFolders: 'Все папки',
|
||||
newFolder: 'Новая папка',
|
||||
editTags: 'Изменить теги',
|
||||
totalAssets: 'Всего файлов',
|
||||
logos: 'Логотипы',
|
||||
unused: 'Неиспользуемые',
|
||||
storageUsed: 'Использовано места',
|
||||
altCoverage: 'Покрытие alt-текстом',
|
||||
allTypes: 'Все типы',
|
||||
typeImage: 'Изображения',
|
||||
typeSvg: 'SVG',
|
||||
typePdf: 'PDF',
|
||||
sortRecent: 'Недавние',
|
||||
sortName: 'Имя',
|
||||
sortSize: 'Размер',
|
||||
viewMode: 'Вид',
|
||||
viewGrid: 'Сетка',
|
||||
viewList: 'Список',
|
||||
dropHint: 'Перетащите изображения сюда или используйте кнопку «Загрузить» выше.',
|
||||
cancelUpload: 'Отменить',
|
||||
retry: 'Повторить',
|
||||
errorTooLarge: 'Файл слишком большой. Попробуйте файл меньшего размера.',
|
||||
errorUnsupportedType: 'Этот тип файла не поддерживается. Используйте JPG, PNG, WebP, GIF, SVG или PDF.',
|
||||
errorGeneric: 'Не удалось загрузить файл. Попробуйте ещё раз.',
|
||||
selectedCount: 'выбрано',
|
||||
download: 'Скачать',
|
||||
exportMetadata: 'Экспортировать метаданные',
|
||||
bulkDelete: 'Удалить выбранные',
|
||||
bulkDeleteConfirm: 'Удалить выбранные файлы? Это действие нельзя отменить.',
|
||||
clearSelection: 'Снять выделение',
|
||||
guideCopy: 'Повторное использование существующего файла избавляет от дублей и экономит место. Для баннеров и главных изображений рекомендуем размер около 1200×1200px и сжатие крупных фото перед загрузкой.',
|
||||
missingAlt: 'Нет alt-текста',
|
||||
usageCount: 'исп.',
|
||||
detailsTitle: 'Информация о файле',
|
||||
filename: 'Имя файла',
|
||||
format: 'Формат',
|
||||
fileSize: 'Размер файла',
|
||||
dimensions: 'Размеры',
|
||||
uploadDate: 'Загружен',
|
||||
lastUsed: 'Последнее использование',
|
||||
usageCountLabel: 'Количество использований',
|
||||
usedIn: 'Используется в',
|
||||
notUsed: 'Пока нигде не используется.',
|
||||
unknownValue: 'Неизвестно',
|
||||
altText: 'Alt-текст',
|
||||
altTextHelp: 'Описывает изображение для программ чтения с экрана и поисковых систем.',
|
||||
decorative: 'Декоративное (alt-текст не нужен)',
|
||||
missingAltWarning: 'У этого изображения нет alt-текста, и оно не отмечено как декоративное — пользователи с программами чтения с экрана не узнают, что на нём изображено.',
|
||||
caption: 'Подпись',
|
||||
description: 'Описание',
|
||||
usage: {
|
||||
brand: 'Логотип',
|
||||
theme: 'Тема',
|
||||
header: 'Шапка',
|
||||
footer: 'Подвал',
|
||||
staticPage: 'Статическая страница',
|
||||
homepage: 'Главная страница',
|
||||
catalog: 'Каталог',
|
||||
productPage: 'Страница товара',
|
||||
widget: 'Виджет',
|
||||
company: 'Информация о компании',
|
||||
seo: 'SEO',
|
||||
navigation: 'Навигация',
|
||||
other: 'Другое',
|
||||
},
|
||||
},
|
||||
adminCategories: {
|
||||
chooseImage: 'Выбрать изображение',
|
||||
|
||||
@@ -1150,6 +1150,71 @@ export interface Translations {
|
||||
confirm: string;
|
||||
emptyTitle: string;
|
||||
emptyDescription: string;
|
||||
allFolders: string;
|
||||
newFolder: string;
|
||||
editTags: string;
|
||||
totalAssets: string;
|
||||
logos: string;
|
||||
unused: string;
|
||||
storageUsed: string;
|
||||
altCoverage: string;
|
||||
allTypes: string;
|
||||
typeImage: string;
|
||||
typeSvg: string;
|
||||
typePdf: string;
|
||||
sortRecent: string;
|
||||
sortName: string;
|
||||
sortSize: string;
|
||||
viewMode: string;
|
||||
viewGrid: string;
|
||||
viewList: string;
|
||||
dropHint: string;
|
||||
cancelUpload: string;
|
||||
retry: string;
|
||||
errorTooLarge: string;
|
||||
errorUnsupportedType: string;
|
||||
errorGeneric: string;
|
||||
selectedCount: string;
|
||||
download: string;
|
||||
exportMetadata: string;
|
||||
bulkDelete: string;
|
||||
bulkDeleteConfirm: string;
|
||||
clearSelection: string;
|
||||
guideCopy: string;
|
||||
missingAlt: string;
|
||||
usageCount: string;
|
||||
detailsTitle: string;
|
||||
filename: string;
|
||||
format: string;
|
||||
fileSize: string;
|
||||
dimensions: string;
|
||||
uploadDate: string;
|
||||
lastUsed: string;
|
||||
usageCountLabel: string;
|
||||
usedIn: string;
|
||||
notUsed: string;
|
||||
unknownValue: string;
|
||||
altText: string;
|
||||
altTextHelp: string;
|
||||
decorative: string;
|
||||
missingAltWarning: string;
|
||||
caption: string;
|
||||
description: string;
|
||||
usage: {
|
||||
brand: string;
|
||||
theme: string;
|
||||
header: string;
|
||||
footer: string;
|
||||
staticPage: string;
|
||||
homepage: string;
|
||||
catalog: string;
|
||||
productPage: string;
|
||||
widget: string;
|
||||
company: string;
|
||||
seo: string;
|
||||
navigation: string;
|
||||
other: string;
|
||||
};
|
||||
};
|
||||
// Sprint 28: only the new empty-state copy this sprint's skeleton/empty-
|
||||
// state consistency fix introduces - NOT the full adminProducts/
|
||||
|
||||
Reference in New Issue
Block a user