const { assetCategoryManager } = require('../../datasets/assetData'); class AssetMenu { constructor({ manager = assetCategoryManager, softwareType = null } = {}) { this.manager = manager; this.softwareType = softwareType; this.categories = this.manager .listCategories({ withMeta: true }) .reduce((map, meta) => { map[meta.softwareType] = this.manager.getCategory(meta.softwareType); return map; }, {}); } normalizeCategory(key) { const category = this.categories[key]; if (!category) { return null; } return { ...category, label: category.label || category.softwareType || key, suppliers: (category.suppliers || []).map((supplier) => ({ ...supplier, id: supplier.id || supplier.name, types: (supplier.types || []).map((type) => ({ ...type, id: type.id || type.name, models: (type.models || []).map((model) => ({ ...model, id: model.id || model.name, units: model.units || [] })) })) })) }; } resolveCategoryForNode(nodeName) { const keys = Object.keys(this.categories); if (keys.length === 0) { return null; } if (this.softwareType && this.categories[this.softwareType]) { return this.softwareType; } if (nodeName) { const normalized = typeof nodeName === 'string' ? nodeName.toLowerCase() : nodeName; if (normalized && this.categories[normalized]) { return normalized; } } return keys[0]; } getAllMenuData(nodeName) { const categoryKey = this.resolveCategoryForNode(nodeName); const selectedCategories = {}; if (categoryKey && this.categories[categoryKey]) { selectedCategories[categoryKey] = this.normalizeCategory(categoryKey); } return { categories: selectedCategories, defaultCategory: categoryKey }; } getClientInitCode(nodeName) { const htmlCode = this.getHtmlInjectionCode(nodeName); const dataCode = this.getDataInjectionCode(nodeName); const eventsCode = this.getEventInjectionCode(nodeName); const saveCode = this.getSaveInjectionCode(nodeName); return ` // --- AssetMenu for ${nodeName} --- window.EVOLV.nodes.${nodeName}.assetMenu = window.EVOLV.nodes.${nodeName}.assetMenu || {}; ${htmlCode} ${dataCode} ${eventsCode} ${saveCode} window.EVOLV.nodes.${nodeName}.assetMenu.initEditor = function(node) { console.log('Initializing asset properties for ${nodeName}'); this.injectHtml(); this.wireEvents(node); this.loadData(node); }; `; } getDataInjectionCode(nodeName) { return ` // Asset data loader for ${nodeName} window.EVOLV.nodes.${nodeName}.assetMenu.loadData = function(node) { const menuAsset = window.EVOLV.nodes.${nodeName}.menuData.asset || {}; const categories = menuAsset.categories || {}; const defaultCategory = menuAsset.defaultCategory || Object.keys(categories)[0] || null; const elems = { supplier: document.getElementById('node-input-supplier'), type: document.getElementById('node-input-assetType'), model: document.getElementById('node-input-model'), unit: document.getElementById('node-input-unit') }; function populate(selectEl, items = [], selectedValue, mapFn, placeholderText = 'Select...') { const previous = selectEl.value; const mapper = typeof mapFn === 'function' ? mapFn : (value) => ({ value, label: value }); selectEl.innerHTML = ''; const placeholder = document.createElement('option'); placeholder.value = ''; placeholder.textContent = placeholderText; placeholder.disabled = true; placeholder.selected = true; selectEl.appendChild(placeholder); items.forEach((item) => { const option = mapper(item); if (!option || typeof option.value === 'undefined') { return; } const opt = document.createElement('option'); opt.value = option.value; opt.textContent = option.label; selectEl.appendChild(opt); }); if (selectedValue) { selectEl.value = selectedValue; if (!selectEl.value) { selectEl.value = ''; } } else { selectEl.value = ''; } if (selectEl.value !== previous) { selectEl.dispatchEvent(new Event('change')); } } const resolveCategoryKey = () => { if (node.softwareType && categories[node.softwareType]) { return node.softwareType; } if (node.category && categories[node.category]) { return node.category; } return defaultCategory; }; const categoryKey = resolveCategoryKey(); node.category = categoryKey; const activeCategory = categoryKey ? categories[categoryKey] : null; const suppliers = activeCategory ? activeCategory.suppliers : []; populate( elems.supplier, suppliers, node.supplier, (supplier) => ({ value: supplier.id || supplier.name, label: supplier.name }), suppliers.length ? 'Select...' : 'No suppliers available' ); const activeSupplier = suppliers.find( (supplier) => (supplier.id || supplier.name) === node.supplier ); const types = activeSupplier ? activeSupplier.types : []; populate( elems.type, types, node.assetType, (type) => ({ value: type.id || type.name, label: type.name }), activeSupplier ? 'Select...' : 'Awaiting Supplier Selection' ); const activeType = types.find( (type) => (type.id || type.name) === node.assetType ); const models = activeType ? activeType.models : []; populate( elems.model, models, node.model, (model) => ({ value: model.id || model.name, label: model.name }), activeType ? 'Select...' : 'Awaiting Type Selection' ); const activeModel = models.find( (model) => (model.id || model.name) === node.model ); populate( elems.unit, activeModel ? activeModel.units || [] : [], node.unit, (unit) => ({ value: unit, label: unit }), activeModel ? 'Select...' : activeType ? 'Awaiting Model Selection' : 'Awaiting Type Selection' ); }; `; } getEventInjectionCode(nodeName) { return ` // Asset event wiring for ${nodeName} window.EVOLV.nodes.${nodeName}.assetMenu.wireEvents = function(node) { const menuAsset = window.EVOLV.nodes.${nodeName}.menuData.asset || {}; const categories = menuAsset.categories || {}; const defaultCategory = menuAsset.defaultCategory || Object.keys(categories)[0] || null; const elems = { supplier: document.getElementById('node-input-supplier'), type: document.getElementById('node-input-assetType'), model: document.getElementById('node-input-model'), unit: document.getElementById('node-input-unit') }; function populate(selectEl, items = [], selectedValue, mapFn, placeholderText = 'Select...') { const previous = selectEl.value; const mapper = typeof mapFn === 'function' ? mapFn : (value) => ({ value, label: value }); selectEl.innerHTML = ''; const placeholder = document.createElement('option'); placeholder.value = ''; placeholder.textContent = placeholderText; placeholder.disabled = true; placeholder.selected = true; selectEl.appendChild(placeholder); items.forEach((item) => { const option = mapper(item); if (!option || typeof option.value === 'undefined') { return; } const opt = document.createElement('option'); opt.value = option.value; opt.textContent = option.label; selectEl.appendChild(opt); }); if (selectedValue) { selectEl.value = selectedValue; if (!selectEl.value) { selectEl.value = ''; } } else { selectEl.value = ''; } if (selectEl.value !== previous) { selectEl.dispatchEvent(new Event('change')); } } const resolveCategoryKey = () => { if (node.softwareType && categories[node.softwareType]) { return node.softwareType; } if (node.category && categories[node.category]) { return node.category; } return defaultCategory; }; const getActiveCategory = () => { const key = resolveCategoryKey(); return key ? categories[key] : null; }; node.category = resolveCategoryKey(); elems.supplier.addEventListener('change', () => { const category = getActiveCategory(); const supplier = category ? category.suppliers.find( (item) => (item.id || item.name) === elems.supplier.value ) : null; const types = supplier ? supplier.types : []; populate( elems.type, types, node.assetType, (type) => ({ value: type.id || type.name, label: type.name }), supplier ? 'Select...' : 'Awaiting Supplier Selection' ); populate(elems.model, [], '', undefined, 'Awaiting Type Selection'); populate(elems.unit, [], '', undefined, 'Awaiting Type Selection'); }); elems.type.addEventListener('change', () => { const category = getActiveCategory(); const supplier = category ? category.suppliers.find( (item) => (item.id || item.name) === elems.supplier.value ) : null; const type = supplier ? supplier.types.find( (item) => (item.id || item.name) === elems.type.value ) : null; const models = type ? type.models : []; populate( elems.model, models, node.model, (model) => ({ value: model.id || model.name, label: model.name }), type ? 'Select...' : 'Awaiting Type Selection' ); populate( elems.unit, [], '', undefined, type ? 'Awaiting Model Selection' : 'Awaiting Type Selection' ); }); elems.model.addEventListener('change', () => { const category = getActiveCategory(); const supplier = category ? category.suppliers.find( (item) => (item.id || item.name) === elems.supplier.value ) : null; const type = supplier ? supplier.types.find( (item) => (item.id || item.name) === elems.type.value ) : null; const model = type ? type.models.find( (item) => (item.id || item.name) === elems.model.value ) : null; populate( elems.unit, model ? model.units || [] : [], node.unit, (unit) => ({ value: unit, label: unit }), model ? 'Select...' : type ? 'Awaiting Model Selection' : 'Awaiting Type Selection' ); }); }; `; } getHtmlTemplate() { return `