mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
200 lines
6.0 KiB
JavaScript
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 };
|
|
}
|