Files
diodiogod-TTS-Audio-Suite/web/audio_analyzer_widgets.js

276 lines
10 KiB
JavaScript

/**
* Audio Wave Analyzer Widget Management Module
* Handles widget creation, positioning, and spacer management
*/
export class AudioAnalyzerWidgets {
constructor(core) {
this.core = core;
this.interfaceHeight = 420;
this.bottomSpacerHeight = 16;
}
setWidgetHeightSafe(widget, height) {
if (!widget) return;
try {
widget.height = height;
} catch {
// Newer ComfyUI BaseWidget exposes height as getter-only.
}
try {
widget.computedHeight = height;
} catch {
// Ignore readonly implementations.
}
}
setupWidgetHeight(widget, height = this.interfaceHeight) {
// Ensure the widget properly reserves its height in ComfyUI's layout system
const interfaceHeight = height;
// Set the widget to properly reserve space in the layout
this.setWidgetHeightSafe(widget, interfaceHeight);
// Override ComfyUI's height calculation methods
widget.computeSize = function(width) {
return [width || 760, interfaceHeight]; // Reserve full height in layout
};
widget.getHeight = function() {
return interfaceHeight;
};
// Simple draw method that doesn't interfere with positioning
widget.draw = function(ctx, node, widget_width, y, widget_height) {
ctx.fillStyle = 'rgba(42, 42, 42, 0.35)';
ctx.fillRect(0, y, widget_width, Math.min(widget_height, interfaceHeight));
};
// Override mouse handling to ensure proper event coordination
widget.mouse = function(event, pos, node) {
// Let the interface handle its own mouse events
return false; // Don't consume the event
};
// Ensure the DOM element has the correct styling but let ComfyUI position it
if (widget.element) {
widget.element.style.height = interfaceHeight + 'px';
widget.element.style.minHeight = interfaceHeight + 'px';
widget.element.style.display = 'block';
widget.element.style.position = 'relative'; // Let ComfyUI handle positioning
widget.element.style.boxSizing = 'border-box';
widget.element.style.width = '100%';
widget.element.style.maxWidth = 'none';
widget.element.style.minWidth = '100%';
widget.element.style.alignSelf = 'stretch';
widget.element.style.margin = '0';
widget.element.style.padding = '0';
widget.element.style.pointerEvents = 'auto';
if (widget.name === 'audio_analyzer_button_row') {
widget.element.style.height = interfaceHeight + 'px';
widget.element.style.minHeight = interfaceHeight + 'px';
widget.element.style.background = 'transparent';
widget.element.style.display = 'flex';
widget.element.style.justifyContent = 'center';
widget.element.style.alignItems = 'center';
}
}
}
insertSpacerWidget() {
if (!this.core.node.widgets) return null;
const existingSpacerIndex = this.core.node.widgets.findIndex(w => w.name === 'audio_analyzer_spacer');
if (existingSpacerIndex >= 0) {
this.core.node.widgets.splice(existingSpacerIndex, 1);
}
const spacerHeight = this.bottomSpacerHeight;
const spacerWidget = {
type: 'audio_analyzer_bottom_spacer',
name: 'audio_analyzer_spacer',
value: '',
serialize: false,
computedHeight: spacerHeight,
computeSize(width) {
return [width || 760, spacerHeight];
},
getHeight() {
return spacerHeight;
},
draw() {
// Reserve a real ComfyUI layout row below the DOM panel.
},
mouse() {
return false;
}
};
this.core.node.widgets.push(spacerWidget);
return spacerWidget;
}
positionInterfaceOverSpacer() {
// Kept as a no-op for compatibility with older call sites.
}
getWidgetHeight(widget) {
if (widget.name === 'audio_analyzer_interface') {
return 0;
}
if (typeof widget.getHeight === 'function') {
const height = Number(widget.getHeight());
if (Number.isFinite(height) && height > 0) {
return height;
}
}
if (typeof widget.computeSize === 'function') {
const size = widget.computeSize(this.core.node?.size?.[0] || 760);
const height = Number(size?.[1]);
if (Number.isFinite(height) && height > 0) {
return height;
}
}
const explicitHeight = Number(widget.computedHeight || widget.height);
if (Number.isFinite(explicitHeight) && explicitHeight > 0) {
return explicitHeight;
}
if (widget.type === 'string' && widget.options && widget.options.multiline) {
const lines = Math.max(1, (widget.value || '').split('\n').length);
return Math.max(200, lines * 20 + 20);
}
return 30;
}
findNodeElement() {
// Try multiple methods to find the node's DOM element
// Method 1: Look for node by ID
if (this.core.node.id) {
let nodeElement = document.querySelector(`[data-id="${this.core.node.id}"]`);
if (nodeElement) return nodeElement;
}
// Method 2: Look for the node through LiteGraph canvas
try {
const canvas = this.core.node.graph?.canvas;
if (canvas && canvas.canvas) {
// Find the node element within the canvas container
const canvasContainer = canvas.canvas.parentElement;
if (canvasContainer) {
// Look for elements that might be our node
const nodeElements = canvasContainer.querySelectorAll('.litegraph-node, .node, [class*="node"]');
for (let element of nodeElements) {
// This is a rough check - in a real implementation you'd need more specific identification
if (element.textContent && element.textContent.includes('Audio Wave Analyzer')) {
return element;
}
}
}
}
} catch (error) {
console.log('Failed to find node through canvas:', error);
}
// Method 3: Fallback to a container that can handle absolute positioning
return document.body;
}
findInsertPosition() {
// Find position after energy_sensitivity to insert our spacer
if (!this.core.node.widgets) return 0;
const widgets = this.core.node.widgets;
// Always insert at the very end
// console.log(`🎵 Inserting spacer at end position ${widgets.length}`); // Debug: spacer position
return widgets.length;
}
setupMultilineWidgetWatchers() {
// Watch for changes in multiline widgets that might affect node height
if (!this.core.node.widgets) return;
this.core.node.widgets.forEach(widget => {
if (widget.type === 'string' && widget.options && widget.options.multiline) {
// Store original callback
const originalCallback = widget.callback;
// Wrap callback to recalculate height on changes
widget.callback = (value) => {
if (originalCallback) {
originalCallback.call(widget, value);
}
// Recalculate and update node height after a short delay
setTimeout(() => {
this.recalculateNodeHeight();
}, 50);
};
}
});
}
recalculateNodeHeight() {
// Recalculate node height based on current widget content
if (!this.core.node || !this.core.layout) return;
const computedHeight = this.core.layout.getTargetNodeHeight();
// Update node size if height changed significantly
if (this.core.node.size && Math.abs(this.core.node.size[1] - computedHeight) > 1) {
this.core.layout?.setNodeSize?.(
this.core.node.size[0],
computedHeight
);
}
}
calculateWidgetHeights(widgets) {
// Calculate total height needed for widgets
let otherWidgetsHeight = 0;
widgets.forEach(widget => {
otherWidgetsHeight += this.getWidgetHeight(widget);
});
return otherWidgetsHeight;
}
ensureUIVisible() {
// Ensure UI is visible in the spacer area
if (!this.core.container) {
console.log('🎵 UI container not found for visibility check');
return;
}
// Make sure container is visible and positioned correctly
const container = this.core.container;
// Ensure container has proper styling for visibility
container.style.display = 'block';
container.style.visibility = 'visible';
container.style.opacity = '1';
// Add to node's DOM if not already present
if (!container.parentElement) {
// Find the node's DOM element
const nodeElement = document.querySelector(`[data-id="${this.core.node.id}"]`) ||
this.core.node.graph?.canvas?.canvas?.parentElement;
if (nodeElement) {
nodeElement.appendChild(container);
console.log('🌊 Audio Wave Analyzer: Added container to node DOM');
} else {
// Fallback to body
document.body.appendChild(container);
console.log('🌊 Audio Wave Analyzer: Added container to body as fallback');
}
}
}
}