Files
webref/frontend/src/lib/canvas/interactions/drag.ts
Danilo Reyes 3700ba02ea phase 6
2025-11-02 14:03:01 -06:00

185 lines
4.2 KiB
TypeScript

/**
* Image dragging interactions for canvas
* Handles dragging images to reposition them
*/
import Konva from 'konva';
import { selection } from '$lib/stores/selection';
import { get } from 'svelte/store';
export interface DragState {
isDragging: boolean;
startPos: { x: number; y: number } | null;
draggedImageId: string | null;
}
const dragState: DragState = {
isDragging: false,
startPos: null,
draggedImageId: null,
};
/**
* Setup drag interactions for an image
*/
export function setupImageDrag(
image: Konva.Image | Konva.Group,
imageId: string,
onDragMove?: (imageId: string, x: number, y: number) => void,
onDragEnd?: (imageId: string, x: number, y: number) => void
): () => void {
/**
* Handle drag start
*/
function handleDragStart(e: Konva.KonvaEventObject<DragEvent>) {
dragState.isDragging = true;
dragState.startPos = { x: image.x(), y: image.y() };
dragState.draggedImageId = imageId;
// If dragged image is not selected, select it
const selectionState = get(selection);
if (!selectionState.selectedIds.has(imageId)) {
// Check if Ctrl/Cmd key is pressed
if (e.evt.ctrlKey || e.evt.metaKey) {
selection.addToSelection(imageId);
} else {
selection.selectOne(imageId);
}
}
// Set dragging cursor
const stage = image.getStage();
if (stage) {
stage.container().style.cursor = 'grabbing';
}
}
/**
* Handle drag move
*/
function handleDragMove(_e: Konva.KonvaEventObject<DragEvent>) {
if (!dragState.isDragging) return;
const x = image.x();
const y = image.y();
// Call callback if provided
if (onDragMove) {
onDragMove(imageId, x, y);
}
// If multiple images are selected, move them together
const selectionState = get(selection);
if (selectionState.selectedIds.size > 1 && dragState.startPos) {
const deltaX = x - dragState.startPos.x;
const deltaY = y - dragState.startPos.y;
// Update start position for next delta calculation
dragState.startPos = { x, y };
// Dispatch custom event to move other selected images
const stage = image.getStage();
if (stage) {
stage.fire('multiDragMove', {
draggedImageId: imageId,
deltaX,
deltaY,
selectedIds: Array.from(selectionState.selectedIds),
});
}
}
}
/**
* Handle drag end
*/
function handleDragEnd(_e: Konva.KonvaEventObject<DragEvent>) {
if (!dragState.isDragging) return;
const x = image.x();
const y = image.y();
// Call callback if provided
if (onDragEnd) {
onDragEnd(imageId, x, y);
}
// Reset drag state
dragState.isDragging = false;
dragState.startPos = null;
dragState.draggedImageId = null;
// Reset cursor
const stage = image.getStage();
if (stage) {
stage.container().style.cursor = 'default';
}
}
// Enable dragging
image.draggable(true);
// Attach event listeners
image.on('dragstart', handleDragStart);
image.on('dragmove', handleDragMove);
image.on('dragend', handleDragEnd);
// Return cleanup function
return () => {
image.off('dragstart', handleDragStart);
image.off('dragmove', handleDragMove);
image.off('dragend', handleDragEnd);
image.draggable(false);
};
}
/**
* Move image to specific position (programmatic)
*/
export function moveImageTo(
image: Konva.Image | Konva.Group,
x: number,
y: number,
animate: boolean = false
): void {
if (animate) {
// TODO: Add animation support using Konva.Tween
image.to({
x,
y,
duration: 0.3,
easing: Konva.Easings.EaseOut,
});
} else {
image.position({ x, y });
}
}
/**
* Move image by delta (programmatic)
*/
export function moveImageBy(
image: Konva.Image | Konva.Group,
deltaX: number,
deltaY: number,
animate: boolean = false
): void {
const currentX = image.x();
const currentY = image.y();
moveImageTo(image, currentX + deltaX, currentY + deltaY, animate);
}
/**
* Get current drag state (useful for debugging)
*/
export function getDragState(): DragState {
return { ...dragState };
}
/**
* Check if currently dragging
*/
export function isDragging(): boolean {
return dragState.isDragging;
}