const AssetMenu = require('./asset.js'); // TagcodeApp and DynamicAssetMenu available via ./tagcodeApp.js const LoggerMenu = require('./logger.js'); const PhysicalPositionMenu = require('./physicalPosition.js'); const AquonSamplesMenu = require('./aquonSamples.js'); const IconHelpers = require('./iconHelpers.js'); const ConfigManager = require('../configs'); class MenuManager { constructor() { this.registeredMenus = new Map(); this.configManager = new ConfigManager('../configs'); // Register factory functions this.registerMenu('asset', (nodeName) => new AssetMenu({ softwareType: this._getSoftwareType(nodeName) })); // static menu to be replaced by dynamic one but later //this.registerMenu('asset', (nodeName) => new DynamicAssetMenu(nodeName, new TagcodeApp())); this.registerMenu('logger', () => new LoggerMenu()); this.registerMenu('position', () => new PhysicalPositionMenu()); this.registerMenu('aquon', () => new AquonSamplesMenu()); } /** * Register a menu type with its handler factory function * @param {string} menuType - The type of menu (e.g., 'asset', 'logging') * @param {function} menuFactory - The menu factory function */ registerMenu(menuType, menuFactory) { this.registeredMenus.set(menuType, menuFactory); } _getSoftwareType(nodeName) { if (!nodeName) { return null; } try { const config = this.configManager.getConfig(nodeName); const softwareType = config?.functionality?.softwareType; if (typeof softwareType === 'string' && softwareType.trim()) { return softwareType; } if ( softwareType && typeof softwareType === 'object' && typeof softwareType.default === 'string' && softwareType.default.trim() ) { return softwareType.default; } return nodeName; } catch (error) { console.warn(`Unable to determine softwareType for ${nodeName}: ${error.message}`); return nodeName; } } /** * Create a complete endpoint script with data and initialization functions * @param {string} nodeName - The name of the node type * @param {Array} menuTypes - Array of menu types to include * @returns {string} Complete JavaScript code to serve */ createEndpoint(nodeName, menuTypes) { try { // ✅ Create instances using factory functions with proper error handling const instantiatedMenus = new Map(); menuTypes.forEach(menuType => { try { const factory = this.registeredMenus.get(menuType); if (typeof factory === 'function') { const instance = factory(nodeName); instantiatedMenus.set(menuType, instance); } else { console.warn(`No factory function found for menu type: ${menuType}`); } } catch (error) { console.error(`Error creating instance for ${menuType}:`, error); } }); // ✅ Collect all menu data with error handling const menuData = {}; menuTypes.forEach(menuType => { try { const handler = instantiatedMenus.get(menuType); if (handler && typeof handler.getAllMenuData === 'function') { menuData[menuType] = handler.getAllMenuData(nodeName); } else { // Provide default empty data if method doesn't exist menuData[menuType] = {}; } } catch (error) { console.error(`Error getting menu data for ${menuType}:`, error); menuData[menuType] = {}; } }); // ✅ Generate HTML injection code with error handling const htmlInjections = menuTypes.map(type => { try { const menu = instantiatedMenus.get(type); if (menu && typeof menu.getHtmlInjectionCode === 'function') { return menu.getHtmlInjectionCode(nodeName); } return ''; } catch (error) { console.error(`Error generating HTML injection for ${type}:`, error); return `// Error generating HTML injection for ${type}: ${error.message}`; } }).join('\n'); // ✅ Collect all client initialization code with error handling const initFunctions = []; menuTypes.forEach(menuType => { try { const handler = instantiatedMenus.get(menuType); if (handler && typeof handler.getClientInitCode === 'function') { initFunctions.push(handler.getClientInitCode(nodeName)); } } catch (error) { console.error(`Error generating init code for ${menuType}:`, error); initFunctions.push(`// Error in ${menuType} initialization: ${error.message}`); } }); // Convert menu data to JSON const menuDataJSON = JSON.stringify(menuData, null, 2); // ✅ Assemble the complete script with comprehensive error handling return ` try { // Create the namespace structure with safety checks window.EVOLV = window.EVOLV || {}; window.EVOLV.nodes = window.EVOLV.nodes || {}; window.EVOLV.nodes.${nodeName} = window.EVOLV.nodes.${nodeName} || {}; // Shared icon-picker helpers (no-op if already loaded by another node) ${IconHelpers.getClientInitCode()} // Initialize menu namespaces ${menuTypes.map(type => `window.EVOLV.nodes.${nodeName}.${type}Menu = window.EVOLV.nodes.${nodeName}.${type}Menu || {};`).join('\n ')} // Inject the pre-loaded menu data directly into the namespace window.EVOLV.nodes.${nodeName}.menuData = ${menuDataJSON}; // HTML injections with error handling try { ${htmlInjections} } catch (htmlError) { console.error('Error in HTML injections for ${nodeName}:', htmlError); } // Initialize functions with error handling try { ${initFunctions.join('\n\n ')} } catch (initError) { console.error('Error in initialization functions for ${nodeName}:', initError); } // Main initialization function that calls all menu initializers window.EVOLV.nodes.${nodeName}.initEditor = function(node) { try { ${menuTypes.map(type => ` try { // initEditor is responsible for calling initVisuals // at the right time (after any async data load). if (window.EVOLV.nodes.${nodeName}.${type}Menu && window.EVOLV.nodes.${nodeName}.${type}Menu.initEditor) { window.EVOLV.nodes.${nodeName}.${type}Menu.initEditor(node); } } catch (${type}Error) { console.error('Error initializing ${type} menu for ${nodeName}:', ${type}Error); }`).join('')} // Platform-wide: upgrade output-format