Files

426 lines
14 KiB
JavaScript

import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { MultiButtonWidget } from "./multi_button_widget.js";
// 获取input目录的文件列表
async function getInputFileList() {
try {
const response = await fetch('/object_info');
const data = await response.json();
// 从 LoadImage 节点类型获取可用文件列表
const loadImageInfo = data.LoadImage;
if (loadImageInfo && loadImageInfo.input && loadImageInfo.input.required && loadImageInfo.input.required.image) {
return loadImageInfo.input.required.image[0]; // 返回文件列表数组
}
return [];
} catch (error) {
console.error("获取文件列表失败:", error);
return [];
}
}
// 删除图片文件(直接删除,无确认弹窗)
async function deleteImageFile(filename) {
try {
const response = await api.fetchApi('/lg/delete_image', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filename: filename })
});
if (response.status === 200) {
const result = await response.json();
if (result.success) {
console.log(`文件 ${filename} 删除成功`);
return true;
}
} else {
const error = await response.json();
console.error(`删除失败: ${error.error || '未知错误'}`);
return false;
}
} catch (error) {
console.error(`删除文件失败: ${error}`);
return false;
}
}
// 加载最新图片 - 参考PB.js的实现方式
async function loadLatestImage(node, folder_type) {
try {
// 获取指定目录中的最新图片
const res = await api.fetchApi(`/lg/get/latest_image?type=${folder_type}`, { cache: "no-store" });
if (res.status === 200) {
const item = await res.json();
if (item && item.filename) {
// 找到图像小部件
const imageWidget = node.widgets.find(w => w.name === 'image');
if (!imageWidget) return false;
// 直接使用原始文件,不需要复制到input
const displayValue = `${item.filename} [${folder_type}]`;
imageWidget.value = displayValue;
// 通过回调更新预览图像
if (typeof imageWidget.callback === "function") {
imageWidget.callback(displayValue);
}
// 更新画布
app.graph.setDirtyCanvas(true);
return true;
}
}
} catch (error) {
console.error(`加载图像失败: ${error}`);
}
return false;
}
// 扩展ContextMenu以支持图片缩略图和删除功能
function extendContextMenuForThumbnails() {
const originalContextMenu = LiteGraph.ContextMenu;
LiteGraph.ContextMenu = function(values, options) {
const ctx = originalContextMenu.call(this, values, options);
// 检查是否是LG_LoadImage节点的image widget的下拉菜单
if (options?.className === 'dark' && values?.length > 0) {
// 等待DOM更新后处理
requestAnimationFrame(() => {
const currentNode = LGraphCanvas.active_canvas?.current_node;
// 检查是否是LG_LoadImage节点的image widget
if (currentNode?.comfyClass === "LG_LoadImage") {
const imageWidget = currentNode.widgets?.find(w => w.name === 'image');
if (imageWidget && imageWidget.options?.values?.length === values.length) {
// 限制菜单宽度 - 调整为更宽
ctx.root.style.maxWidth = '400px';
ctx.root.style.minWidth = '350px';
// 为每个菜单项添加缩略图和删除按钮
const menuItems = ctx.root.querySelectorAll('.litemenu-entry');
menuItems.forEach((item, index) => {
if (index < values.length) {
const filename = values[index];
addThumbnailAndDeleteToMenuItem(item, filename, currentNode, ctx);
}
});
}
}
});
}
return ctx;
};
// 保持原型链
LiteGraph.ContextMenu.prototype = originalContextMenu.prototype;
}
// 为菜单项添加缩略图和删除按钮
function addThumbnailAndDeleteToMenuItem(menuItem, filename, node, contextMenu) {
// 避免重复添加
if (menuItem.querySelector('.thumbnail-container')) {
return;
}
// 保存原始文本内容
const originalText = menuItem.textContent;
// 清空菜单项内容
menuItem.innerHTML = '';
// 设置菜单项样式为flex布局
menuItem.style.cssText += `
display: flex;
align-items: center;
padding: 6px 12px;
min-height: 48px;
position: relative;
`;
// 创建缩略图容器
const thumbnailContainer = document.createElement('div');
thumbnailContainer.className = 'thumbnail-container';
thumbnailContainer.style.cssText = `
width: 40px;
height: 40px;
margin-right: 10px;
border-radius: 4px;
overflow: hidden;
background: #222;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
border: 1px solid #444;
`;
// 创建缩略图
const thumbnail = document.createElement('img');
thumbnail.style.cssText = `
max-width: 100%;
max-height: 100%;
object-fit: cover;
`;
// 设置图片源
thumbnail.src = `/view?filename=${encodeURIComponent(filename)}&type=input&subfolder=`;
thumbnail.alt = filename;
// 处理图片加载失败
thumbnail.onerror = function() {
thumbnailContainer.innerHTML = `
<span style="
color: #888;
font-size: 10px;
text-align: center;
line-height: 1.2;
">无<br>预览</span>
`;
};
thumbnailContainer.appendChild(thumbnail);
// 创建文件名标签
const textLabel = document.createElement('span');
// 截断长文件名 - 保留前10位和后10位文件名及扩展名
let displayName = originalText;
if (displayName.length > 35) {
// 保留文件扩展名
const lastDotIndex = displayName.lastIndexOf('.');
if (lastDotIndex > 0) {
const name = displayName.substring(0, lastDotIndex);
const extension = displayName.substring(lastDotIndex);
if (name.length > 20) {
// 保留前10位 + ... + 后10位 + 扩展名
const firstPart = name.substring(0, 10);
const lastPart = name.substring(name.length - 10);
displayName = firstPart + '...' + lastPart + extension;
}
} else {
// 没有扩展名的情况,保留前10位和后10位
if (displayName.length > 20) {
const firstPart = displayName.substring(0, 10);
const lastPart = displayName.substring(displayName.length - 10);
displayName = firstPart + '...' + lastPart;
}
}
}
textLabel.textContent = displayName;
textLabel.title = originalText; // 悬停时显示完整文件名
textLabel.style.cssText = `
color: inherit;
font-size: inherit;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
cursor: pointer;
max-width: 280px;
min-width: 0;
`;
// 创建删除按钮 - 扩大点击范围
const deleteButton = document.createElement('button');
deleteButton.innerHTML = '✕';
deleteButton.title = `删除 ${filename}`;
deleteButton.style.cssText = `
width: 28px;
height: 28px;
border: none;
background: transparent;
color: #888;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
margin-left: 8px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.7;
transition: all 0.15s;
padding: 0;
`;
// 删除按钮悬停效果 - 更明显的反馈
deleteButton.addEventListener('mouseenter', () => {
deleteButton.style.opacity = '1';
deleteButton.style.color = '#fff';
deleteButton.style.background = 'rgba(255, 255, 255, 0.15)';
deleteButton.style.transform = 'scale(1.05)';
});
deleteButton.addEventListener('mouseleave', () => {
deleteButton.style.opacity = '0.7';
deleteButton.style.color = '#888';
deleteButton.style.background = 'transparent';
deleteButton.style.transform = 'scale(1)';
});
// 删除按钮点击事件 - 快速删除,无动画延迟
deleteButton.addEventListener('click', async (e) => {
e.stopPropagation(); // 阻止触发菜单项选择
e.preventDefault();
// 立即显示删除中状态
deleteButton.innerHTML = '⋯';
deleteButton.style.pointerEvents = 'none';
deleteButton.style.opacity = '0.5';
// 直接执行删除操作,无确认弹窗
const deleted = await deleteImageFile(filename);
if (deleted) {
// 立即移除菜单项,无动画延迟
if (menuItem.parentNode) {
menuItem.parentNode.removeChild(menuItem);
}
// 更新节点的文件列表
const imageWidget = node.widgets.find(w => w.name === 'image');
if (imageWidget) {
// 重新获取文件列表
const fileList = await getInputFileList();
imageWidget.options.values = fileList;
// 如果删除的是当前选中的文件,选择第一个可用文件
if (imageWidget.value === filename) {
imageWidget.value = fileList.length > 0 ? fileList[0] : '';
// 触发回调更新预览
if (typeof imageWidget.callback === "function") {
imageWidget.callback(imageWidget.value);
}
}
// 更新画布
app.graph.setDirtyCanvas(true);
}
// 检查是否还有剩余菜单项,如果没有则关闭菜单
const remainingItems = contextMenu.root.querySelectorAll('.litemenu-entry');
if (remainingItems.length === 0) {
contextMenu.close();
}
} else {
// 删除失败,恢复按钮状态
deleteButton.innerHTML = '✕';
deleteButton.style.pointerEvents = 'auto';
deleteButton.style.opacity = '0.7';
}
});
// 创建可点击区域(除了删除按钮)
const clickableArea = document.createElement('div');
clickableArea.style.cssText = `
display: flex;
align-items: center;
flex: 1;
cursor: pointer;
`;
clickableArea.appendChild(thumbnailContainer);
clickableArea.appendChild(textLabel);
// 为可点击区域添加选择事件
clickableArea.addEventListener('click', () => {
// 模拟原始菜单项点击
const imageWidget = node.widgets.find(w => w.name === 'image');
if (imageWidget) {
imageWidget.value = filename;
// 触发回调
if (typeof imageWidget.callback === "function") {
imageWidget.callback(filename);
}
// 更新画布
app.graph.setDirtyCanvas(true);
}
// 关闭菜单
contextMenu.close();
});
// 组装菜单项
menuItem.appendChild(clickableArea);
menuItem.appendChild(deleteButton);
// 移除原有的点击事件,因为我们现在有自定义的点击处理
menuItem.onclick = null;
}
app.registerExtension({
name: "Comfy.LG.LoadImageButtons",
init() {
// 扩展ContextMenu以支持缩略图和删除功能
extendContextMenuForThumbnails();
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name !== "LG_LoadImage") return;
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function() {
const result = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
// 使用多按钮组件创建刷新按钮
const refreshWidget = this.addCustomWidget(MultiButtonWidget(app, "Refresh From", {
labelWidth: 80,
buttonSpacing: 4
}, [
{
text: "Temp",
callback: () => {
loadLatestImage(this, "temp").then(success => {
if (success) {
app.graph.setDirtyCanvas(true);
}
});
}
},
{
text: "Output",
callback: () => {
loadLatestImage(this, "output").then(success => {
if (success) {
app.graph.setDirtyCanvas(true);
}
});
}
}
]));
refreshWidget.serialize = false;
return result;
};
}
});
app.registerExtension({
name: "Comfy.LG.LoadImage_V2",
init() {
// 扩展ContextMenu以支持缩略图和删除功能
extendContextMenuForThumbnails();
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name !== "LG_LoadImage_V2") return;
// 保持节点的原始行为,不添加额外的前端功能
// auto_refresh的逻辑现在完全在后端处理
}
});