220 lines
7.4 KiB
TypeScript
220 lines
7.4 KiB
TypeScript
/**
|
|
* ComfyUI MagnifyGlass - Info Panel Event Manager (TypeScript)
|
|
*
|
|
* Complete Event Manager extracted from magnify_info_panel.js
|
|
* Handles all event binding and delegation for the info panel.
|
|
*/
|
|
|
|
import { StateManager } from './StateManager';
|
|
import { UIManager } from './UIManager'; // Kept for type compatibility if needed, though we use HTMLElement mostly
|
|
import { PositionManager } from './PositionManager';
|
|
import { isUserTyping } from '../shared/utils';
|
|
|
|
export interface EventCallbacks {
|
|
toggleVisibility: () => void;
|
|
togglePin: () => void;
|
|
updatePosition: () => void;
|
|
}
|
|
|
|
/**
|
|
* Event Manager class.
|
|
* Handles all event listeners for the info panel.
|
|
*/
|
|
export class EventManager {
|
|
stateManager: StateManager;
|
|
panelElement: HTMLElement | null;
|
|
positionManager: PositionManager;
|
|
callbacks: EventCallbacks | null;
|
|
|
|
dragCleanup: (() => void) | null;
|
|
|
|
boundHandleMouseMove: (e: MouseEvent) => void;
|
|
boundHandleKeyDown: (e: KeyboardEvent) => void;
|
|
boundHandleKeyUp: (e: KeyboardEvent) => void;
|
|
|
|
constructor(stateManager: StateManager, panelElement: HTMLElement | null, positionManager: PositionManager, callbacks?: EventCallbacks) {
|
|
this.stateManager = stateManager;
|
|
this.panelElement = panelElement;
|
|
this.positionManager = positionManager;
|
|
this.callbacks = callbacks || null;
|
|
this.dragCleanup = null;
|
|
|
|
this.boundHandleMouseMove = this.handleMouseMove.bind(this);
|
|
this.boundHandleKeyDown = this.handleKeyDown.bind(this);
|
|
this.boundHandleKeyUp = this.handleKeyUp.bind(this);
|
|
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
setupEventListeners(): void {
|
|
this.setupPanelEvents();
|
|
this.setupHotkeyEvents();
|
|
this.setupDragEvents();
|
|
this.setupHoverEvents();
|
|
}
|
|
|
|
setupPanelEvents(): void {
|
|
if (!this.panelElement) return;
|
|
|
|
// Vue handles most internal events now.
|
|
// We can keep specific global-ish listeners if needed here.
|
|
}
|
|
|
|
setupHotkeyEvents(): void {
|
|
document.addEventListener('keydown', this.boundHandleKeyDown);
|
|
}
|
|
|
|
setupDragEvents(): void {
|
|
if (!this.panelElement) return;
|
|
|
|
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 (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;
|
|
const rect = this.panelElement.getBoundingClientRect();
|
|
const startLeft = rect.left;
|
|
const startTop = rect.top;
|
|
|
|
// 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;
|
|
|
|
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);
|
|
|
|
// Restore visual state
|
|
if (this.panelElement) {
|
|
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);
|
|
document.addEventListener('mouseup', onMouseUp);
|
|
};
|
|
|
|
this.panelElement.addEventListener('mousedown', startDrag);
|
|
|
|
this.dragCleanup = () => {
|
|
if (this.panelElement) this.panelElement.removeEventListener('mousedown', startDrag);
|
|
};
|
|
}
|
|
|
|
setupHoverEvents(): void {
|
|
if (!this.panelElement) return;
|
|
|
|
this.panelElement.addEventListener('mouseenter', () => {
|
|
this.stateManager.state.isPanelHovered = true;
|
|
this.stateManager.clearAutoExpandTimer();
|
|
});
|
|
|
|
this.panelElement.addEventListener('mouseleave', () => {
|
|
this.stateManager.state.isPanelHovered = false;
|
|
if (!this.stateManager.state.isHoveringNode) {
|
|
this.stateManager.scheduleAutoCollapse();
|
|
}
|
|
});
|
|
}
|
|
|
|
handleKeyDown(e: KeyboardEvent): void {
|
|
if (isUserTyping()) return;
|
|
|
|
const settings = this.stateManager.state.settings;
|
|
const key = e.key.toLowerCase();
|
|
// console.log(`[InfoPanel] KeyDown: ${key}`);
|
|
|
|
// Toggle visibility
|
|
if (key === (settings["🔍MagnifyGlass.ToggleHotkey"] as string).toLowerCase()) {
|
|
console.log("[InfoPanel] Toggle visibility hotkey matched!");
|
|
e.preventDefault();
|
|
this.callbacks?.toggleVisibility();
|
|
}
|
|
|
|
// Toggle Pin
|
|
if (key === (settings["🔍MagnifyGlass.PinPanelHotkey"] as string).toLowerCase()) {
|
|
e.preventDefault();
|
|
this.callbacks?.togglePin();
|
|
}
|
|
}
|
|
|
|
handleMouseMove(e: MouseEvent): void {
|
|
// Placeholder
|
|
}
|
|
|
|
handleKeyUp(e: KeyboardEvent): void {
|
|
// Placeholder
|
|
}
|
|
|
|
cleanup(): void {
|
|
document.removeEventListener('keydown', this.boundHandleKeyDown);
|
|
if (this.dragCleanup) this.dragCleanup();
|
|
}
|
|
}
|