Files
arcum42-ComfyUI_SageUtils/js/components/display.js
T

292 lines
10 KiB
JavaScript

// Widget utilities for Sage Utils nodes
// This file contains common widget creation and manipulation functions
import { ComfyWidgets } from "../../../../scripts/widgets.js";
import { renderMarkdown, ensureMarkdownStyles } from "../shared/markdown.js";
import { loadComponentStyles as ensureComponentStyles } from './styleLoader.js';
console.log('[SageUtils] display.js imported');
function loadComponentStyles() {
ensureComponentStyles('display.js');
}
/**
* Creates a basic text output widget for a node.
* @param {Object} node - The node instance.
* @param {Object} app - The ComfyUI app instance.
* @param {string} widgetName - The name of the widget (default: "output").
* @returns {Object} - The created widget.
*/
export function createTextOutputWidget(node, app, widgetName = "output") {
let w = node.widgets?.find((w) => w.name === widgetName);
if (!w) {
w = ComfyWidgets["STRING"](
node,
widgetName,
["STRING", { multiline: true }],
app
).widget;
w.inputEl.readOnly = true;
w.inputEl.classList.add('sage-text-output-widget');
}
return w;
}
/**
* Updates a text widget with content, handling both arrays and strings.
* @param {Object} widget - The widget to update.
* @param {Object} message - The message object containing text.
*/
export function updateTextWidget(widget, message) {
// Defensive: handle message["text"] as array or string
if (Array.isArray(message["text"])) {
widget.value = message["text"].join("");
} else if (typeof message["text"] === "string") {
widget.value = message["text"];
} else {
widget.value = String(message["text"] ?? "");
}
}
/**
* Sets up proper HTML markdown display for a text widget.
* @param {Object} widget - The text widget.
* @param {string} content - The markdown content to render.
*/
export function setupMarkdownDisplay(widget, content) {
// Set the textarea value for fallback
widget.value = content;
console.log("Setting up markdown display, content length:", content.length);
// Ensure widget is properly initialized
if (!widget.inputEl) {
console.warn("Widget inputEl not available, retrying in 100ms...");
setTimeout(() => {
if (widget.inputEl) {
setupMarkdownDisplay(widget, content);
} else {
console.error("Widget still not initialized after delay, falling back to text display");
}
}, 100);
return;
}
if (!widget.inputEl.parentElement) {
console.warn("Widget parentElement not available, retrying in 100ms...");
setTimeout(() => {
if (widget.inputEl && widget.inputEl.parentElement) {
setupMarkdownDisplay(widget, content);
} else {
console.error("Widget parent still not available after delay, falling back to text display");
}
}, 100);
return;
}
console.log("Widget is properly initialized, proceeding with markdown setup");
// Remove any existing markdown overlay
if (widget.markdownOverlay) {
widget.markdownOverlay.remove();
widget.markdownOverlay = null;
widget.inputEl?.classList.remove('sage-hidden-input');
}
loadComponentStyles();
// Create markdown display overlay
const markdownDiv = document.createElement('div');
markdownDiv.classList.add('markdown-overlay');
const renderedHTML = renderMarkdown(content);
console.log("Rendered HTML length:", renderedHTML.length);
markdownDiv.innerHTML = renderedHTML;
console.log("Created markdown div with HTML:", markdownDiv.innerHTML.substring(0, 200) + "...");
// Hide the textarea
widget.inputEl.classList.add('sage-hidden-input');
console.log("Hiding textarea for markdown overlay");
// Make sure the parent has position relative for absolute positioning
const parent = widget.inputEl.parentElement;
if (getComputedStyle(parent).position === 'static') {
parent.classList.add('sage-overlay-parent');
}
// Insert the overlay
parent.appendChild(markdownDiv);
console.log("Appended markdown overlay to parent, parent children count:", parent.children.length);
// Store reference for cleanup
widget.markdownOverlay = markdownDiv;
// Add styles for markdown elements
ensureMarkdownStyles();
// Add a small delay to ensure the overlay is properly rendered
setTimeout(() => {
console.log("Final check - overlay visibility:", getComputedStyle(markdownDiv).visibility);
console.log("Final check - overlay display:", getComputedStyle(markdownDiv).display);
console.log("Final check - overlay z-index:", getComputedStyle(markdownDiv).zIndex);
}, 100);
}
/**
* Sets up image display for a text widget.
* @param {Object} widget - The text widget.
* @param {string} filename - The image filename to display.
*/
export function setupImageDisplay(widget, filename) {
// Set the textarea value to show filename
widget.value = `Displaying image: ${filename}`;
if (widget.inputEl && widget.inputEl.parentElement) {
// Remove any existing overlays
const existingOverlay = widget.inputEl.parentElement.querySelector('.markdown-overlay, .image-overlay, .video-overlay');
if (existingOverlay) {
existingOverlay.remove();
widget.inputEl.classList.remove('sage-hidden-input');
}
// Create image display overlay
const imageDiv = document.createElement('div');
imageDiv.className = 'image-overlay';
loadComponentStyles();
// Create image element
const img = document.createElement('img');
img.classList.add('display-overlay-media');
// Construct the URL for the image in the notes directory
img.src = `/sage_utils/read_note?filename=${encodeURIComponent(filename)}`;
img.alt = filename;
imageDiv.appendChild(img);
// Hide the textarea
widget.inputEl.classList.add('sage-hidden-input');
// Make sure the parent has position relative for absolute positioning
const parent = widget.inputEl.parentElement;
if (getComputedStyle(parent).position === 'static') {
parent.classList.add('sage-overlay-parent');
}
// Insert the overlay
parent.appendChild(imageDiv);
// Store reference for cleanup
widget.imageOverlay = imageDiv;
// Handle image load errors
img.onerror = function() {
imageDiv.innerHTML = `
<div class="display-error-container">
<p class="display-error-heading">Failed to load image: ${filename}</p>
<p class="display-error-text">Image may not exist or format may not be supported</p>
</div>
`;
};
// Handle successful image load
img.onload = function() {
console.log(`Successfully loaded image: ${filename}`);
};
}
}
/**
* Sets up video display for a text widget.
* @param {Object} widget - The text widget.
* @param {string} filename - The video filename to display.
* @param {boolean} isSupported - Whether the video format is browser-supported.
*/
export function setupVideoDisplay(widget, filename, isSupported = true) {
// Set the textarea value to show filename
widget.value = `Displaying video: ${filename}`;
if (widget.inputEl && widget.inputEl.parentElement) {
// Remove any existing overlays
const existingOverlay = widget.inputEl.parentElement.querySelector('.markdown-overlay, .image-overlay, .video-overlay');
if (existingOverlay) {
existingOverlay.remove();
widget.inputEl.classList.remove('sage-hidden-input');
}
// Create video display overlay
const videoDiv = document.createElement('div');
videoDiv.className = 'video-overlay';
if (!isSupported) {
// Show format not supported message
const extension = filename.split('.').pop().toUpperCase();
videoDiv.innerHTML = `
<div class="display-error-container">
<h3 class="display-error-heading">Video Format Not Supported</h3>
<p class="display-error-text"><strong>${filename}</strong></p>
<p class="display-error-text">${extension} format is not supported by browsers.</p>
<div class="display-error-box">
<p class="display-error-item display-error-success">✅ Supported formats:</p>
<p class="display-error-item">• MP4 (H.264/H.265) - Best compatibility</p>
<p class="display-error-item">• WebM (VP8/VP9) - Good for web</p>
<p class="display-error-item">• OGG (Theora) - Open source</p>
<p class="display-error-item">• M4V - Apple format</p>
<p class="display-error-item display-error-warning">❌ Unsupported formats:</p>
<p class="display-error-item">• MKV, AVI, MOV, WMV, FLV</p>
</div>
<p class="display-error-text">Convert your video to MP4 or WebM for browser playback.</p>
</div>
`;
} else {
// Create video element for supported formats
const video = document.createElement('video');
video.classList.add('display-overlay-media');
video.controls = true;
video.preload = 'metadata';
video.src = `/sage_utils/read_note?filename=${encodeURIComponent(filename)}`;
videoDiv.appendChild(video);
// Handle video load errors
video.onerror = function() {
videoDiv.innerHTML = `
<div class="display-error-container">
<h3 class="display-error-heading">Failed to load video: ${filename}</h3>
<p class="display-error-text">The file may be corrupted or the codec may not be supported</p>
<p class="display-error-text">Try converting to H.264 MP4 for best compatibility</p>
</div>
`;
};
// Handle successful video load
video.onloadedmetadata = function() {
console.log(`Successfully loaded video: ${filename} (${video.videoWidth}x${video.videoHeight})`);
};
// Handle when video data is loaded
video.onloadeddata = function() {
console.log(`Video data loaded for: ${filename}`);
};
}
// Hide the textarea
widget.inputEl.classList.add('sage-hidden-input');
// Make sure the parent has position relative for absolute positioning
const parent = widget.inputEl.parentElement;
if (getComputedStyle(parent).position === 'static') {
parent.classList.add('sage-overlay-parent');
}
// Insert the overlay
parent.appendChild(videoDiv);
// Store reference for cleanup
widget.videoOverlay = videoDiv;
}
}