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 @@
+
+
+
+
+
+
+
+
+
+ {{ _(tab.titleKey) }}
+
+
+
+
+
+
+
+
+
+
+
+
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/