284 lines
9.4 KiB
JavaScript
284 lines
9.4 KiB
JavaScript
/**
|
|
* Audio Wave Analyzer Layout Management Module
|
|
* Handles node sizing, positioning, and resize behavior
|
|
*/
|
|
export class AudioAnalyzerLayout {
|
|
constructor(core) {
|
|
this.core = core;
|
|
this.buttonRowHeight = 44;
|
|
this.interfaceHeight = 420;
|
|
this.nodeBottomPadding = 48;
|
|
this.minInterfaceWidth = 760;
|
|
}
|
|
|
|
getTargetNodeHeight() {
|
|
let requiredHeight = this.interfaceHeight + this.buttonRowHeight + 120;
|
|
if (typeof this.core.node?.computeSize === 'function') {
|
|
requiredHeight = Math.max(requiredHeight, Number(this.core.node.computeSize()?.[1]) || 0);
|
|
} else {
|
|
const widgets = this.core.node?.widgets || [];
|
|
requiredHeight = this.core.widgets.calculateWidgetHeights(widgets) + 80;
|
|
}
|
|
return Math.ceil(requiredHeight + this.nodeBottomPadding);
|
|
}
|
|
|
|
resizeNodeForInterface() {
|
|
// Ensure the ComfyUI node is properly sized to contain our interface
|
|
if (!this.core.node) return;
|
|
|
|
try {
|
|
const requiredWidth = this.minInterfaceWidth;
|
|
const requiredHeight = this.getTargetNodeHeight();
|
|
this.setNodeSize(
|
|
Math.max(this.core.node.size?.[0] || 0, requiredWidth),
|
|
requiredHeight
|
|
);
|
|
|
|
} catch (error) {
|
|
console.error('Failed to resize node for interface:', error);
|
|
}
|
|
}
|
|
|
|
setNodeSize(width, height) {
|
|
if (!this.core.node) return;
|
|
|
|
const nextSize = [
|
|
Math.max(Number(width) || 0, this.minInterfaceWidth),
|
|
Math.ceil(Number(height) || 0),
|
|
];
|
|
|
|
if (typeof this.core.node.setSize === 'function') {
|
|
this.core.node.setSize(nextSize);
|
|
} else {
|
|
this.core.node.size = nextSize;
|
|
if (typeof this.core.node.onResize === 'function') {
|
|
this.core.node.onResize(nextSize);
|
|
}
|
|
}
|
|
|
|
if (this.core.node.graph?.setDirtyCanvas) {
|
|
this.core.node.graph.setDirtyCanvas(true, true);
|
|
}
|
|
}
|
|
|
|
updateNodeLayout() {
|
|
// Force ComfyUI to update the node layout after adding our interface
|
|
if (!this.core.node) return;
|
|
|
|
try {
|
|
// Trigger a layout update
|
|
if (this.core.node.graph && this.core.node.graph.setDirtyCanvas) {
|
|
this.core.node.graph.setDirtyCanvas(true, true);
|
|
}
|
|
|
|
// Schedule a delayed resize to ensure everything is rendered
|
|
setTimeout(() => {
|
|
if (this.core.node.setDirtyCanvas) {
|
|
this.core.node.setDirtyCanvas(true);
|
|
}
|
|
}, 100);
|
|
|
|
} catch (error) {
|
|
console.error('Failed to update node layout:', error);
|
|
}
|
|
}
|
|
|
|
setupNodeResizeHandling() {
|
|
// Setup resize handling
|
|
if (!this.core.node) return;
|
|
|
|
this.core.node.resizable = true;
|
|
|
|
// Store original resize method
|
|
const originalOnResize = this.core.node.onResize;
|
|
|
|
// Custom resize handler that keeps the widget edge-to-edge and rerenders
|
|
// the canvas at the current graph zoom/DPR.
|
|
this.core.node.onResize = (size) => {
|
|
// Prevent infinite resize loops
|
|
if (this.resizing) return;
|
|
this.resizing = true;
|
|
|
|
const constrainedSize = [
|
|
Math.max(size[0], this.minInterfaceWidth),
|
|
Math.max(size[1], this.getTargetNodeHeight())
|
|
];
|
|
|
|
if (originalOnResize) {
|
|
originalOnResize.call(this.core.node, constrainedSize);
|
|
}
|
|
|
|
// Ensure our canvas resizes with the interface
|
|
if (this.core.canvas) {
|
|
this.core.resizeCanvas();
|
|
}
|
|
|
|
setTimeout(() => { this.resizing = false; }, 10);
|
|
};
|
|
|
|
// Store reference to interface for access in node methods
|
|
this.core.node.audioAnalyzerInterface = this.core;
|
|
|
|
// Hook into node removal for cleanup
|
|
const originalOnRemoved = this.core.node.onRemoved;
|
|
this.core.node.onRemoved = () => {
|
|
if (originalOnRemoved) {
|
|
originalOnRemoved.call(this.core.node);
|
|
}
|
|
this.destroy();
|
|
};
|
|
|
|
// Watch for changes in multiline widgets to recalculate height
|
|
this.core.widgets.setupMultilineWidgetWatchers();
|
|
}
|
|
|
|
destroy() {
|
|
// Cleanup when the interface is destroyed
|
|
if (this.positionUpdateInterval) {
|
|
clearInterval(this.positionUpdateInterval);
|
|
this.positionUpdateInterval = null;
|
|
}
|
|
|
|
if (this.core.widget && this.core.widget.element && this.core.widget.element.parentElement) {
|
|
this.core.widget.element.parentElement.removeChild(this.core.widget.element);
|
|
}
|
|
|
|
console.log('🌊 Audio Wave Analyzer Layout destroyed and cleaned up');
|
|
}
|
|
|
|
createMainContainer() {
|
|
// Create main container
|
|
const container = document.createElement('div');
|
|
container.className = 'audio-analyzer-container';
|
|
container.style.cssText = `
|
|
width: 100%;
|
|
height: ${this.interfaceHeight}px;
|
|
min-height: ${this.interfaceHeight}px;
|
|
background: #1a1a1a;
|
|
border: 1px solid #333;
|
|
border-radius: 4px;
|
|
box-sizing: border-box;
|
|
overflow: hidden;
|
|
position: relative;
|
|
font-family: Arial, sans-serif;
|
|
font-size: 12px;
|
|
color: #ffffff;
|
|
`;
|
|
|
|
return container;
|
|
}
|
|
|
|
createCanvas() {
|
|
// Create canvas
|
|
const canvas = document.createElement('canvas');
|
|
canvas.className = 'audio-analyzer-canvas';
|
|
canvas.style.cssText = `
|
|
width: 100%;
|
|
height: 300px;
|
|
background: #1a1a1a;
|
|
display: block;
|
|
cursor: crosshair;
|
|
image-rendering: auto;
|
|
`;
|
|
|
|
return canvas;
|
|
}
|
|
|
|
createControlsContainer() {
|
|
// Create controls container
|
|
const controls = document.createElement('div');
|
|
controls.className = 'audio-analyzer-controls';
|
|
controls.style.cssText = `
|
|
height: 80px;
|
|
background: #2a2a2a;
|
|
border-top: 1px solid #333;
|
|
padding: 8px;
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
`;
|
|
|
|
return controls;
|
|
}
|
|
|
|
createAnalyzeButtonRow() {
|
|
const row = document.createElement('div');
|
|
row.className = 'audio-analyzer-button-row';
|
|
row.style.cssText = `
|
|
width: 100%;
|
|
height: ${this.buttonRowHeight}px;
|
|
min-height: ${this.buttonRowHeight}px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-sizing: border-box;
|
|
pointer-events: auto;
|
|
`;
|
|
|
|
if (this.core.analyzeButton) {
|
|
row.appendChild(this.core.analyzeButton);
|
|
}
|
|
|
|
return row;
|
|
}
|
|
|
|
addContainerToNode(container) {
|
|
// Use ComfyUI's DOM widget as the actual layout element. The old
|
|
// spacer/absolute-position approach drifted with widget heights and zoom.
|
|
try {
|
|
// Ensure the node is properly sized to contain our interface
|
|
this.resizeNodeForInterface();
|
|
|
|
if (typeof this.core.node.addDOMWidget === 'function') {
|
|
const buttonRow = this.createAnalyzeButtonRow();
|
|
const buttonWidget = this.core.node.addDOMWidget('audio_analyzer_button_row', 'div', buttonRow, {
|
|
serialize: false,
|
|
hideOnZoom: false,
|
|
height: this.buttonRowHeight
|
|
});
|
|
|
|
this.core.buttonWidget = buttonWidget;
|
|
this.core.widgets.setupWidgetHeight(buttonWidget, this.buttonRowHeight);
|
|
|
|
const widget = this.core.node.addDOMWidget('audio_analyzer_interface', 'div', container, {
|
|
serialize: false,
|
|
hideOnZoom: false,
|
|
height: this.interfaceHeight
|
|
});
|
|
|
|
this.core.widget = widget;
|
|
this.core.widgets.setupWidgetHeight(widget);
|
|
this.core.spacerWidget = this.core.widgets.insertSpacerWidget();
|
|
} else {
|
|
console.log('🌊 Audio Wave Analyzer: addDOMWidget not available, using fallback');
|
|
return false;
|
|
}
|
|
|
|
this.resizeNodeForInterface();
|
|
|
|
// Ensure node layout is updated
|
|
this.updateNodeLayout();
|
|
|
|
// Setup simplified node resize handling
|
|
this.setupNodeResizeHandling();
|
|
|
|
const finalizeLayout = () => {
|
|
this.resizeNodeForInterface();
|
|
this.core.widgets.recalculateNodeHeight();
|
|
this.core.resizeCanvas();
|
|
};
|
|
requestAnimationFrame(finalizeLayout);
|
|
setTimeout(finalizeLayout, 100);
|
|
|
|
return true;
|
|
|
|
} catch (error) {
|
|
console.error('Failed to add container as widget:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
positionInterfaceOverSpacer() {}
|
|
}
|