// UI components for the mapping panel import { loadControllerData } from './controllerList.js'; import { loadMappingData } from './mappingList.js'; import { loadCommandsData } from './commandsList.js'; import { showNotification } from './notifications.js'; import { contextProvider } from '../core/contextProvider.js'; import { eventBus } from '../core/eventBus.js'; import { app } from "../../../../scripts/app.js"; /** * Toggle the management UI * @param {boolean} [show] - Force show or hide * @param {Object} [mappingEngine] - Optional mappingEngine instance * @returns {HTMLElement|null} - The panel element if shown, null if hidden */ export function toggleMappingUI(show, mappingEngine) { const existingPanel = document.getElementById('controller-mapping-panel'); // Determine if we should show or hide const shouldShow = show !== undefined ? show : !existingPanel; if (existingPanel && !shouldShow) { existingPanel.remove(); eventBus.emit('ui:mappingPanelClosed'); return null; } else if (shouldShow && !existingPanel) { const panel = createMappingPanel(mappingEngine); eventBus.emit('ui:mappingPanelOpened'); return panel; } else if (existingPanel) { return existingPanel; // Already showing } return null; } /** * Create the mapping management panel * @param {Object} [mappingEngine] - Optional mappingEngine instance * @returns {HTMLElement} - The created panel */ export function createMappingPanel(mappingEngine) { // Get mappingEngine from parameter or context const engine = mappingEngine || contextProvider.get('mappingEngine'); // Remove mappings for deleted nodes removeDeletedNodeMappings(engine); // Create the main panel const panel = document.createElement('div'); panel.id = 'controller-mapping-panel'; document.body.appendChild(panel); // Create the header const header = document.createElement('div'); header.id = 'controller-mapping-header'; panel.appendChild(header); // Create logo and title const title = document.createElement('h2'); title.innerHTML = ` Take control of your workflows `; header.appendChild(title); const closeButton = document.createElement('button'); closeButton.id = 'controller-mapping-close'; closeButton.textContent = '×'; closeButton.addEventListener('click', () => { panel.remove(); eventBus.emit('ui:mappingPanelClosed'); }); header.appendChild(closeButton); // Create tabs const tabs = document.createElement('div'); tabs.id = 'controller-mapping-tabs'; panel.appendChild(tabs); const controllersTab = document.createElement('div'); controllersTab.className = 'controller-mapping-tab active'; controllersTab.dataset.tab = 'controllers'; controllersTab.textContent = 'Controllers'; tabs.appendChild(controllersTab); const mappingsTab = document.createElement('div'); mappingsTab.className = 'controller-mapping-tab'; mappingsTab.dataset.tab = 'mappings'; mappingsTab.textContent = 'Mappings'; tabs.appendChild(mappingsTab); // Add a new tab for UI commands const commandsTab = document.createElement('div'); commandsTab.className = 'controller-mapping-tab'; commandsTab.dataset.tab = 'commands'; commandsTab.textContent = 'UI Commands'; tabs.appendChild(commandsTab); // Add a new tab for "About" section const aboutTab = document.createElement('div'); aboutTab.className = 'controller-mapping-tab'; aboutTab.dataset.tab = 'about'; aboutTab.textContent = 'Propaganda'; aboutTab.style.marginLeft = 'auto'; // Push to the right tabs.appendChild(aboutTab); // Create content area const content = document.createElement('div'); content.id = 'controller-mapping-content'; panel.appendChild(content); // Create controllers panel const controllersPanel = document.createElement('div'); controllersPanel.className = 'tab-panel active'; controllersPanel.id = 'controllers-panel'; content.appendChild(controllersPanel); // Create mappings panel const mappingsPanel = document.createElement('div'); mappingsPanel.className = 'tab-panel'; mappingsPanel.id = 'mappings-panel'; content.appendChild(mappingsPanel); // Create commands panel const commandsPanel = document.createElement('div'); commandsPanel.className = 'tab-panel'; commandsPanel.id = 'commands-panel'; content.appendChild(commandsPanel); // Create about panel const aboutPanel = document.createElement('div'); aboutPanel.className = 'tab-panel'; aboutPanel.id = 'about-panel'; // Add about content with proper branding aboutPanel.innerHTML = `
Take control of your ComfyUI workflows

Universal MIDI & Gamepad Mapping for ComfyUI

Control Freak lets you map MIDI controllers and gamepads to ComfyUI widgets and actions, giving you physical control over your image generation workflows.

RYANONTHEINSIDE
`; content.appendChild(aboutPanel); // Add tab switching functionality tabs.querySelectorAll('.controller-mapping-tab').forEach(tab => { tab.addEventListener('click', () => { tabs.querySelectorAll('.controller-mapping-tab').forEach(t => t.classList.remove('active')); tab.classList.add('active'); content.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active')); document.getElementById(`${tab.dataset.tab}-panel`).classList.add('active'); // Emit tab change event eventBus.emit('ui:mappingPanelTabChanged', { tab: tab.dataset.tab }); }); }); // Load data loadControllerData(controllersPanel); loadMappingData(mappingsPanel, engine); loadCommandsData(commandsPanel, engine); // Add event listeners to refresh mappings panel when mappings change const refreshMappings = () => { if (document.getElementById('mappings-panel')) { loadMappingData(mappingsPanel, engine); } }; // Listen for mapping events that should trigger a refresh eventBus.on('mapping:added', refreshMappings); eventBus.on('mapping:deleted', refreshMappings); eventBus.on('mapping:updated', refreshMappings); // Cleanup event listeners when panel is closed eventBus.on('ui:mappingPanelClosed', () => { eventBus.off('mapping:added', refreshMappings); eventBus.off('mapping:deleted', refreshMappings); eventBus.off('mapping:updated', refreshMappings); }); return panel; } /** * Remove mappings for deleted nodes * @param {Object} mappingEngine - The mapping engine instance * @returns {number} - Number of removed mappings */ function removeDeletedNodeMappings(mappingEngine) { // Get the mapping engine from parameter or context const engine = mappingEngine || contextProvider.get('mappingEngine'); if (!engine || !app?.graph?.nodes) return 0; // Get mappings for current profile const mappings = engine.getMappings(); let removed = 0; // Check each widget mapping to see if the node still exists mappings.forEach(mapping => { if (mapping.target.type === 'widget') { const nodeId = mapping.target.nodeId; const node = app.graph.getNodeById(nodeId); // If node doesn't exist, remove the mapping if (!node) { engine.deleteMapping(mapping.id); removed++; } } }); // Save if any mappings were removed if (removed > 0) { engine.saveMappings(); // Emit event for removed mappings eventBus.emit('mappings:cleanedUp', { removed }); } return removed; }