Implement clickable icon embedded in node for preset revelation

- Replace button widget with embedded clickable icon in top-right corner of node
- Add circular 📋 icon with purple gradient background and white border
- Implement hover effects with scaling and purple glow
- Add tooltip showing 'Show All Presets' on hover
- Use absolute positioning for seamless integration with node UI
- Add click event protection with stopPropagation()
- Implement fallback system with MutationObserver for DOM element detection
- Add memory management to prevent observer leaks
- Include duplicate prevention for icon replacement

The icon now appears as a native part of the node interface, providing intuitive access to the preset revelation modal.
This commit is contained in:
lum3on
2025-07-15 13:55:00 +00:00
parent 1c06f188c4
commit 9c031f5443
+76 -37
View File
@@ -287,47 +287,86 @@ app.registerExtension({
node.color = "#9968f3";
node.bgcolor = "#2a2a2a";
// Add button to reveal presets
const button = document.createElement('button');
button.textContent = '📋 Show All Presets';
button.style.cssText = `
background: linear-gradient(45deg, #9968f3, #7c4dff);
border: none;
color: white;
padding: 8px 12px;
margin: 5px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
font-weight: bold;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
transition: all 0.2s ease;
`;
button.onmouseover = () => {
button.style.transform = 'translateY(-1px)';
button.style.boxShadow = '0 4px 8px rgba(0,0,0,0.4)';
};
button.onmouseout = () => {
button.style.transform = 'translateY(0)';
button.style.boxShadow = '0 2px 4px rgba(0,0,0,0.3)';
// Create clickable icon in the node title area
const createRevealIcon = () => {
const icon = document.createElement('div');
icon.innerHTML = '📋';
icon.title = 'Show All Presets';
icon.style.cssText = `
position: absolute;
top: 8px;
right: 8px;
width: 24px;
height: 24px;
background: linear-gradient(45deg, #9968f3, #7c4dff);
border: 2px solid #fff;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
box-shadow: 0 2px 6px rgba(0,0,0,0.4);
transition: all 0.2s ease;
z-index: 1000;
user-select: none;
`;
icon.onmouseover = () => {
icon.style.transform = 'scale(1.1)';
icon.style.boxShadow = '0 4px 12px rgba(153, 104, 243, 0.6)';
};
icon.onmouseout = () => {
icon.style.transform = 'scale(1)';
icon.style.boxShadow = '0 2px 6px rgba(0,0,0,0.4)';
};
icon.onclick = (e) => {
e.stopPropagation();
showPresetModal();
};
return icon;
};
button.onclick = showPresetModal;
// Add icon to node after it's rendered
setTimeout(() => {
const nodeElement = node.canvas?.canvas?.parentElement?.querySelector(`[data-id="${node.id}"]`) ||
document.querySelector(`[data-id="${node.id}"]`);
// Add button to the node
if (node.addDOMWidget) {
node.addDOMWidget("button", "reveal_presets", button);
} else {
// Fallback: append to node element
setTimeout(() => {
const nodeElement = document.querySelector(`[data-id="${node.id}"]`);
if (nodeElement) {
nodeElement.appendChild(button);
if (nodeElement) {
// Make sure the node has relative positioning for absolute positioning of icon
nodeElement.style.position = 'relative';
// Remove any existing icon
const existingIcon = nodeElement.querySelector('.preset-reveal-icon');
if (existingIcon) {
existingIcon.remove();
}
}, 100);
}
// Add the new icon
const icon = createRevealIcon();
icon.classList.add('preset-reveal-icon');
nodeElement.appendChild(icon);
} else {
// Fallback: try to find the node element in the DOM
const observer = new MutationObserver((mutations) => {
const nodeEl = document.querySelector(`[data-id="${node.id}"]`);
if (nodeEl) {
nodeEl.style.position = 'relative';
const icon = createRevealIcon();
icon.classList.add('preset-reveal-icon');
nodeEl.appendChild(icon);
observer.disconnect();
}
});
observer.observe(document.body, { childList: true, subtree: true });
// Stop observing after 5 seconds to prevent memory leaks
setTimeout(() => observer.disconnect(), 5000);
}
}, 200);
}
}
});