feat: Improve info panel dragging with smoother animation, visual feedback, and robust position initialization and fallback logic.
This commit is contained in:
@@ -67,23 +67,24 @@ export class EventManager {
|
||||
setupDragEvents(): void {
|
||||
if (!this.panelElement) return;
|
||||
|
||||
// Simple drag implementation for the Vue panel
|
||||
// The dragging logic could be inside Vue, but if we want to update global state/position, we can do it here.
|
||||
// For now, let's look for the header element.
|
||||
|
||||
// We need to wait for Vue to render the header?
|
||||
// Since we pass the container, and Vue mounts to it, the header might be inside.
|
||||
// We can use event delegation on the container.
|
||||
let isDragging = false;
|
||||
let rafId: number | null = null;
|
||||
let targetX = 0;
|
||||
let targetY = 0;
|
||||
|
||||
const startDrag = (e: MouseEvent) => {
|
||||
if (!this.panelElement) return;
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
// Only allow dragging from header
|
||||
if (!target.closest('.mag-panel-header')) return;
|
||||
// Only allow dragging from header (expand clickable area)
|
||||
const header = target.closest('.panel-header');
|
||||
if (!header) return;
|
||||
if (target.closest('button')) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
isDragging = true;
|
||||
|
||||
const startX = e.clientX;
|
||||
const startY = e.clientY;
|
||||
@@ -91,31 +92,68 @@ export class EventManager {
|
||||
const startLeft = rect.left;
|
||||
const startTop = rect.top;
|
||||
|
||||
// Use requestAnimationFrame for smoother dragging?
|
||||
// Visual feedback - dragging state
|
||||
this.panelElement.style.cursor = 'grabbing';
|
||||
this.panelElement.style.opacity = '0.9';
|
||||
this.panelElement.style.transition = 'none'; // Disable transitions during drag
|
||||
document.body.style.cursor = 'grabbing';
|
||||
document.body.style.userSelect = 'none';
|
||||
|
||||
const onMouseMove = (moveEvent: MouseEvent) => {
|
||||
if (!isDragging) return;
|
||||
|
||||
const dx = moveEvent.clientX - startX;
|
||||
const dy = moveEvent.clientY - startY;
|
||||
|
||||
if (this.panelElement) {
|
||||
this.panelElement.style.left = `${startLeft + dx}px`;
|
||||
this.panelElement.style.top = `${startTop + dy}px`;
|
||||
targetX = startLeft + dx;
|
||||
targetY = startTop + dy;
|
||||
|
||||
// Keep within viewport bounds
|
||||
const panelWidth = this.panelElement?.offsetWidth || 0;
|
||||
const panelHeight = this.panelElement?.offsetHeight || 0;
|
||||
targetX = Math.max(0, Math.min(targetX, window.innerWidth - panelWidth));
|
||||
targetY = Math.max(0, Math.min(targetY, window.innerHeight - panelHeight));
|
||||
|
||||
// Use RAF for smooth updates
|
||||
if (rafId === null) {
|
||||
rafId = requestAnimationFrame(() => {
|
||||
if (this.panelElement && isDragging) {
|
||||
this.panelElement.style.left = `${targetX}px`;
|
||||
this.panelElement.style.top = `${targetY}px`;
|
||||
}
|
||||
rafId = null;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
isDragging = false;
|
||||
|
||||
// Cancel any pending RAF
|
||||
if (rafId !== null) {
|
||||
cancelAnimationFrame(rafId);
|
||||
rafId = null;
|
||||
}
|
||||
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
|
||||
// Save pinned position
|
||||
// Restore visual state
|
||||
if (this.panelElement) {
|
||||
const finalRect = this.panelElement.getBoundingClientRect();
|
||||
this.stateManager.state.pinnedPosition = { x: finalRect.left, y: finalRect.top };
|
||||
// Auto-pin on drag end?
|
||||
if (!this.stateManager.state.isPanelPinned) {
|
||||
// callbacks.togglePin? Or just set state?
|
||||
// Better to let user manually pin, or follow existing logic.
|
||||
}
|
||||
this.panelElement.style.cursor = '';
|
||||
this.panelElement.style.opacity = '';
|
||||
this.panelElement.style.transition = '';
|
||||
|
||||
// Final position update
|
||||
this.panelElement.style.left = `${targetX}px`;
|
||||
this.panelElement.style.top = `${targetY}px`;
|
||||
|
||||
// Save pinned position
|
||||
this.stateManager.state.pinnedPosition = { x: targetX, y: targetY };
|
||||
}
|
||||
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
|
||||
@@ -31,8 +31,20 @@ export class PositionManager {
|
||||
if (!this.panelElement) return;
|
||||
|
||||
let { x, y } = this.stateManager.state.pinnedPosition;
|
||||
const panelWidth = this.panelElement.offsetWidth;
|
||||
const panelHeight = this.panelElement.offsetHeight;
|
||||
|
||||
// If pinnedPosition is uninitialized (0,0), use calculateNormalPosition to get a valid position
|
||||
if (x === 0 && y === 0) {
|
||||
this.calculateNormalPosition();
|
||||
// Save the calculated position as the new pinned position
|
||||
if (this.panelElement) {
|
||||
const rect = this.panelElement.getBoundingClientRect();
|
||||
this.stateManager.state.pinnedPosition = { x: rect.left, y: rect.top };
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const panelWidth = this.panelElement.offsetWidth || 300;
|
||||
const panelHeight = this.panelElement.offsetHeight || 400;
|
||||
const margin = 10;
|
||||
|
||||
const boundedX = Math.max(margin, Math.min(x, window.innerWidth - panelWidth - margin));
|
||||
@@ -47,46 +59,65 @@ export class PositionManager {
|
||||
if (!magnifyGlass || !this.panelElement) return;
|
||||
|
||||
const settings = this.stateManager.state.settings;
|
||||
const panelWidth = settings["🔍MagnifyGlass.InfoPanelWidth"];
|
||||
const panelHeight = this.panelElement.offsetHeight; // Use actual height
|
||||
const panelWidth = settings["🔍MagnifyGlass.InfoPanelWidth"] || 300;
|
||||
const panelHeight = this.panelElement.offsetHeight || 400;
|
||||
|
||||
let left: number;
|
||||
let top: number;
|
||||
const margin = 15;
|
||||
|
||||
// Check for valid glass rect first
|
||||
const glassRect = magnifyGlass.ui.glassDiv?.getBoundingClientRect();
|
||||
const hasValidGlassRect = glassRect && (glassRect.right > 0 || glassRect.top > 0);
|
||||
|
||||
// Check for valid mouse position
|
||||
const mouseX = magnifyGlass.lastKnownMousePosition?.x || 0;
|
||||
const mouseY = magnifyGlass.lastKnownMousePosition?.y || 0;
|
||||
const hasValidMousePosition = mouseX > 0 || mouseY > 0;
|
||||
|
||||
if (!this.stateManager.state.isGlassPreviewVisible) {
|
||||
left = magnifyGlass.lastKnownMousePosition.x - (panelWidth / 2);
|
||||
top = magnifyGlass.lastKnownMousePosition.y - 20;
|
||||
} else {
|
||||
const glassRect = magnifyGlass.ui.glassDiv?.getBoundingClientRect();
|
||||
if (glassRect) {
|
||||
const position = settings["🔍MagnifyGlass.InfoPanelPosition"];
|
||||
switch (position) {
|
||||
case "Right":
|
||||
left = glassRect.right + margin;
|
||||
top = glassRect.top;
|
||||
break;
|
||||
case "Left":
|
||||
left = glassRect.left - panelWidth - margin;
|
||||
top = glassRect.top;
|
||||
break;
|
||||
case "Top":
|
||||
left = glassRect.left;
|
||||
top = glassRect.top - panelHeight - margin;
|
||||
break;
|
||||
case "Bottom":
|
||||
left = glassRect.left;
|
||||
top = glassRect.bottom + margin;
|
||||
break;
|
||||
default:
|
||||
left = glassRect.right + margin;
|
||||
top = glassRect.top;
|
||||
break;
|
||||
}
|
||||
// Glass is hidden, use mouse position
|
||||
if (hasValidMousePosition) {
|
||||
left = mouseX - (panelWidth / 2);
|
||||
top = mouseY - 20;
|
||||
} else {
|
||||
left = magnifyGlass.lastKnownMousePosition.x - (panelWidth / 2);
|
||||
top = magnifyGlass.lastKnownMousePosition.y - 20;
|
||||
// Fallback: right side of screen
|
||||
left = window.innerWidth - panelWidth - 50;
|
||||
top = 100;
|
||||
}
|
||||
} else if (hasValidGlassRect) {
|
||||
// Position relative to glass
|
||||
const position = settings["🔍MagnifyGlass.InfoPanelPosition"];
|
||||
switch (position) {
|
||||
case "Right":
|
||||
left = glassRect!.right + margin;
|
||||
top = glassRect!.top;
|
||||
break;
|
||||
case "Left":
|
||||
left = glassRect!.left - panelWidth - margin;
|
||||
top = glassRect!.top;
|
||||
break;
|
||||
case "Top":
|
||||
left = glassRect!.left;
|
||||
top = glassRect!.top - panelHeight - margin;
|
||||
break;
|
||||
case "Bottom":
|
||||
left = glassRect!.left;
|
||||
top = glassRect!.bottom + margin;
|
||||
break;
|
||||
default:
|
||||
left = glassRect!.right + margin;
|
||||
top = glassRect!.top;
|
||||
break;
|
||||
}
|
||||
} else if (hasValidMousePosition) {
|
||||
// Fall back to mouse position
|
||||
left = mouseX - (panelWidth / 2);
|
||||
top = mouseY - 20;
|
||||
} else {
|
||||
// Final fallback: right side of screen
|
||||
left = window.innerWidth - panelWidth - 50;
|
||||
top = 100;
|
||||
}
|
||||
|
||||
left = Math.max(10, Math.min(left, window.innerWidth - panelWidth - 10));
|
||||
|
||||
@@ -169,11 +169,11 @@ export class UIManager {
|
||||
this.stateManager.state.isGlassPreviewVisible = !this.stateManager.state.isGlassPreviewVisible;
|
||||
this.updateControlStates();
|
||||
// Toggle glass visibility on the magnify glass
|
||||
if (window.magnifyGlass) {
|
||||
if (window.comfyUIMagnifyGlass) {
|
||||
if (this.stateManager.state.isGlassPreviewVisible) {
|
||||
window.magnifyGlass.ui.show();
|
||||
window.comfyUIMagnifyGlass.ui.show();
|
||||
} else {
|
||||
window.magnifyGlass.ui.hide();
|
||||
window.comfyUIMagnifyGlass.ui.hide();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -244,6 +244,16 @@
|
||||
background: linear-gradient(135deg, rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.05));
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
user-select: none;
|
||||
cursor: grab;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.panel-header:hover {
|
||||
background: linear-gradient(135deg, rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.08));
|
||||
}
|
||||
|
||||
.panel-header:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
|
||||
@@ -36,33 +36,68 @@ class EventManager {
|
||||
}
|
||||
setupDragEvents() {
|
||||
if (!this.panelElement) return;
|
||||
let isDragging = false;
|
||||
let rafId = null;
|
||||
let targetX = 0;
|
||||
let targetY = 0;
|
||||
const startDrag = (e) => {
|
||||
if (!this.panelElement) return;
|
||||
const target = e.target;
|
||||
if (!target.closest(".mag-panel-header")) return;
|
||||
const header = target.closest(".panel-header");
|
||||
if (!header) return;
|
||||
if (target.closest("button")) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
isDragging = true;
|
||||
const startX = e.clientX;
|
||||
const startY = e.clientY;
|
||||
const rect = this.panelElement.getBoundingClientRect();
|
||||
const startLeft = rect.left;
|
||||
const startTop = rect.top;
|
||||
this.panelElement.style.cursor = "grabbing";
|
||||
this.panelElement.style.opacity = "0.9";
|
||||
this.panelElement.style.transition = "none";
|
||||
document.body.style.cursor = "grabbing";
|
||||
document.body.style.userSelect = "none";
|
||||
const onMouseMove = (moveEvent) => {
|
||||
var _a, _b;
|
||||
if (!isDragging) return;
|
||||
const dx = moveEvent.clientX - startX;
|
||||
const dy = moveEvent.clientY - startY;
|
||||
if (this.panelElement) {
|
||||
this.panelElement.style.left = `${startLeft + dx}px`;
|
||||
this.panelElement.style.top = `${startTop + dy}px`;
|
||||
targetX = startLeft + dx;
|
||||
targetY = startTop + dy;
|
||||
const panelWidth = ((_a = this.panelElement) == null ? void 0 : _a.offsetWidth) || 0;
|
||||
const panelHeight = ((_b = this.panelElement) == null ? void 0 : _b.offsetHeight) || 0;
|
||||
targetX = Math.max(0, Math.min(targetX, window.innerWidth - panelWidth));
|
||||
targetY = Math.max(0, Math.min(targetY, window.innerHeight - panelHeight));
|
||||
if (rafId === null) {
|
||||
rafId = requestAnimationFrame(() => {
|
||||
if (this.panelElement && isDragging) {
|
||||
this.panelElement.style.left = `${targetX}px`;
|
||||
this.panelElement.style.top = `${targetY}px`;
|
||||
}
|
||||
rafId = null;
|
||||
});
|
||||
}
|
||||
};
|
||||
const onMouseUp = () => {
|
||||
isDragging = false;
|
||||
if (rafId !== null) {
|
||||
cancelAnimationFrame(rafId);
|
||||
rafId = null;
|
||||
}
|
||||
document.removeEventListener("mousemove", onMouseMove);
|
||||
document.removeEventListener("mouseup", onMouseUp);
|
||||
if (this.panelElement) {
|
||||
const finalRect = this.panelElement.getBoundingClientRect();
|
||||
this.stateManager.state.pinnedPosition = { x: finalRect.left, y: finalRect.top };
|
||||
if (!this.stateManager.state.isPanelPinned) ;
|
||||
this.panelElement.style.cursor = "";
|
||||
this.panelElement.style.opacity = "";
|
||||
this.panelElement.style.transition = "";
|
||||
this.panelElement.style.left = `${targetX}px`;
|
||||
this.panelElement.style.top = `${targetY}px`;
|
||||
this.stateManager.state.pinnedPosition = { x: targetX, y: targetY };
|
||||
}
|
||||
document.body.style.cursor = "";
|
||||
document.body.style.userSelect = "";
|
||||
};
|
||||
document.addEventListener("mousemove", onMouseMove);
|
||||
document.addEventListener("mouseup", onMouseUp);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -19,8 +19,16 @@ class PositionManager {
|
||||
applyPinnedPosition() {
|
||||
if (!this.panelElement) return;
|
||||
let { x, y } = this.stateManager.state.pinnedPosition;
|
||||
const panelWidth = this.panelElement.offsetWidth;
|
||||
const panelHeight = this.panelElement.offsetHeight;
|
||||
if (x === 0 && y === 0) {
|
||||
this.calculateNormalPosition();
|
||||
if (this.panelElement) {
|
||||
const rect = this.panelElement.getBoundingClientRect();
|
||||
this.stateManager.state.pinnedPosition = { x: rect.left, y: rect.top };
|
||||
}
|
||||
return;
|
||||
}
|
||||
const panelWidth = this.panelElement.offsetWidth || 300;
|
||||
const panelHeight = this.panelElement.offsetHeight || 400;
|
||||
const margin = 10;
|
||||
const boundedX = Math.max(margin, Math.min(x, window.innerWidth - panelWidth - margin));
|
||||
const boundedY = Math.max(margin, Math.min(y, window.innerHeight - panelHeight - margin));
|
||||
@@ -28,48 +36,58 @@ class PositionManager {
|
||||
this.panelElement.style.top = `${boundedY}px`;
|
||||
}
|
||||
calculateNormalPosition() {
|
||||
var _a;
|
||||
var _a, _b, _c;
|
||||
const magnifyGlass = window.comfyUIMagnifyGlass;
|
||||
if (!magnifyGlass || !this.panelElement) return;
|
||||
const settings = this.stateManager.state.settings;
|
||||
const panelWidth = settings["🔍MagnifyGlass.InfoPanelWidth"];
|
||||
const panelHeight = this.panelElement.offsetHeight;
|
||||
const panelWidth = settings["🔍MagnifyGlass.InfoPanelWidth"] || 300;
|
||||
const panelHeight = this.panelElement.offsetHeight || 400;
|
||||
let left;
|
||||
let top;
|
||||
const margin = 15;
|
||||
const glassRect = (_a = magnifyGlass.ui.glassDiv) == null ? void 0 : _a.getBoundingClientRect();
|
||||
const hasValidGlassRect = glassRect && (glassRect.right > 0 || glassRect.top > 0);
|
||||
const mouseX = ((_b = magnifyGlass.lastKnownMousePosition) == null ? void 0 : _b.x) || 0;
|
||||
const mouseY = ((_c = magnifyGlass.lastKnownMousePosition) == null ? void 0 : _c.y) || 0;
|
||||
const hasValidMousePosition = mouseX > 0 || mouseY > 0;
|
||||
if (!this.stateManager.state.isGlassPreviewVisible) {
|
||||
left = magnifyGlass.lastKnownMousePosition.x - panelWidth / 2;
|
||||
top = magnifyGlass.lastKnownMousePosition.y - 20;
|
||||
} else {
|
||||
const glassRect = (_a = magnifyGlass.ui.glassDiv) == null ? void 0 : _a.getBoundingClientRect();
|
||||
if (glassRect) {
|
||||
const position = settings["🔍MagnifyGlass.InfoPanelPosition"];
|
||||
switch (position) {
|
||||
case "Right":
|
||||
left = glassRect.right + margin;
|
||||
top = glassRect.top;
|
||||
break;
|
||||
case "Left":
|
||||
left = glassRect.left - panelWidth - margin;
|
||||
top = glassRect.top;
|
||||
break;
|
||||
case "Top":
|
||||
left = glassRect.left;
|
||||
top = glassRect.top - panelHeight - margin;
|
||||
break;
|
||||
case "Bottom":
|
||||
left = glassRect.left;
|
||||
top = glassRect.bottom + margin;
|
||||
break;
|
||||
default:
|
||||
left = glassRect.right + margin;
|
||||
top = glassRect.top;
|
||||
break;
|
||||
}
|
||||
if (hasValidMousePosition) {
|
||||
left = mouseX - panelWidth / 2;
|
||||
top = mouseY - 20;
|
||||
} else {
|
||||
left = magnifyGlass.lastKnownMousePosition.x - panelWidth / 2;
|
||||
top = magnifyGlass.lastKnownMousePosition.y - 20;
|
||||
left = window.innerWidth - panelWidth - 50;
|
||||
top = 100;
|
||||
}
|
||||
} else if (hasValidGlassRect) {
|
||||
const position = settings["🔍MagnifyGlass.InfoPanelPosition"];
|
||||
switch (position) {
|
||||
case "Right":
|
||||
left = glassRect.right + margin;
|
||||
top = glassRect.top;
|
||||
break;
|
||||
case "Left":
|
||||
left = glassRect.left - panelWidth - margin;
|
||||
top = glassRect.top;
|
||||
break;
|
||||
case "Top":
|
||||
left = glassRect.left;
|
||||
top = glassRect.top - panelHeight - margin;
|
||||
break;
|
||||
case "Bottom":
|
||||
left = glassRect.left;
|
||||
top = glassRect.bottom + margin;
|
||||
break;
|
||||
default:
|
||||
left = glassRect.right + margin;
|
||||
top = glassRect.top;
|
||||
break;
|
||||
}
|
||||
} else if (hasValidMousePosition) {
|
||||
left = mouseX - panelWidth / 2;
|
||||
top = mouseY - 20;
|
||||
} else {
|
||||
left = window.innerWidth - panelWidth - 50;
|
||||
top = 100;
|
||||
}
|
||||
left = Math.max(10, Math.min(left, window.innerWidth - panelWidth - 10));
|
||||
top = Math.max(10, Math.min(top, window.innerHeight - panelHeight - 10));
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -119,11 +119,11 @@ class UIManager {
|
||||
case "toggle-glass":
|
||||
this.stateManager.state.isGlassPreviewVisible = !this.stateManager.state.isGlassPreviewVisible;
|
||||
this.updateControlStates();
|
||||
if (window.magnifyGlass) {
|
||||
if (window.comfyUIMagnifyGlass) {
|
||||
if (this.stateManager.state.isGlassPreviewVisible) {
|
||||
window.magnifyGlass.ui.show();
|
||||
window.comfyUIMagnifyGlass.ui.show();
|
||||
} else {
|
||||
window.magnifyGlass.ui.hide();
|
||||
window.comfyUIMagnifyGlass.ui.hide();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user