feat: replace hover clipboard icon with interactive copy button and clipboard functionality for info panel values

This commit is contained in:
AEmotionStudio
2026-01-06 00:09:42 -08:00
parent dcab6a749b
commit ee45d627f1
4 changed files with 102 additions and 8 deletions
+32
View File
@@ -890,10 +890,21 @@ export class UIManager {
const clickableClass = item.clickable ? 'clickable-row' : '';
// Only show dropdown arrow for actual dropdowns, not actions like zoom
const dropdownIcon = (item.clickable && item.clickable !== 'zoom') ? '<span class="dropdown-indicator" style="margin-left: 4px; opacity: 0.6; font-size: 10px;">▼</span>' : '';
// Show copy button for string values that are long enough to be worth copying
const rawValue = String(item.value || '');
const showCopyBtn = !item.clickable && rawValue.length > 3 && typeof item.value === 'string';
const copyBtnHtml = showCopyBtn ? `
<button class="copy-btn" data-copy-value="${rawValue.replace(/"/g, '&quot;')}" title="Copy to clipboard">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
</button>` : '';
return `
<div class="info-row ${clickableClass}" ${clickableAttr} ${nodeIdAttr} style="${item.clickable ? 'cursor: pointer;' : ''}">
<span class="info-label">${item.label}</span>
<span class="info-value ${valueClass}" ${valueAttributes}>${value}${dropdownIcon}</span>
${copyBtnHtml}
</div>`;
}).join('')}
</div>
@@ -943,6 +954,27 @@ export class UIManager {
(row as HTMLElement).style.background = '';
});
});
// Add click handlers for copy buttons
const copyButtons = this.elements.content.querySelectorAll('.copy-btn');
copyButtons.forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const value = (btn as HTMLElement).dataset.copyValue || '';
navigator.clipboard.writeText(value).then(() => {
// Show success feedback
btn.classList.add('copied');
const originalHtml = btn.innerHTML;
btn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
setTimeout(() => {
btn.innerHTML = originalHtml;
btn.classList.remove('copied');
}, 1500);
}).catch(err => {
console.error('Failed to copy:', err);
});
});
});
}
/**
+41 -7
View File
@@ -690,15 +690,49 @@
font-style: italic;
}
/* Copy to clipboard functionality */
.info-value[title]:hover::after {
content: '📋';
/* Copy button for values */
.info-row {
position: relative;
}
.copy-btn {
position: absolute;
right: -20px;
top: 50%;
transform: translateY(-50%);
font-size: 12px;
right: 4px;
top: 8px;
background: var(--comfy-input-bg, rgba(60, 60, 70, 0.8));
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 4px;
padding: 4px 6px;
cursor: pointer;
display: none;
align-items: center;
justify-content: center;
opacity: 0.7;
transition: all 0.15s ease;
z-index: 10;
}
.info-row:hover .copy-btn {
display: flex;
}
.copy-btn:hover {
opacity: 1;
background: rgba(80, 80, 90, 0.9);
}
.copy-btn svg {
width: 12px;
height: 12px;
stroke: currentColor;
}
.copy-btn.copied {
border-color: #22c55e;
}
.copy-btn.copied svg {
stroke: #22c55e;
}
/* Smooth loading states */
+28
View File
@@ -638,10 +638,20 @@ class UIManager {
const nodeIdAttr = item.nodeId !== void 0 ? `data-node-id="${item.nodeId}"` : "";
const clickableClass = item.clickable ? "clickable-row" : "";
const dropdownIcon = item.clickable && item.clickable !== "zoom" ? '<span class="dropdown-indicator" style="margin-left: 4px; opacity: 0.6; font-size: 10px;">▼</span>' : "";
const rawValue = String(item.value || "");
const showCopyBtn = !item.clickable && rawValue.length > 3 && typeof item.value === "string";
const copyBtnHtml = showCopyBtn ? `
<button class="copy-btn" data-copy-value="${rawValue.replace(/"/g, "&quot;")}" title="Copy to clipboard">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
</button>` : "";
return `
<div class="info-row ${clickableClass}" ${clickableAttr} ${nodeIdAttr} style="${item.clickable ? "cursor: pointer;" : ""}">
<span class="info-label">${item.label}</span>
<span class="info-value ${valueClass}" ${valueAttributes}>${value}${dropdownIcon}</span>
${copyBtnHtml}
</div>`;
}).join("")}
</div>
@@ -683,6 +693,24 @@ class UIManager {
row.style.background = "";
});
});
const copyButtons = this.elements.content.querySelectorAll(".copy-btn");
copyButtons.forEach((btn) => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const value = btn.dataset.copyValue || "";
navigator.clipboard.writeText(value).then(() => {
btn.classList.add("copied");
const originalHtml = btn.innerHTML;
btn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
setTimeout(() => {
btn.innerHTML = originalHtml;
btn.classList.remove("copied");
}, 1500);
}).catch((err) => {
console.error("Failed to copy:", err);
});
});
});
}
/**
* Update section expansion states.
File diff suppressed because one or more lines are too long