246 lines
9.4 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
});
|