diff --git a/assets/components/minishop3/js/mgr/utilities/utilities.js b/assets/components/minishop3/js/mgr/utilities/utilities.js deleted file mode 100644 index c05b4dc4d..000000000 --- a/assets/components/minishop3/js/mgr/utilities/utilities.js +++ /dev/null @@ -1,37 +0,0 @@ -ms3.page.Utilities = function (config) { - config = config || {}; - Ext.apply(config, { - formpanel: 'ms3-panel-utilities', - cls: 'container', - buttons: this.getButtons(config), - components: [{ - xtype: 'ms3-panel-utilities' - }] - }); - ms3.page.Utilities.superclass.constructor.call(this, config); -}; -Ext.extend(ms3.page.Utilities, MODx.Component, { - getButtons: function (config) { - const b = []; - - if (MODx.perm.mssetting_list) { - b.push({ - text: _('ms3_orders'), - id: 'ms-abtn-orders', - cls: 'primary-button', - handler: function () { - MODx.loadPage('?', 'a=mgr/orders&namespace=minishop3'); - } - }, { - text: _('ms3_settings'), - id: 'ms2-abtn-settings', - handler: function () { - MODx.loadPage('?', 'a=mgr/settings&namespace=minishop3'); - } - }); - } - - return b; - } -}); -Ext.reg('ms3-page-utilities', ms3.page.Utilities); diff --git a/assets/components/minishop3/js/mgr/utilities/utilities.panel.js b/assets/components/minishop3/js/mgr/utilities/utilities.panel.js deleted file mode 100644 index 27eb1f142..000000000 --- a/assets/components/minishop3/js/mgr/utilities/utilities.panel.js +++ /dev/null @@ -1,192 +0,0 @@ -ms3.panel.Utilities = function (config) { - config = config || {}; - Ext.apply(config, { - cls: 'container', - items: [{ - html: '

' + _('ms3_header') + ' :: ' + _('ms3_utilities') + '

', - cls: 'modx-page-header', - }, { - xtype: 'modx-tabs', - id: 'ms3-utilities-tabs', - cls: 'ms3-panel', - deferredRender: false, - listeners: { - afterrender: function(panel) { - var savedId = localStorage.getItem('ms3-utilities-active-tab'); - if (savedId) { - var tab = Ext.getCmp(savedId); - if (tab) { - panel.setActiveTab(tab); - } - } - // Enable saving only after initial restore - panel.tabStateReady = true; - }, - tabchange: function(panel, tab) { - if (!panel.tabStateReady) return; - localStorage.setItem('ms3-utilities-active-tab', tab.id); - } - }, - items: [{ - title: _('ms3_utilities_gallery'), - id: 'ms3-utilities-gallery-tab', - layout: 'fit', - autoScroll: true, - items: [{ - xtype: 'panel', - id: 'ms3-vue-utilities-gallery-panel', - border: false, - autoHeight: true, - html: '', - listeners: { - afterrender: function() { - // Mount Vue application after panel render - const event = new CustomEvent('ms3:mountVueUtilitiesGallery', { - detail: { - targetId: '#ms3-vue-utilities-gallery' - } - }); - document.dispatchEvent(event); - } - } - }] - }, - { - title: _('ms3_utilities_import'), - id: 'ms3-utilities-import-tab', - layout: 'fit', - autoScroll: true, - items: [{ - xtype: 'panel', - id: 'ms3-vue-import-panel', - border: false, - autoHeight: true, - html: '
', - listeners: { - afterrender: function() { - // Mount Vue application after panel render - const event = new CustomEvent('ms3:mountVueImport', { - detail: { - targetId: '#ms3-vue-import' - } - }); - document.dispatchEvent(event); - } - } - }] - }, - { - title: _('ms3_vue_product_fields_title'), - id: 'ms3-utilities-fields-management-tab', - layout: 'fit', - autoScroll: true, - items: [{ - xtype: 'panel', - id: 'ms3-vue-fields-management-panel', - border: false, - autoHeight: true, - html: '
', - listeners: { - afterrender: function() { - // Mount Vue application after panel render - const event = new CustomEvent('ms3:mountVueFieldsManagement', { - detail: { - targetId: '#ms3-vue-fields-management' - } - }); - document.dispatchEvent(event); - } - } - }] - }, - { - title: _('ms3_extra_fields_title'), - id: 'ms3-utilities-extra-fields-tab', - layout: 'fit', - autoScroll: true, - items: [{ - xtype: 'panel', - id: 'ms3-vue-extra-fields-panel', - border: false, - autoHeight: true, - html: '
', - listeners: { - afterrender: function() { - // Mount Vue application after panel render - const event = new CustomEvent('ms3:mountVueExtraFields', { - detail: { - targetId: '#ms3-vue-extra-fields' - } - }); - document.dispatchEvent(event); - } - } - }] - }, - { - title: _('grid_fields_config_title'), - id: 'ms3-utilities-grid-fields-config-tab', - layout: 'fit', - autoScroll: true, - items: [{ - xtype: 'panel', - id: 'ms3-vue-grid-fields-config-panel', - border: false, - autoHeight: true, - html: '
', - listeners: { - afterrender: function() { - // Mount Vue application after panel render - const event = new CustomEvent('ms3:mountVueGridFieldsConfig', { - detail: { - targetId: '#ms3-grid-fields-config-vue-wrapper' - } - }); - document.dispatchEvent(event); - } - } - }] - }, - { - title: _('ms3_model_fields_title'), - id: 'ms3-utilities-model-fields-tab', - layout: 'fit', - autoScroll: true, - items: [{ - xtype: 'panel', - id: 'ms3-vue-model-fields-panel', - border: false, - autoHeight: true, - html: '
', - listeners: { - afterrender: function() { - // Mount Vue application after panel render - const event = new CustomEvent('ms3:mountVueModelFields', { - detail: { - targetId: '#ms3-model-fields-vue-wrapper' - } - }); - document.dispatchEvent(event); - } - } - }] - } - // Old ExtJS "Object Extension" tab removed - using new Vue widget instead - ] - }] - - }); - ms3.panel.Utilities.superclass.constructor.call(this, config); - - // fixExtraFieldsPanelHeight method removed along with old ExtJS widget -}; -Ext.extend(ms3.panel.Utilities, MODx.Panel, { - // Old methods for ExtJS extra fields removed -}); -Ext.reg('ms3-panel-utilities', ms3.panel.Utilities); diff --git a/core/components/minishop3/controllers/mgr/utilities.class.php b/core/components/minishop3/controllers/mgr/utilities.class.php index 52ffe8fbc..5c9e49e18 100644 --- a/core/components/minishop3/controllers/mgr/utilities.class.php +++ b/core/components/minishop3/controllers/mgr/utilities.class.php @@ -2,7 +2,6 @@ use MiniShop3\Model\msProduct; use MiniShop3\Model\msProductFile; -use MODX\Revolution\Sources\modMediaSource; if (!class_exists('msManagerController')) { require_once dirname(__FILE__, 2) . '/manager.class.php'; @@ -26,77 +25,73 @@ public function getLanguageTopics() return ['minishop3:default', 'minishop3:product', 'minishop3:manager', 'minishop3:vue']; } - /** - * - */ public function loadCustomCssJs() { - $this->addCss($this->ms3->config['cssUrl'] . 'mgr/bootstrap.buttons.css'); - $this->addCss($this->ms3->config['cssUrl'] . 'mgr/main.css'); - // Gallery CSS now included in Vue component (utilities-gallery.min.css) - - $this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/minishop3.js'); - $this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/misc/default.grid.js'); - $this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/misc/default.window.js'); - $this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/misc/ms3.utils.js'); - $this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/misc/ms3.combo.js'); - - $this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/utilities/utilities.js'); - $this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/utilities/utilities.panel.js'); - // Import tab uses Vue (import.min.js); Ext import/panel.js removed (#522). - $config = $this->ms3->config; + $config['mssetting_list'] = $this->modx->hasPermission('mssetting_list'); $productSource = (int)$this->getOption('ms3_product_source_default', null, 1); - $source = null; - if ($productSource > 0) { - $source = $this->modx->getObject('sources.modMediaSource', $productSource); - } + $source = $productSource > 0 + ? $this->modx->getObject('sources.modMediaSource', $productSource) + : null; if ($source) { $config['utility_gallery_source_id'] = $productSource; $config['utility_gallery_source_name'] = $source->get('name'); - $properties = $source->get('properties'); + $thumbnails = json_decode( + (string)($source->get('properties')['thumbnails']['value'] ?? ''), + true + ); $propertiesString = ''; - foreach (json_decode($properties['thumbnails']['value'], true) as $key => $value) { - $propertiesString .= "$key: " . json_encode($value) . "
"; + if (is_array($thumbnails)) { + foreach ($thumbnails as $key => $value) { + $propertiesString .= '' . htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8') + . ': ' . htmlspecialchars(json_encode($value), ENT_QUOTES, 'UTF-8') . '
'; + } } $config['utility_gallery_thumbnails'] = $propertiesString; } - // get information about products and files - $config['utility_gallery_total_products'] = $this->modx->getCount(msProduct::class, ['class_key' => msProduct::class]); - $config['utility_gallery_total_products_files'] = $this->modx->getCount(msProductFile::class, ['parent_id' => 0]); - - $config['utility_import_fields'] = $this->getOption('ms3_utility_import_fields', null, 'pagetitle,parent,price,article', true); - $config['utility_import_fields_delimiter'] = $this->getOption('ms3_utility_import_fields_delimiter', null, ';', true); - - // CSS for Vue components - $this->addCss($this->ms3->config['assetsUrl'] . 'css/mgr/vue-dist/primeicons.min.css'); - $this->addCss($this->ms3->config['assetsUrl'] . 'css/mgr/vue-dist/fields-management.min.css'); - $this->addCss($this->ms3->config['assetsUrl'] . 'css/mgr/vue-dist/extra-fields.min.css'); - $this->addCss($this->ms3->config['assetsUrl'] . 'css/mgr/vue-dist/grid-fields-config.min.css'); - $this->addCss($this->ms3->config['assetsUrl'] . 'css/mgr/vue-dist/model-fields.min.css'); - $this->addCss($this->ms3->config['assetsUrl'] . 'css/mgr/vue-dist/import.min.css'); - $this->addCss($this->ms3->config['assetsUrl'] . 'css/mgr/vue-dist/utilities-gallery.min.css'); + $config['utility_gallery_total_products'] = $this->modx->getCount( + msProduct::class, + ['class_key' => msProduct::class] + ); + $config['utility_gallery_total_products_files'] = $this->modx->getCount( + msProductFile::class, + ['parent_id' => 0] + ); + + // Config must precede Vue modules (#524). + $this->addHtml( + '' + ); + + $cssBase = $this->ms3->config['assetsUrl'] . 'css/mgr/vue-dist/'; + foreach (['primeicons', 'utilities'] as $asset) { + $this->addCss($cssBase . $asset . '.min.css'); + } + $this->addVueModule($this->ms3->config['jsUrl'] . 'mgr/vue-dist/utilities.min.js'); - // Config MUST be set BEFORE Vue modules load (they read from ms3.config) - $this->addHtml(''); + $this->modx->invokeEvent('msOnManagerCustomCssJs', [ + 'controller' => $this, + 'page' => 'utilities', + ]); + } - // Vue modules with VueTools dependency check - $assetsUrl = $this->ms3->config['assetsUrl']; - $this->addVueModule($assetsUrl . 'js/mgr/vue-dist/fields-management.min.js'); - $this->addVueModule($assetsUrl . 'js/mgr/vue-dist/extra-fields.min.js'); - $this->addVueModule($assetsUrl . 'js/mgr/vue-dist/grid-fields-config.min.js'); - $this->addVueModule($assetsUrl . 'js/mgr/vue-dist/model-fields.min.js'); - $this->addVueModule($assetsUrl . 'js/mgr/vue-dist/import.min.js'); - $this->addVueModule($assetsUrl . 'js/mgr/vue-dist/utilities-gallery.min.js'); + /** + * @param array $scriptProperties + * @return mixed + */ + public function process(array $scriptProperties = []) + { + return []; + } - $this->addHtml(' - '); + /** + * @return string + */ + public function getTemplateFile() + { + return dirname(__FILE__, 3) . '/templates/default/utilities.tpl'; } } diff --git a/core/components/minishop3/templates/default/utilities.tpl b/core/components/minishop3/templates/default/utilities.tpl new file mode 100644 index 000000000..f2ba0aeac --- /dev/null +++ b/core/components/minishop3/templates/default/utilities.tpl @@ -0,0 +1,3 @@ +
+
+
diff --git a/core/components/minishop3/tests/OrphanExtAssetsTest.php b/core/components/minishop3/tests/OrphanExtAssetsTest.php index 377f5a1c1..f11caaa77 100644 --- a/core/components/minishop3/tests/OrphanExtAssetsTest.php +++ b/core/components/minishop3/tests/OrphanExtAssetsTest.php @@ -37,8 +37,13 @@ $fail('utilities controller must not load utilities/import/panel.js (#522)'); } -if (preg_match('/addVueModule\([^;]*import\.min\.js/', $utilitiesController) !== 1) { - $fail('utilities controller must still register import.min.js Vue entry'); +// Import lives inside utilities.min.js after Phase 1c (#524 / #537), not a separate entry. +if (preg_match('/addVueModule\([^;]*utilities\.min\.js/', $utilitiesController) !== 1) { + $fail('utilities controller must register utilities.min.js Vue entry (#524)'); +} + +if (preg_match('/addVueModule\([^;]*import\.min\.js/', $utilitiesController) === 1) { + $fail('utilities controller must not register separate import.min.js after utilities consolidation (#524)'); } $scanRoots = [ diff --git a/core/components/minishop3/tests/UtilitiesVueEntryTest.php b/core/components/minishop3/tests/UtilitiesVueEntryTest.php new file mode 100644 index 000000000..07d163d6b --- /dev/null +++ b/core/components/minishop3/tests/UtilitiesVueEntryTest.php @@ -0,0 +1,151 @@ + { }) } -// Get configuration directly from ms3.config (reactive refs) const sourceId = ref(1) const sourceName = ref('') const totalProducts = ref(0) const totalFiles = ref(0) const thumbnailsInfo = ref('') -// Load config from data-attributes of mount element -const loadConfig = () => { - const el = document.getElementById('ms3-vue-utilities-gallery') - if (el) { - sourceId.value = parseInt(el.dataset.sourceId) || 1 - sourceName.value = el.dataset.sourceName || '' - totalProducts.value = parseInt(el.dataset.totalProducts) || 0 - totalFiles.value = parseInt(el.dataset.totalFiles) || 0 - thumbnailsInfo.value = decodeURIComponent(el.dataset.thumbnails || '') - } +function loadConfig() { + const cfg = getMs3Config() || {} + sourceId.value = parseInt(cfg.utility_gallery_source_id, 10) || 1 + sourceName.value = cfg.utility_gallery_source_name || '' + totalProducts.value = parseInt(cfg.utility_gallery_total_products, 10) || 0 + totalFiles.value = parseInt(cfg.utility_gallery_total_products_files, 10) || 0 + thumbnailsInfo.value = cfg.utility_gallery_thumbnails || '' } // State diff --git a/vueManager/src/components/UtilitiesPage.vue b/vueManager/src/components/UtilitiesPage.vue new file mode 100644 index 000000000..92893d60f --- /dev/null +++ b/vueManager/src/components/UtilitiesPage.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/vueManager/src/entries/extra-fields.js b/vueManager/src/entries/extra-fields.js deleted file mode 100644 index b8b9af79d..000000000 --- a/vueManager/src/entries/extra-fields.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Entry point for Extra Fields Manager widget (ES Module) - * - * Exports initialization function for mounting Vue application - */ - -import '../scss/primevue.scss' -import 'primeicons/primeicons.css' - -import Aura from '@primeuix/themes/aura' -import { getPrimeVueLocale } from '@vuetools/usePrimeVueLocale' -import { createPinia } from 'pinia' -import PrimeVue from 'primevue/config' -import ConfirmationService from 'primevue/confirmationservice' -import ToastService from 'primevue/toastservice' -import Tooltip from 'primevue/tooltip' -import { createApp } from 'vue' - -import VueExtraFieldsManager from '../components/ExtraFieldsManager.vue' -import { injectFormStylesOverride } from '../utils/formStyles.js' - -/** - * Creates and configures Vue application - */ -function createVueApp() { - const app = createApp(VueExtraFieldsManager) - - const pinia = createPinia() - app.use(pinia) - - app.use(PrimeVue, { - theme: { - preset: Aura, - options: { - darkModeSelector: 'none', - }, - }, - locale: getPrimeVueLocale(), - }) - - app.use(ConfirmationService) - app.use(ToastService) - - app.directive('tooltip', Tooltip) - - return app -} - -/** - * Widget initialization - * Called externally when switching to the tab - */ -export function init(selector = '#ms3-vue-extra-fields') { - const $el = document.querySelector(selector) - - if (!$el) { - console.warn(`[ExtraFields] Element ${selector} not found`) - return null - } - - if ($el.dataset.vApp === 'true') { - return null - } - - const app = createVueApp() - app.mount(selector) - injectFormStylesOverride() - $el.dataset.vApp = 'true' - - return app -} - -/** - * Wait for ExtJS to create DOM element - */ -function waitForElement(selector, callback) { - const element = document.querySelector(selector) - if (element) { - callback(element) - return - } - - const observer = new MutationObserver(() => { - const element = document.querySelector(selector) - if (element) { - observer.disconnect() - callback(element) - } - }) - - observer.observe(document.body, { - childList: true, - subtree: true, - }) -} - -/** - * Listen for mount event from ExtJS - */ -document.addEventListener('ms3:mountVueExtraFields', e => { - const targetId = e.detail?.targetId || '#ms3-vue-extra-fields' - init(targetId) -}) - -/** - * Automatic initialization - wait for element to appear - */ -if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => { - waitForElement('#ms3-vue-extra-fields', () => init('#ms3-vue-extra-fields')) - }) -} else { - waitForElement('#ms3-vue-extra-fields', () => init('#ms3-vue-extra-fields')) -} diff --git a/vueManager/src/entries/fields-management.js b/vueManager/src/entries/fields-management.js deleted file mode 100644 index a92027226..000000000 --- a/vueManager/src/entries/fields-management.js +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Entry point for Fields Management widget (ES Module) - * - * Exports initialization function for mounting Vue application - */ - -import '../scss/primevue.scss' -import 'primeicons/primeicons.css' - -import Aura from '@primeuix/themes/aura' -import { getPrimeVueLocale } from '@vuetools/usePrimeVueLocale' -import { createPinia } from 'pinia' -import PrimeVue from 'primevue/config' -import ConfirmationService from 'primevue/confirmationservice' -import ToastService from 'primevue/toastservice' -import { createApp } from 'vue' - -import VueFieldsManagement from '../components/FieldsManagement.vue' -import { injectFormStylesOverride } from '../utils/formStyles.js' - -/** - * Creates and configures Vue application - */ -function createVueApp() { - const app = createApp(VueFieldsManagement) - - const pinia = createPinia() - app.use(pinia) - - app.use(PrimeVue, { - theme: { - preset: Aura, - options: { - darkModeSelector: 'none', - }, - }, - locale: getPrimeVueLocale(), - }) - - app.use(ConfirmationService) - app.use(ToastService) - - return app -} - -/** - * Widget initialization - * Called externally when switching to the tab - */ -export function init(selector = '#vue-fields-management') { - const $el = document.querySelector(selector) - - if (!$el) { - console.warn(`[Fields Management] Element ${selector} not found`) - return null - } - - if ($el.dataset.vApp === 'true') { - // Already mounted - return null - } - - const app = createVueApp() - app.mount(selector) - injectFormStylesOverride() - $el.dataset.vApp = 'true' - - return app -} - -/** - * Wait for ExtJS to create DOM element - */ -function waitForElement(selector, callback) { - const element = document.querySelector(selector) - if (element) { - callback(element) - return - } - - const observer = new MutationObserver(() => { - const element = document.querySelector(selector) - if (element) { - observer.disconnect() - callback(element) - } - }) - - observer.observe(document.body, { - childList: true, - subtree: true, - }) -} - -/** - * Listen for mount event from ExtJS - */ -document.addEventListener('ms3:mountVueFieldsManagement', e => { - const targetId = e.detail?.targetId || '#ms3-vue-fields-management' - init(targetId) -}) - -/** - * Automatic initialization - wait for element to appear - */ -if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => { - waitForElement('#ms3-vue-fields-management', () => init('#ms3-vue-fields-management')) - }) -} else { - waitForElement('#ms3-vue-fields-management', () => init('#ms3-vue-fields-management')) -} diff --git a/vueManager/src/entries/grid-fields-config.js b/vueManager/src/entries/grid-fields-config.js deleted file mode 100644 index ee2fe56a5..000000000 --- a/vueManager/src/entries/grid-fields-config.js +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Entry point for Grid Fields Config widget (ES Module) - * - * Exports initialization function for mounting Vue application - */ - -import '../scss/primevue.scss' -import 'primeicons/primeicons.css' - -import Aura from '@primeuix/themes/aura' -import { getPrimeVueLocale } from '@vuetools/usePrimeVueLocale' -import { createPinia } from 'pinia' -import PrimeVue from 'primevue/config' -import ConfirmationService from 'primevue/confirmationservice' -import ToastService from 'primevue/toastservice' -import { createApp } from 'vue' - -import GridFieldsConfig from '../components/GridFieldsConfig.vue' -import { injectFormStylesOverride } from '../utils/formStyles.js' - -/** - * Creates and configures Vue application - */ -function createVueApp() { - const app = createApp(GridFieldsConfig) - - const pinia = createPinia() - app.use(pinia) - - app.use(PrimeVue, { - theme: { - preset: Aura, - options: { - darkModeSelector: 'none', - }, - }, - locale: getPrimeVueLocale(), - }) - - app.use(ConfirmationService) - app.use(ToastService) - - return app -} - -/** - * Widget initialization - * Called when grid configuration page loads - */ -export function init(selector = '#ms3-grid-fields-config-vue-wrapper') { - const $el = document.querySelector(selector) - - if (!$el) { - return null - } - - if ($el.dataset.vApp === 'true') { - return null - } - - const app = createVueApp() - app.mount(selector) - injectFormStylesOverride() - $el.dataset.vApp = 'true' - - return app -} - -/** - * Wait for ExtJS to create DOM element - */ -function waitForElement(selector, callback) { - const element = document.querySelector(selector) - if (element) { - callback(element) - return - } - - const observer = new MutationObserver(() => { - const element = document.querySelector(selector) - if (element) { - observer.disconnect() - callback(element) - } - }) - - observer.observe(document.body, { - childList: true, - subtree: true, - }) -} - -/** - * Listen for custom event from ExtJS panel - * Mount application when tab is rendered - */ -document.addEventListener('ms3:mountVueGridFieldsConfig', event => { - const targetId = event.detail?.targetId || '#ms3-grid-fields-config-vue-wrapper' - init(targetId) -}) - -/** - * Automatic initialization - wait for element to appear - */ -if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => { - waitForElement('#ms3-grid-fields-config-vue-wrapper', () => - init('#ms3-grid-fields-config-vue-wrapper') - ) - }) -} else { - waitForElement('#ms3-grid-fields-config-vue-wrapper', () => - init('#ms3-grid-fields-config-vue-wrapper') - ) -} diff --git a/vueManager/src/entries/import.js b/vueManager/src/entries/import.js deleted file mode 100644 index f4cf178fd..000000000 --- a/vueManager/src/entries/import.js +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Entry point for Import Products page (ES Module) - * - * Exports initialization function for mounting Vue application - */ - -import '../scss/primevue.scss' -import 'primeicons/primeicons.css' - -import Aura from '@primeuix/themes/aura' -import { getPrimeVueLocale } from '@vuetools/usePrimeVueLocale' -import PrimeVue from 'primevue/config' -import ConfirmationService from 'primevue/confirmationservice' -import ToastService from 'primevue/toastservice' -import { createApp } from 'vue' - -import ImportProducts from '../components/ImportProducts.vue' -import { injectFormStylesOverride } from '../utils/formStyles.js' - -let app = null - -/** - * Creates and configures Vue application - */ -function createVueApp() { - const vueApp = createApp(ImportProducts) - - vueApp.use(PrimeVue, { - theme: { - preset: Aura, - options: { - prefix: 'p', - darkModeSelector: '.ms3-dark-mode', - cssLayer: false, - }, - }, - locale: getPrimeVueLocale(), - }) - - vueApp.use(ConfirmationService) - vueApp.use(ToastService) - - return vueApp -} - -/** - * Widget initialization - */ -export function init(selector = '#ms3-vue-import') { - const $el = document.querySelector(selector) - - if (!$el) { - console.warn(`[MS3 Import] Target element not found: ${selector}`) - return null - } - - if ($el.dataset.vApp === 'true') { - return null - } - - // Unmount existing app if any - if (app) { - app.unmount() - } - - app = createVueApp() - app.mount(selector) - injectFormStylesOverride() - $el.dataset.vApp = 'true' - - return app -} - -/** - * Unmount the application - */ -export function unmount() { - if (app) { - app.unmount() - app = null - } -} - -/** - * Wait for ExtJS to create DOM element - */ -function waitForElement(selector, callback) { - const element = document.querySelector(selector) - - if (element) { - callback(element) - return - } - - const observer = new MutationObserver(() => { - const element = document.querySelector(selector) - if (element) { - observer.disconnect() - callback(element) - } - }) - - observer.observe(document.body, { - childList: true, - subtree: true, - }) -} - -// Listen for mount event from ExtJS -document.addEventListener('ms3:mountVueImport', event => { - const targetId = event.detail?.targetId || '#ms3-vue-import' - init(targetId) -}) - -/** - * Automatic initialization on DOM load - */ -if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => { - waitForElement('#ms3-vue-import', () => init()) - }) -} else { - waitForElement('#ms3-vue-import', () => init()) -} - -// Export for programmatic usage -export default { init, unmount } diff --git a/vueManager/src/entries/model-fields.js b/vueManager/src/entries/model-fields.js deleted file mode 100644 index 81ac20c24..000000000 --- a/vueManager/src/entries/model-fields.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Entry point for Model Fields Management widget (ES Module) - * - * Exports initialization function for mounting Vue application - */ - -import '../scss/primevue.scss' -import 'primeicons/primeicons.css' - -import Aura from '@primeuix/themes/aura' -import { getPrimeVueLocale } from '@vuetools/usePrimeVueLocale' -import { createPinia } from 'pinia' -import PrimeVue from 'primevue/config' -import ConfirmationService from 'primevue/confirmationservice' -import ToastService from 'primevue/toastservice' -import { createApp } from 'vue' - -import ModelFieldsGrid from '../components/ModelFieldsGrid.vue' -import { injectFormStylesOverride } from '../utils/formStyles.js' - -/** - * Creates and configures Vue application - */ -function createVueApp() { - const app = createApp(ModelFieldsGrid) - const pinia = createPinia() - - app.use(pinia) - - app.use(PrimeVue, { - theme: { - preset: Aura, - options: { - darkModeSelector: 'none', - }, - }, - locale: getPrimeVueLocale(), - }) - - app.use(ConfirmationService) - app.use(ToastService) - - return app -} - -/** - * Widget initialization - * Called when Model Fields page loads - */ -export function init(selector = '#ms3-model-fields-vue-wrapper') { - const $el = document.querySelector(selector) - - if (!$el) { - return null - } - - if ($el.dataset.vApp === 'true') { - return null - } - - const app = createVueApp() - app.mount(selector) - injectFormStylesOverride() - $el.dataset.vApp = 'true' - - return app -} - -/** - * Wait for ExtJS to create DOM element - * Uses MutationObserver to track element appearance - */ -function waitForElement(selector, callback) { - const element = document.querySelector(selector) - - if (element) { - callback(element) - return - } - - const observer = new MutationObserver(() => { - const element = document.querySelector(selector) - if (element) { - observer.disconnect() - callback(element) - } - }) - - observer.observe(document.body, { - childList: true, - subtree: true, - }) -} - -/** - * Automatic initialization on DOM load - */ -if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => { - waitForElement('#ms3-model-fields-vue-wrapper', () => init()) - }) -} else { - waitForElement('#ms3-model-fields-vue-wrapper', () => init()) -} diff --git a/vueManager/src/entries/utilities-gallery.js b/vueManager/src/entries/utilities-gallery.js deleted file mode 100644 index 56cf5773e..000000000 --- a/vueManager/src/entries/utilities-gallery.js +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Entry point for Utilities Gallery page (ES Module) - * - * Exports initialization function for mounting Vue application - */ - -import '../scss/primevue.scss' -import 'primeicons/primeicons.css' - -import Aura from '@primeuix/themes/aura' -import { getPrimeVueLocale } from '@vuetools/usePrimeVueLocale' -import PrimeVue from 'primevue/config' -import ToastService from 'primevue/toastservice' -import { createApp } from 'vue' - -import UtilitiesGallery from '../components/UtilitiesGallery.vue' -import { injectFormStylesOverride } from '../utils/formStyles.js' - -let app = null - -/** - * Creates and configures Vue application - */ -function createVueApp(props = {}) { - const vueApp = createApp(UtilitiesGallery, props) - vueApp.use(PrimeVue, { - theme: { - preset: Aura, - options: { - prefix: 'p', - darkModeSelector: '.ms3-dark-mode', - cssLayer: false, - }, - }, - locale: getPrimeVueLocale(), - }) - - vueApp.use(ToastService) - - return vueApp -} - -/** - * Widget initialization - */ -export function init(selector = '#ms3-vue-utilities-gallery') { - const $el = document.querySelector(selector) - - if (!$el) { - console.warn('[Gallery] Element not found:', selector) - return null - } - - if ($el.dataset.vApp === 'true') { - return null - } - - // Unmount existing app if any - if (app) { - app.unmount() - } - - // Component reads config from data-attributes set by ExtJS - app = createVueApp() - app.mount(selector) - injectFormStylesOverride() - $el.dataset.vApp = 'true' - - return app -} - -/** - * Unmount the application - */ -export function unmount() { - if (app) { - app.unmount() - app = null - } -} - -/** - * Wait for ExtJS to create DOM element - */ -function waitForElement(selector, callback) { - const element = document.querySelector(selector) - - if (element) { - callback(element) - return - } - - const observer = new MutationObserver(() => { - const element = document.querySelector(selector) - if (element) { - observer.disconnect() - callback(element) - } - }) - - observer.observe(document.body, { - childList: true, - subtree: true, - }) -} - -// Listen for mount event from ExtJS -document.addEventListener('ms3:mountVueUtilitiesGallery', event => { - const targetId = event.detail?.targetId || '#ms3-vue-utilities-gallery' - init(targetId) -}) - -/** - * Automatic initialization on DOM load - */ -if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => { - waitForElement('#ms3-vue-utilities-gallery', () => init()) - }) -} else { - waitForElement('#ms3-vue-utilities-gallery', () => init()) -} - -// Export for programmatic usage -export default { init, unmount } diff --git a/vueManager/src/entries/utilities.js b/vueManager/src/entries/utilities.js new file mode 100644 index 000000000..db8bccda4 --- /dev/null +++ b/vueManager/src/entries/utilities.js @@ -0,0 +1,49 @@ +/** + * Utilities page entry — Vue shell instead of Ext panel (#524). + */ + +import '../scss/primevue.scss' +import 'primeicons/primeicons.css' + +import Aura from '@primeuix/themes/aura' +import { getPrimeVueLocale } from '@vuetools/usePrimeVueLocale' +import { createPinia } from 'pinia' +import PrimeVue from 'primevue/config' +import ConfirmationService from 'primevue/confirmationservice' +import ToastService from 'primevue/toastservice' +import { createApp } from 'vue' + +import UtilitiesPage from '../components/UtilitiesPage.vue' +import { injectFormStylesOverride } from '../utils/formStyles.js' + +export function init(selector = '#ms3-vue-utilities') { + const $el = document.querySelector(selector) + if (!$el || $el.dataset.vApp === 'true') { + return null + } + + const app = createApp(UtilitiesPage) + app.use(createPinia()) + app.use(PrimeVue, { + theme: { + preset: Aura, + options: { + darkModeSelector: 'none', + }, + }, + locale: getPrimeVueLocale(), + }) + app.use(ConfirmationService) + app.use(ToastService) + app.mount(selector) + injectFormStylesOverride() + $el.dataset.vApp = 'true' + + return app +} + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => init()) +} else { + init() +} diff --git a/vueManager/vite.config.js b/vueManager/vite.config.js index 6c7b15e8c..f2f7fd584 100644 --- a/vueManager/vite.config.js +++ b/vueManager/vite.config.js @@ -19,21 +19,16 @@ const DevInput = { } const ProdInput = { - 'fields-management': 'src/entries/fields-management.js', - 'extra-fields': 'src/entries/extra-fields.js', 'product-tabs': 'src/entries/product-tabs.js', 'customers': 'src/entries/customers.js', 'orders': 'src/entries/orders.js', 'order': 'src/entries/order.js', 'notifications': 'src/entries/notifications.js', - 'model-fields': 'src/entries/model-fields.js', - 'grid-fields-config': 'src/entries/grid-fields-config.js', - 'import': 'src/entries/import.js', - 'utilities-gallery': 'src/entries/utilities-gallery.js', 'category-options': 'src/entries/category-options.js', 'category-products': 'src/entries/category-products.js', 'help': 'src/entries/help.js', 'settings': 'src/entries/settings.js', + 'utilities': 'src/entries/utilities.js', 'main': 'src/main.js' } // https://vite.dev/config/