class PhysicalPositionMenu { // 1) Server-side: provide the option groups getAllMenuData() { return { positionGroups: [ { group: 'Positional', options: [ { value: 'upstream', label: '← Upstream', icon: '←'}, { value: 'atEquipment', label: '⊥ in place' , icon: '⊥' }, { value: 'downstream', label: '→ Downstream' , icon: '→' } ] } ], // Distance contexts for each position distanceContexts: { upstream: { description: 'Distance from parent inlet', placeholder: 'e.g., 2.5 (meters before parent)', helpText: 'How far upstream from the parent equipment' }, downstream: { description: 'Distance from parent outlet', placeholder: 'e.g., 3.0 (meters after parent)', helpText: 'How far downstream from the parent equipment' }, atEquipment: { description: 'Distance from parent start', placeholder: 'e.g., 1.2 (meters from start)', helpText: 'Position within the parent equipment boundaries' } } }; } // 2) HTML template (pure markup) getHtmlTemplate() { return `

Physical Position vs parent


`; } // 3) HTML injector getHtmlInjectionCode(nodeName) { const tpl = this.getHtmlTemplate() .replace(/`/g,'\\`').replace(/\$/g,'\\$'); return ` // PhysicalPosition HTML injection for ${nodeName} window.EVOLV.nodes.${nodeName}.positionMenu.injectHtml = function() { const ph = document.getElementById('position-fields-placeholder'); if (ph && !ph.hasChildNodes()) { ph.innerHTML = \`${tpl}\`; } }; `; } // 4) Data-loader injector getDataInjectionCode(nodeName) { return ` // PhysicalPosition data loader for ${nodeName} window.EVOLV.nodes.${nodeName}.positionMenu.loadData = function(node) { const data = window.EVOLV.nodes.${nodeName}.menuData.position; const sel = document.getElementById('node-input-positionVsParent'); const hasDistanceCheck = document.getElementById('node-input-hasDistance'); const distanceInput = document.getElementById('node-input-distance'); const distanceSection = document.getElementById('distance-section'); //Load position options if (sel) { sel.innerHTML = ''; (data.positionGroups||[]).forEach(grp => { const optg = document.createElement('optgroup'); optg.label = grp.group; grp.options.forEach(o=>{ const opt = document.createElement('option'); opt.value = o.value; opt.textContent = o.label; opt.setAttribute('data-icon', o.icon); optg.appendChild(opt); }); sel.appendChild(optg); }); sel.value = node.positionVsParent || 'atEquipment'; } //Load distance values if (hasDistanceCheck) { hasDistanceCheck.checked = node.hasDistance || false; distanceSection.style.display = hasDistanceCheck.checked ? 'block' : 'none'; } if (distanceInput) { distanceInput.value = node.distance || ''; } // Update distance context for current position this.updateDistanceContext(node.positionVsParent || 'atEquipment', data.distanceContexts); }; `; } // 5) (no special events needed, but stub for symmetry) getEventInjectionCode(nodeName) { return ` // PhysicalPosition events for ${nodeName} window.EVOLV.nodes.${nodeName}.positionMenu.wireEvents = function(node) { const positionSel = document.getElementById('node-input-positionVsParent'); const hasDistanceCheck = document.getElementById('node-input-hasDistance'); const distanceSection = document.getElementById('distance-section'); const data = window.EVOLV.nodes.${nodeName}.menuData.position; // Toggle distance section visibility if (hasDistanceCheck && distanceSection) { hasDistanceCheck.addEventListener('change', function() { distanceSection.style.display = this.checked ? 'block' : 'none'; // Clear distance if unchecked if (!this.checked) { const distanceInput = document.getElementById('node-input-distance'); if (distanceInput) { distanceInput.value = ''; } } }); } // Update distance context when position changes if (positionSel) { positionSel.addEventListener('change', function() { const position = this.value; window.EVOLV.nodes.${nodeName}.positionMenu.updateDistanceContext(position, data.distanceContexts); }); } }; // Helper function to update distance context window.EVOLV.nodes.${nodeName}.positionMenu.updateDistanceContext = function(position, contexts) { const distanceInput = document.getElementById('node-input-distance'); const distanceHelp = document.getElementById('distance-help'); const context = contexts && contexts[position]; if (context && distanceInput && distanceHelp) { distanceInput.placeholder = context.placeholder || '0.0'; distanceHelp.textContent = context.helpText || 'Enter distance in meters'; } }; `; } // 6) Save-logic injector getSaveInjectionCode(nodeName) { return ` // PhysicalPosition Save injection for ${nodeName} window.EVOLV.nodes.${nodeName}.positionMenu.saveEditor = function(node) { const sel = document.getElementById('node-input-positionVsParent'); const hasDistanceCheck = document.getElementById('node-input-hasDistance'); const distanceInput = document.getElementById('node-input-distance'); // Save existing position data node.positionVsParent = sel ? sel.value : 'atEquipment'; node.positionLabel = sel ? sel.options[sel.selectedIndex].textContent : 'At Equipment'; node.positionIcon = sel ? sel.options[sel.selectedIndex].getAttribute('data-icon') : 'fa fa-cog'; // Save distance data (NEW) node.hasDistance = hasDistanceCheck ? hasDistanceCheck.checked : false; if (node.hasDistance && distanceInput && distanceInput.value) { node.distance = parseFloat(distanceInput.value) || 0; node.distanceUnit = 'm'; // Fixed to meters for now // Generate distance description based on position const contexts = window.EVOLV.nodes.${nodeName}.menuData.position.distanceContexts; const context = contexts && contexts[node.positionVsParent]; node.distanceDescription = context ? context.description : 'Distance from parent'; } else { // Clear distance data if not specified delete node.distance; delete node.distanceUnit; delete node.distanceDescription; } return true; }; `; } // 7) Compose everything into one client bundle getClientInitCode(nodeName) { const htmlCode = this.getHtmlInjectionCode(nodeName); const dataCode = this.getDataInjectionCode(nodeName); const eventCode = this.getEventInjectionCode(nodeName); const saveCode = this.getSaveInjectionCode(nodeName); return ` // --- PhysicalPositionMenu for ${nodeName} --- window.EVOLV.nodes.${nodeName}.positionMenu = window.EVOLV.nodes.${nodeName}.positionMenu || {}; ${htmlCode} ${dataCode} ${eventCode} ${saveCode} // hook into oneditprepare window.EVOLV.nodes.${nodeName}.positionMenu.initEditor = function(node) { this.injectHtml(); this.loadData(node); this.wireEvents(node); }; `; } } module.exports = PhysicalPositionMenu;