feat(gallery): Vue refactor and unified modal styles#121
feat(gallery): Vue refactor and unified modal styles#121Ibochkarev wants to merge 5 commits intobetafrom
Conversation
- Replace legacy gallery JS (panel, toolbar, view, window) with Vue components - Add ProductGallery, ProductGalleryGrid, ProductGalleryToolbar, ProductGalleryEditDialog - Add useGalleryApi composable, integrate GalleryUploader - Unify modal width via --ms3-modal-width, fix ConfirmDialog width (90vw max) - Fix Edit Dialog close button: use closeButtonProps for circular focus ring - Add lexicon entries (ru/en), update Upload processor and product controller
biz87
left a comment
There was a problem hiding this comment.
Ревью PR #121: feat(gallery): Vue refactor and unified modal styles
Большой и качественный PR — полная замена ExtJS-галереи на Vue. Архитектура хорошая: отдельные компоненты, composable useGalleryApi с withLoading, provide/inject для thumb, design-token для модалей. Лексиконы на двух языках, Uppy полностью локализован.
1. Безопасность: getMediaSourcesList() без проверки прав
public function getMediaSourcesList()
{
$sources = $this->modx->getCollection('sources.modMediaSource', ['id:>' => 0]);
...
}Загружает все медиа-источники без проверки access policies. MODX поддерживает ACL на медиа-источники — пользователь может не иметь доступа к некоторым из них.
Старый ms3-combo-source (ExtJS) загружал список через коннектор с проверкой прав. Новый код отдаёт все источники в JSON-конфиг страницы.
Нужно фильтровать через $source->checkPolicy('view') или использовать стандартный механизм MODX для получения доступных источников.
2. updateProductSource — частичное обновление через Product\Update
export async function updateProductSource(productId, sourceId) {
await connectorRequest(PRODUCT_UPDATE, {
id: productId,
source_id: sourceId,
})
location.reload()
}Старый ExtJS код отправлял всю форму через MODx.activePage.submitForm(). Новый код отправляет только id + source_id в Product\Update. Если процессор ожидает обязательные поля (pagetitle и т.д.) или обнуляет пустые — это может привести к потере данных товара. Нужно проверить поведение процессора при частичном вводе.
3. Мёртвый watcher в ProductGalleryGrid.vue
watch(
() => props.first,
(v) => {
if (searchQuery.value !== undefined) return
}
)searchQuery — всегда строка (инициализирован как ''), !== undefined всегда true. Watcher ничего не делает — мёртвый код, удалить.
4. Глобальные стили ConfirmDialog
В ProductGallery.vue (unscoped <style>):
.p-confirm-dialog {
width: var(--ms3-modal-width, 28rem);
max-width: 90vw;
...
}Затронет все ConfirmDialog на странице, не только галерейные. Может сломать другие диалоги (ProductData, будущие компоненты). Стоит ограничить scope или использовать отдельный CSS-класс.
5. CSS галереи загружается безусловно
Старый код загружал CSS/JS аплоадера только при $show_gallery === true. Новый код загружает GalleryUploader.min.css всегда, без проверки настройки ms3_product_tab_gallery.
6. Двойной emit при закрытии EditDialog
<Dialog
@update:visible="(v) => $emit('update:visible', v)"
@hide="close"
>close() вызывает emit('update:visible', false). При закрытии Dialog сначала вызовет @update:visible(false), потом @hide → close() → ещё один emit. Не вредит, но стоит убрать одно из двух.
7. Имя CSS-файла
gallery-uploader.min.css → GalleryUploader.min.css (PascalCase). Проверить, что Vite генерирует файл именно с таким именем.
Что сделано хорошо
- Composable
useGalleryApiсwithLoading— элегантный паттерн provide/injectдля обновления thumb- vuedraggable для drag&drop, context menu на правый клик
- Robust JSON parsing с fallback для PHP notices в
getResponseData - Debounced search
- Полная локализация Uppy
- Design-token
--ms3-modal-width
Итог
Пункты 1-2 — блокирующие (безопасность + потенциальная потеря данных). Остальное — улучшения.
…yles - Update product gallery components to enhance styling and functionality - Refactor ConfirmDialog and ProductGalleryEditDialog for consistent class usage - Adjust CSS class names for better specificity - Modify product update API endpoint for clarity - Remove unused watch functionality in ProductGalleryGrid
biz87
left a comment
There was a problem hiding this comment.
Ревью
PR отличный по архитектуре — composable с withLoading, provide/inject для thumb, отдельный UpdateSource процессор. Замечания biz87 учтены (checkPolicy, dedicated processor, переписанный watcher). Два момента:
1. Блокирующее: watch не импортирован в ProductGalleryGrid.vue
// строка 10
import { computed, ref } from 'vue'// строка 57 — вызов watch(), которого нет в импорте
watch(
() => props.items,
(val) => { orderedItems.value = val?.length ? [...val] : [] },
{ immediate: true }
)Компонент упадёт с ReferenceError: watch is not defined при рендере. Без этого watcher'а сетка галереи не будет обновляться при загрузке данных.
Фикс: import { computed, ref, watch } from 'vue'
2. Не блокирующее: connectorRequest фильтрует пустые строки — нельзя очистить description
В useGalleryApi.js:
Object.entries(allParams).filter(([_, v]) => v !== undefined && v !== null && v !== '')updateFile передаёт description: payload.description ?? ''. Если пользователь очистит description → пустая строка отфильтруется → процессор не получит параметр → значение останется прежним.
Старый ExtJS-код отправлял форму как есть, пустые поля доходили до процессора.
Варианты:
- Убрать
v !== ''из общего фильтра - Или не фильтровать пустые строки только для
updateFile(передавать params напрямую)
- Updated `useGalleryApi.js` to filter out undefined and null parameters for API requests, allowing empty strings for description updates. - Added `watch` import in `ProductGalleryGrid.vue` for potential reactive data handling.
biz87
left a comment
There was a problem hiding this comment.
Code Review: PR #121 — Gallery Vue Refactor
Хороший рефакторинг. Полная замена 4 ExtJS файлов (panel, toolbar, view, window) на Vue-компоненты с композаблом — правильный вектор. Архитектура чистая: ProductGallery как оркестратор, Grid/Toolbar/EditDialog как дочерние, useGalleryApi как data layer. Код читаемый, лексиконы синхронны (ru/en). Выделю важные моменты.
1. Performance: upload-success vs upload-complete
ProductGallery.vue слушает @upload-success, который срабатывает на каждый файл. При загрузке 10 фото — 10 вызовов loadList(). Старый ExtJS код использовал onUploadComplete (1 раз в конце батча).
Рекомендация: слушать @upload-complete вместо @upload-success, или debounce reload.
2. updateProductSource теряет несохранённые изменения
Старый код (gallery.toolbar.js):
MODx.activePage.submitForm({ success: { fn: function(r) {
MODx.loadPage('resource/update', 'id=' + r.result.object.id);
}}});Новый код (useGalleryApi.js):
location.reload()Старый код сначала сохранял форму, потом перезагружал. Новый просто делает location.reload(). Если пользователь редактировал поля товара на других вкладках — всё потеряется без предупреждения.
Варианты:
- Использовать
MODx.activePage.submitForm()из Vue (доступен через global) - Предупреждать пользователя, что несохранённые изменения будут потеряны (это уже частично делает confirm dialog, но текст лексикона не упоминает потерю данных)
3. UpdateSource.php — валидация source_id
if ($sourceId < 0) {
return $this->failure(...)Пропускает source_id = 0, что в MODX обычно означает «нет источника». Также не проверяется, что media source с таким ID существует и пользователь имеет к нему доступ. Сравни с getMediaSourcesList() из того же PR, где правильно проверяется $source->checkPolicy('view').
Рекомендация:
$source = $this->modx->getObject(\MODX\Revolution\Sources\modMediaSource::class, $sourceId);
if (!$source || !$source->checkPolicy('view')) {
return $this->failure(...);
}4. UpdateSource.php — не проверяет policy на продукт
Проверяется только $permission = 'msproduct_save' (пермишен), но не $this->product->checkPolicy('save') (ACL-политика ресурса). В основном Update.php процессоре это проверяется.
5. Неиспользуемый лексикон ms3_gallery_search_placeholder
Добавлены ключи ms3_gallery_search_placeholder (ru/en), но в ProductGalleryGrid.vue поиск использует:
:placeholder="_('search')"Вместо:
:placeholder="_('ms3_gallery_search_placeholder')"Либо удалить неиспользуемый лексикон, либо использовать его.
6. connectorRequest — пустые строки в GET-запросах
После commit 3 фильтр пропускает пустые строки (v !== undefined && v !== null). Для POST это правильно (чтобы description можно было очистить). Но для GET-запросов (fetchGalleryList) это означает, что пустая query отправится как &query=, что раньше (commit 1) фильтровалось. Скорее всего не критично, но стоит проверить, что GetList процессор нормально обрабатывает пустой query.
7. Minor
getMediaSourcesList()использует строковый alias'sources.modMediaSource'вместо\MODX\Revolution\Sources\modMediaSource::class. Не критично, но отличается от паттерна остального кода.- Commit 3 message: «Added watch import for potential reactive data handling» —
watchреально используется дляorderedItems, commit message вводит в заблуждение. GalleryUploader.vuegetResponseData: Хороший defensive parsing (обработка PHP notices перед JSON), но стоит добавитьconsole.warnдля случая когда JSON извлекается из «мусорного» ответа — иначе PHP-ошибки на сервере будут полностью тихими.
Итого
Главные замечания:
⚠️ upload-success → upload-complete (performance, N запросов вместо 1)⚠️ location.reload() без сохранения (потеря данных)⚠️ UpdateSource.php — неполная валидация (существование source, policy check)
Остальное — минор.
…ation - Changed confirmation message for media source changes to clarify that unsaved changes on other tabs will be lost. - Enhanced validation in UpdateSource processor to check for valid source IDs and user permissions. - Updated various components to reflect changes in event naming and placeholder text for better clarity.
Описание
Рефакторинг галереи товара: замена старого JS (gallery.panel, toolbar, view, window) на Vue-компоненты и унификация стилей модальных окон.
Галерея:
ProductGallery,ProductGalleryGrid,ProductGalleryToolbar,ProductGalleryEditDialoguseGalleryApiдля работы с API галереиGalleryUploaderв новую структуруМодальные окна:
--ms3-modal-width(design tokens), ширина ConfirmDialog и Dialog приведена к единому виду (без растягивания на 90vw по умолчанию)closeButtonPropsдля применения класса к корневому элементу кнопки; круглая обводка при фокусе черезbox-shadowвместоoutlineТип изменений
Связанные Issues
(номер issue при наличии)
Как это было протестировано?
Конфигурация тестирования:
Скриншоты (если применимо)
Чеклист
Дополнительные заметки
gallery.panel.js,gallery.toolbar.js,gallery.view.js,gallery.window.js; стили галереи убраны изmain.cssoverflow-x: hidden) вынесены вprimevue.scss