Files
2025-04-06 12:54:48 -04:00

246 lines
9.4 KiB
JavaScript

/**
* Main entry point for the Controller Mapping system
* Exports all modules
*/
import { app } from "../../../scripts/app.js";
import { WebMidiController } from './controllers/midi.js';
import { GamepadController } from './controllers/gamepad.js';
import { MappingEngine } from './core/mappingEngine.js';
import { WorkflowIntegration } from './core/workflowIntegration.js';
import { showNotification } from './ui/notifications.js';
import { registerNodeContextMenu } from './ui/contextMenu.js';
import { addControllerButton } from './ui/controllerButton.js';
import { contextProvider } from './core/contextProvider.js';
import { eventBus } from './core/eventBus.js';
import { LearningStateManager } from './core/learningStateManager.js';
// Import UI components after core initialization to avoid circular dependencies
import { showLearningDialog, hideLearningDialog, showControllerDialog } from './ui/dialogs.js';
import { startMappingNode, startMappingUI, cancelLearning, completeMapping as completeStandardMapping } from './core/learningManager.js';
import { highlightNodeParameter } from './handlers/controllerEventHandlers.js';
// import { registerExtension as registerMenuExtension } from './ui/menuIntegration.js'; // No longer needed
import { createMappingList, deleteMapping } from './ui/mappingComponent.js';
import { toggleMappingUI } from './ui/mappingPanel.js';
import { initWidgetHighlighter } from './ui/widgetHighlighter.js';
// Variables to store controller instances for exports
let midiController, gamepadController, mappingEngine;
// Initialize the ControlFreak system
async function initializeControlFreak() {
// Initialize core instances
mappingEngine = new MappingEngine(eventBus);
midiController = new WebMidiController();
gamepadController = new GamepadController();
const learningState = LearningStateManager.getInstance(eventBus);
// Register instances with context provider
contextProvider.register('mappingEngine', mappingEngine);
contextProvider.register('midiController', midiController);
contextProvider.register('gamepadController', gamepadController);
contextProvider.register('eventBus', eventBus);
contextProvider.register('learningState', learningState);
// Initialize UI styles
initializeUI();
// Initialize controller-UI event handlers
initializeControllerUIHandlers();
// Setup workflow event handlers
setupWorkflowEventHandlers();
// Initialize controllers
await midiController.initialize();
gamepadController.initialize();
// Setup controller event listeners
setupControllerEventListeners(midiController, gamepadController);
// Wait until ComfyUI has loaded the graph before syncing with workflow
waitForGraph();
}
// Add UI initialization
function initializeUI() {
console.log("ControlFreak: Style loading initiated via CSS links.");
}
// Initialize direct controller-to-UI handlers
function initializeControllerUIHandlers() {
// Add any other direct UI handlers here if necessary in the future
// For now, it's empty after removing the 'controller:input' handler.
}
// Add a handler for workflow events
function setupWorkflowEventHandlers() {
// Check if we have access to the LiteGraph graph
if (!app || !app.graph) {
console.warn("ControlFreak: Can't set up workflow event handlers - app or graph not available");
return;
}
// Use ComfyUI's registerExtension API for graph events
app.registerExtension({
name: "ControlFreak.WorkflowEvents",
async setup() {
// Nothing to do here
},
async beforeClearGraph() {
const mappingEngine = contextProvider.get('mappingEngine');
if (mappingEngine) {
mappingEngine.mappings = [];
mappingEngine.setActiveProfile('default');
// Clear any stored data
if (app.graph) {
if (!app.graph.extra) app.graph.extra = {};
app.graph.extra.controlFreak = {
activeProfile: 'default',
mappings: []
};
}
// Notify about the reset
eventBus.emit('mappings:reset');
}
},
async graphLoaded() {
// Use a small delay to ensure the graph is fully loaded
setTimeout(() => {
const mappingEngine = contextProvider.get('mappingEngine');
if (mappingEngine && app.graph) {
mappingEngine.loadMappings();
}
}, 300);
}
});
}
// Set up event listeners for controllers
function setupControllerEventListeners(midiController, gamepadController) {
// Setup event listeners for controllers to feed into the mapping engine via event bus
midiController.onMessage(message => {
// Format MIDI message for the mapping engine
const controlInput = {
type: 'midi', // Or more specific like 'midi_cc', 'midi_note'
deviceId: message.deviceId, // Use the actual device ID from the Web MIDI API
controlId: `${message.command}_${message.control}`, // e.g., "176_7" for CC 7
rawValue: message.value,
deviceName: message.deviceName,
name: `MIDI ${message.command === 144 ? 'Note' : 'CC'} ${message.control}`
};
// Publish to event bus
eventBus.emit('controller:input', controlInput);
});
gamepadController.onUpdate(update => {
// Format Gamepad update for the mapping engine
const controlInput = {
type: update.type === 'button' ? 'gamepad_button' : 'gamepad_axis',
deviceId: update.gamepadId, // Use the gamepad ID
controlId: `${update.type}_${update.index}`, // e.g., "button_0", "axis_1"
rawValue: update.value, // This is already correctly processed with deadzone in the gamepad controller
name: `Gamepad ${update.type === 'button' ? 'Button' : 'Axis'} ${update.index}`,
// Include additional data that may be helpful
details: {
raw: update.raw, // Original raw value before deadzone processing
pressed: update.pressed, // For buttons
index: update.index
}
};
// Publish to event bus
eventBus.emit('controller:input', controlInput);
});
// Listen for state changes to update UI (e.g., controller list)
// Use a debounce mechanism to prevent infinite recursion
let midiStateChangePending = false;
midiController.onStateChange((inputs, outputs) => {
if (midiStateChangePending) return;
midiStateChangePending = true;
// Publish to event bus
setTimeout(() => {
eventBus.emit('midi:stateChanged', inputs, outputs);
midiStateChangePending = false;
}, 50);
});
// Use a debounce mechanism for gamepad state changes too
let gamepadStateChangePending = false;
gamepadController.onStateChange(gamepads => {
if (gamepadStateChangePending) return;
gamepadStateChangePending = true;
// Publish to event bus with a small delay to break potential recursion
setTimeout(() => {
eventBus.emit('gamepad:stateChanged', gamepads);
gamepadStateChangePending = false;
}, 50);
});
}
// Wait for graph to be available before loading mappings
function waitForGraph() {
if (app && app.graph && app.graph.nodes) {
try {
const mappingEngine = contextProvider.get('mappingEngine');
if (mappingEngine) {
mappingEngine.loadMappings();
setTimeout(() => {
if (mappingEngine.reconnectMappings) {
mappingEngine.reconnectMappings();
}
}, 500);
setTimeout(() => {
if (mappingEngine.workflowIntegration) {
mappingEngine.workflowIntegration.syncProfileFromWorkflow();
}
}, 500);
}
} catch (error) {
console.error("ControlFreak: Error syncing with workflow:", error);
}
} else {
setTimeout(waitForGraph, 1000); // Try again in 1 second
}
}
// Export necessary components for potential external use
export { contextProvider, eventBus };
export { toggleMappingUI };
export { startMappingNode, startMappingUI };
// Export controller instances for backward compatibility
export { midiController, gamepadController, mappingEngine };
// Initialize the ControlFreak system via the single extension point
app.registerExtension({
name: "Comfy.ControlFreak.Client",
priority: 1000,
async setup(appInstance) {
try {
// Initialize core components first
await initializeControlFreak();
// Initialize all UI components here after core is ready
registerNodeContextMenu(appInstance);
// Add the button using the ComfyUI-Manager approach
await addControllerButton();
// *** INITIALIZE WIDGET HIGHLIGHTER ***
initWidgetHighlighter();
} catch (error) {
console.error("ControlFreak: Error in extension setup:", error);
}
}
});