Files
ComfyUI-Lora-Manager/web/comfyui/lm_widgets.js
2025-02-28 21:13:37 +08:00

200 lines
6.0 KiB
JavaScript

export function addTagsWidget(node, name, opts, callback) {
// Create container for tags
const container = document.createElement("div");
container.className = "comfy-tags-container";
Object.assign(container.style, {
display: "flex",
flexWrap: "wrap",
gap: "8px",
padding: "6px",
minHeight: "30px",
backgroundColor: "rgba(40, 44, 52, 0.6)", // Darker, more modern background
borderRadius: "6px", // Slightly larger radius
width: "100%",
});
// Initialize default value
const defaultValue = opts?.defaultVal || "[]";
// Parse trigger words and states from string
const parseTagsValue = (value) => {
if (!value) return [];
try {
return JSON.parse(value);
} catch (e) {
// If it's not valid JSON, try legacy format or return empty array
console.warn("Invalid tags data format", e);
return [];
}
};
// Format tags data back to string
const formatTagsValue = (tagsData) => {
return JSON.stringify(tagsData);
};
// Function to render tags from data
const renderTags = (value, widget) => {
// Clear existing tags
while (container.firstChild) {
container.removeChild(container.firstChild);
}
// Parse the tags data
const tagsData = parseTagsValue(value);
tagsData.forEach((tagData) => {
const { text, active } = tagData;
const tagEl = document.createElement("div");
tagEl.className = "comfy-tag";
updateTagStyle(tagEl, active);
tagEl.textContent = text;
tagEl.title = text; // Set tooltip for full content
// Add click handler to toggle state
tagEl.addEventListener("click", (e) => {
e.stopPropagation();
// Toggle active state for this tag
const tagsData = parseTagsValue(widget.value);
const tagIndex = tagsData.findIndex((t) => t.text === text);
if (tagIndex >= 0) {
tagsData[tagIndex].active = !tagsData[tagIndex].active;
updateTagStyle(tagEl, tagsData[tagIndex].active);
// Update value and trigger widget callback
const newValue = formatTagsValue(tagsData);
widget.value = newValue;
widget.callback?.(newValue);
}
});
container.appendChild(tagEl);
});
};
// Helper function to update tag style based on active state
function updateTagStyle(tagEl, active) {
const baseStyles = {
padding: "6px 12px", // 水平内边距从16px减小到12px
borderRadius: "6px", // Matching container radius
maxWidth: "200px", // Increased max width
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
fontSize: "13px", // Slightly larger font
cursor: "pointer",
transition: "all 0.2s ease", // Smoother transition
border: "1px solid transparent",
display: "inline-block",
boxShadow: "0 1px 2px rgba(0,0,0,0.1)",
margin: "4px", // 从6px减小到4px
};
if (active) {
Object.assign(tagEl.style, {
...baseStyles,
backgroundColor: "rgba(66, 153, 225, 0.9)", // Modern blue
color: "white",
borderColor: "rgba(66, 153, 225, 0.9)",
});
} else {
Object.assign(tagEl.style, {
...baseStyles,
backgroundColor: "rgba(45, 55, 72, 0.7)", // Darker inactive state
color: "rgba(226, 232, 240, 0.8)", // Lighter text for contrast
borderColor: "rgba(226, 232, 240, 0.2)",
});
}
// Add hover effect
tagEl.onmouseenter = () => {
tagEl.style.transform = "translateY(-1px)";
tagEl.style.boxShadow = "0 2px 4px rgba(0,0,0,0.15)";
};
tagEl.onmouseleave = () => {
tagEl.style.transform = "translateY(0)";
tagEl.style.boxShadow = "0 1px 2px rgba(0,0,0,0.1)";
};
}
// Store the value in a variable to avoid recursion
let widgetValue = defaultValue;
// Create widget with initial properties
const widget = node.addDOMWidget(name, "tags", container, {
getValue: function() {
return widgetValue;
},
setValue: function(v) {
// Format the incoming value if it's not in the expected JSON format
let parsedValue = v;
try {
// Try to parse as JSON first
if (typeof v === "string" && (v.startsWith("[") || v.startsWith("{"))) {
JSON.parse(v);
// If no error, it's already valid JSON
parsedValue = v;
} else if (typeof v === "string") {
// If it's a comma-separated string of trigger words, convert to tag format
const triggerWords = v
.split(",")
.map((word) => word.trim())
.filter((word) => word);
// Get existing tags to merge with new ones
const existingTags = parseTagsValue(widgetValue || "[]");
const existingTagsMap = {};
existingTags.forEach((tag) => {
existingTagsMap[tag.text] = tag.active;
});
// Create new tags with merging logic
const newTags = triggerWords.map((word) => ({
text: word,
active: word in existingTagsMap ? existingTagsMap[word] : true,
}));
parsedValue = JSON.stringify(newTags);
}
} catch (e) {
console.warn("Error formatting tags value:", e);
// Keep the original value if there's an error
}
widgetValue = parsedValue || ""; // Store in our local variable instead
renderTags(widgetValue, widget);
},
getHeight: function() {
// Calculate height based on content
return Math.max(
150,
Math.ceil(container.scrollHeight / 5) * 5 // Round up to nearest 5px
);
},
onDraw: function() {
// Empty function
}
});
// Initialize widget value using options methods
widget.options.setValue(defaultValue);
widget.callback = callback;
// Render initial state
renderTags(widgetValue, widget);
widget.onRemove = () => {
container.remove();
};
return { minWidth: 300, minHeight: 30, widget };
}