This commit is contained in:
Dennis Postma 2025-04-05 14:20:58 +02:00
parent a773b51ece
commit 22390acfa6
7 changed files with 164 additions and 33 deletions

View File

@ -28,12 +28,23 @@ export function addSprites(newSprites: Sprite[]) {
return; return;
} }
// Log the number of valid sprites being added
logger.info(`Adding ${validSprites.length} valid sprites`);
sprites.value.push(...validSprites); sprites.value.push(...validSprites);
sprites.value.sort((a, b) => a.uploadOrder - b.uploadOrder); sprites.value.sort((a, b) => a.uploadOrder - b.uploadOrder);
// Update cell size before arranging sprites // Update cell size before arranging sprites
logger.info('Updating cell size after adding sprites');
updateCellSize(); updateCellSize();
autoArrangeSprites();
// Only auto-arrange if cell size is valid
if (cellSize && typeof cellSize.width === 'number' && typeof cellSize.height === 'number' && cellSize.width > 0 && cellSize.height > 0) {
logger.info('Auto-arranging sprites');
autoArrangeSprites();
} else {
logger.warn('Skipping auto-arrange due to invalid cell size');
}
} catch (error) { } catch (error) {
logger.error('Error adding sprites:', error); logger.error('Error adding sprites:', error);
} }
@ -44,12 +55,14 @@ export function addSprites(newSprites: Sprite[]) {
*/ */
export function updateCellSize() { export function updateCellSize() {
if (sprites.value.length === 0) { if (sprites.value.length === 0) {
logger.warn('Cannot update cell size: no sprites available');
return; return;
} }
try { try {
let maxWidth = 0; let maxWidth = 0;
let maxHeight = 0; let maxHeight = 0;
let validSpriteCount = 0;
// Find the maximum dimensions across all sprites // Find the maximum dimensions across all sprites
sprites.value.forEach(sprite => { sprites.value.forEach(sprite => {
@ -59,17 +72,29 @@ export function updateCellSize() {
} }
maxWidth = Math.max(maxWidth, sprite.width); maxWidth = Math.max(maxWidth, sprite.width);
maxHeight = Math.max(maxHeight, sprite.height); maxHeight = Math.max(maxHeight, sprite.height);
validSpriteCount++;
}); });
if (maxWidth === 0 || maxHeight === 0) { if (maxWidth === 0 || maxHeight === 0 || validSpriteCount === 0) {
logger.error('Failed to calculate valid cell size'); logger.error('Failed to calculate valid cell size - no valid sprites found');
return; return;
} }
// Add a small buffer to ensure sprites fit completely (optional) // Add a small buffer to ensure sprites fit completely (optional)
const buffer = 0; // Increase if you want padding between sprites const buffer = 0; // Increase if you want padding between sprites
cellSize.width = maxWidth + buffer;
cellSize.height = maxHeight + buffer; // Set cell size with validation
const newWidth = maxWidth + buffer;
const newHeight = maxHeight + buffer;
if (newWidth <= 0 || newHeight <= 0) {
logger.error(`Invalid calculated cell dimensions: ${newWidth}x${newHeight}`);
return;
}
logger.info(`Updating cell size to ${newWidth}x${newHeight}`);
cellSize.width = newWidth;
cellSize.height = newHeight;
// Ensure all sprites are within their cell bounds after resize // Ensure all sprites are within their cell bounds after resize
sprites.value.forEach((sprite, index) => { sprites.value.forEach((sprite, index) => {
@ -100,13 +125,25 @@ export function updateCellSize() {
*/ */
export function autoArrangeSprites() { export function autoArrangeSprites() {
if (sprites.value.length === 0) { if (sprites.value.length === 0) {
logger.warn('No sprites to arrange');
return; return;
} }
try { try {
if (cellSize.width <= 0 || cellSize.height <= 0) { // Ensure cell size is valid before proceeding
if (!cellSize || typeof cellSize.width !== 'number' || typeof cellSize.height !== 'number' || cellSize.width <= 0 || cellSize.height <= 0) {
logger.error('Invalid cell size for auto-arranging', cellSize); logger.error('Invalid cell size for auto-arranging', cellSize);
return;
// Try to update cell size first
updateCellSize();
// Check again after update attempt
if (!cellSize || typeof cellSize.width !== 'number' || typeof cellSize.height !== 'number' || cellSize.width <= 0 || cellSize.height <= 0) {
logger.error('Still invalid cell size after update attempt', cellSize);
return;
}
logger.warn('Cell size was updated and is now valid');
} }
// First update the canvas size to ensure it's large enough // First update the canvas size to ensure it's large enough

View File

@ -5,6 +5,9 @@ import { type Sprite } from '@/application/types';
* Logger utility with consistent error format * Logger utility with consistent error format
*/ */
export const logger = { export const logger = {
info: (message: string, details?: any) => {
console.log(`Spritesheet: ${message}`, details || '');
},
warn: (message: string, details?: any) => { warn: (message: string, details?: any) => {
console.warn(`Spritesheet: ${message}`, details || ''); console.warn(`Spritesheet: ${message}`, details || '');
}, },

View File

@ -1,5 +1,12 @@
<template> <template>
<div @click="openFileDialog" @dragover.prevent="onDragOver" @dragleave="onDragLeave" @drop.prevent="onDrop" class="border-2 border-dashed border-gray-600 rounded-lg p-8 text-center cursor-pointer transition-all" :class="{ 'border-blue-500 bg-blue-500 bg-opacity-5': isDragOver }"> <div
@click="openFileDialog"
@dragover.prevent.stop="onDragOver"
@dragleave.prevent.stop="onDragLeave"
@drop.prevent.stop="onDrop"
class="border-2 border-dashed border-gray-600 rounded-lg p-8 text-center cursor-pointer transition-all"
:class="{ 'border-blue-500 bg-blue-500 bg-opacity-5': isDragOver }"
>
<i class="fas fa-cloud-upload-alt text-blue-500 text-3xl mb-4"></i> <i class="fas fa-cloud-upload-alt text-blue-500 text-3xl mb-4"></i>
<p class="text-gray-400">Drag & drop sprite images here<br />or click to select files</p> <p class="text-gray-400">Drag & drop sprite images here<br />or click to select files</p>
<input type="file" ref="fileInput" multiple accept="image/*" class="hidden" @change="onFileChange" /> <input type="file" ref="fileInput" multiple accept="image/*" class="hidden" @change="onFileChange" />
@ -37,6 +44,8 @@
if (e.dataTransfer?.files.length) { if (e.dataTransfer?.files.length) {
handleFiles(e.dataTransfer.files); handleFiles(e.dataTransfer.files);
} }
e.preventDefault();
e.stopPropagation();
}; };
const onFileChange = (e: Event) => { const onFileChange = (e: Event) => {

View File

@ -158,6 +158,9 @@
if (!canvasEl.value) return; if (!canvasEl.value) return;
// Prevent default to avoid any browser handling that might interfere
e.preventDefault();
const rect = canvasEl.value.getBoundingClientRect(); const rect = canvasEl.value.getBoundingClientRect();
// Adjust coordinates for zoom level // Adjust coordinates for zoom level
const x = (e.clientX - rect.left) / store.zoomLevel.value; const x = (e.clientX - rect.left) / store.zoomLevel.value;
@ -265,8 +268,14 @@
} }
}; };
const handleMouseUp = () => { const handleMouseUp = (e: MouseEvent) => {
store.draggedSprite.value = null; store.draggedSprite.value = null;
// Ensure the event is captured
if (e) {
e.preventDefault();
e.stopPropagation();
}
}; };
const handleMouseOut = () => { const handleMouseOut = () => {
@ -322,14 +331,19 @@
// Regular sprite dragging // Regular sprite dragging
handleMouseDown(e); handleMouseDown(e);
} }
// Ensure the event is captured
e.stopPropagation();
}; };
/** /**
* Handle mouse movement on the canvas for panning and sprite interactions * Handle mouse movement on the canvas for panning and sprite interactions
*/ */
const handleCanvasMouseMove = (e: MouseEvent) => { const handleCanvasMouseMove = (e: MouseEvent) => {
// Always prevent default to ensure consistent behavior
e.preventDefault();
if (isPanning.value && containerEl.value) { if (isPanning.value && containerEl.value) {
e.preventDefault();
// Calculate the distance moved since last position // Calculate the distance moved since last position
const dx = e.clientX - lastPosition.value.x; const dx = e.clientX - lastPosition.value.x;
const dy = e.clientY - lastPosition.value.y; const dy = e.clientY - lastPosition.value.y;
@ -345,11 +359,18 @@
// Handle regular mouse move for sprites and tooltip // Handle regular mouse move for sprites and tooltip
handleMouseMove(e); handleMouseMove(e);
} }
// Ensure the event is captured
e.stopPropagation();
}; };
const handleCanvasMouseUp = () => { const handleCanvasMouseUp = (e: MouseEvent) => {
isPanning.value = false; isPanning.value = false;
handleMouseUp(); handleMouseUp(e);
// Ensure the event is captured
e.preventDefault();
e.stopPropagation();
}; };
const handleCanvasMouseLeave = () => { const handleCanvasMouseLeave = () => {
@ -371,11 +392,15 @@
} }
// Set up mouse events for the canvas // Set up mouse events for the canvas
canvasEl.value.addEventListener('mousedown', handleCanvasMouseDown); canvasEl.value.addEventListener('mousedown', handleCanvasMouseDown, { passive: false });
canvasEl.value.addEventListener('mousemove', handleCanvasMouseMove); canvasEl.value.addEventListener('mousemove', handleCanvasMouseMove, { passive: false });
canvasEl.value.addEventListener('mouseup', handleCanvasMouseUp); canvasEl.value.addEventListener('mouseup', handleCanvasMouseUp, { passive: false });
canvasEl.value.addEventListener('mouseleave', handleCanvasMouseLeave); canvasEl.value.addEventListener('mouseleave', handleCanvasMouseLeave, { passive: false });
canvasEl.value.addEventListener('contextmenu', preventContextMenu); canvasEl.value.addEventListener('contextmenu', preventContextMenu, { passive: false });
// Add global event listeners to ensure drag operations complete even if cursor leaves canvas
window.addEventListener('mousemove', handleCanvasMouseMove, { passive: false });
window.addEventListener('mouseup', handleCanvasMouseUp, { passive: false });
}; };
// Handle window resize to update canvas dimensions // Handle window resize to update canvas dimensions
@ -467,6 +492,10 @@
canvasEl.value.removeEventListener('mouseleave', handleCanvasMouseLeave); canvasEl.value.removeEventListener('mouseleave', handleCanvasMouseLeave);
canvasEl.value.removeEventListener('contextmenu', preventContextMenu); canvasEl.value.removeEventListener('contextmenu', preventContextMenu);
} }
// Remove global event listeners
window.removeEventListener('mousemove', handleCanvasMouseMove);
window.removeEventListener('mouseup', handleCanvasMouseUp);
} catch (error) { } catch (error) {
logger.error('Error during component unmount:', error); logger.error('Error during component unmount:', error);
} }

View File

@ -27,8 +27,8 @@
minHeight: `${store.cellSize.height * previewZoom}px`, minHeight: `${store.cellSize.height * previewZoom}px`,
cursor: previewZoom > 1 ? (isViewportDragging ? 'grabbing' : 'grab') : 'default', cursor: previewZoom > 1 ? (isViewportDragging ? 'grabbing' : 'grab') : 'default',
}" }"
@mousedown="e => startViewportDrag(e, isCanvasDragging)" @mousedown.prevent="e => startViewportDrag(e, isCanvasDragging)"
@wheel="handleCanvasWheel" @wheel.prevent="handleCanvasWheel"
> >
<div <div
class="sprite-wrapper" class="sprite-wrapper"
@ -47,7 +47,7 @@
backgroundPosition: '0 0, 0 5px, 5px -5px, -5px 0px', backgroundPosition: '0 0, 0 5px, 5px -5px, -5px 0px',
backgroundColor: '#2d3748', backgroundColor: '#2d3748',
}" }"
@mousedown.stop="e => startCanvasDrag(e, isViewportDragging, previewZoom)" @mousedown.prevent.stop="e => startCanvasDrag(e, isViewportDragging, previewZoom)"
title="Drag to move sprite within cell" title="Drag to move sprite within cell"
> >
<canvas ref="animCanvas" class="block pixel-art absolute top-0 left-0"></canvas> <canvas ref="animCanvas" class="block pixel-art absolute top-0 left-0"></canvas>
@ -143,7 +143,9 @@
await nextTick(); await nextTick();
// Ensure cell size is valid before proceeding // Ensure cell size is valid before proceeding
if (!store.cellSize || typeof store.cellSize.width !== 'number' || typeof store.cellSize.height !== 'number' || store.cellSize.width <= 0 || store.cellSize.height <= 0) { if (!store.cellSize.value || typeof store.cellSize.value.width !== 'number' || typeof store.cellSize.value.height !== 'number' || store.cellSize.value.width <= 0 || store.cellSize.value.height <= 0) {
console.log('Attempting to update cell size...', store.cellSize.value);
// Try to update cell size if there are sprites // Try to update cell size if there are sprites
if (sprites.value.length > 0) { if (sprites.value.length > 0) {
store.updateCellSize(); store.updateCellSize();
@ -151,8 +153,12 @@
} }
// Check again after update attempt // Check again after update attempt
if (!store.cellSize || typeof store.cellSize.width !== 'number' || typeof store.cellSize.height !== 'number' || store.cellSize.width <= 0 || store.cellSize.height <= 0) { if (!store.cellSize.value || typeof store.cellSize.value.width !== 'number' || typeof store.cellSize.value.height !== 'number' || store.cellSize.value.width <= 0 || store.cellSize.value.height <= 0) {
console.warn('Failed to set valid cell dimensions', store.cellSize.value);
store.showNotification('Invalid cell dimensions. Please check sprite sizes.', 'error'); store.showNotification('Invalid cell dimensions. Please check sprite sizes.', 'error');
return; // Don't proceed if we can't set valid cell dimensions
} else {
console.log('Successfully updated cell size to', store.cellSize.value);
} }
} }
@ -242,6 +248,24 @@
} }
); );
// Watch for changes in cellSize to update the canvas
watch(
() => store.cellSize.value,
newCellSize => {
if (isModalOpen.value && sprites.value.length > 0) {
if (newCellSize && typeof newCellSize.width === 'number' && typeof newCellSize.height === 'number' && newCellSize.width > 0 && newCellSize.height > 0) {
console.log('Cell size changed, updating canvas...', newCellSize);
updateCanvasSize();
updateCanvasContainerSize();
updateFrame();
} else {
console.warn('Invalid cell size detected in watcher', newCellSize);
}
}
},
{ deep: true }
);
// Expose openModal for external use // Expose openModal for external use
defineExpose({ openModal }); defineExpose({ openModal });
</script> </script>

View File

@ -58,6 +58,13 @@ export function useSpritePosition(sprites: Ref<Sprite[]>, currentFrame: Ref<numb
if (sprites.value.length === 0) return; if (sprites.value.length === 0) return;
if (isViewportDragging.value) return; if (isViewportDragging.value) return;
// Validate cell size before starting drag
if (!cellSize.value || typeof cellSize.value.width !== 'number' || typeof cellSize.value.height !== 'number' || cellSize.value.width <= 0 || cellSize.value.height <= 0) {
console.warn('Cannot start drag - invalid cell dimensions', cellSize.value ? `width: ${cellSize.value.width}, height: ${cellSize.value.height}` : 'cellSize is undefined');
showNotification('Cannot drag sprite - invalid cell dimensions', 'error');
return;
}
isCanvasDragging.value = true; isCanvasDragging.value = true;
// Store initial position // Store initial position
@ -66,9 +73,13 @@ export function useSpritePosition(sprites: Ref<Sprite[]>, currentFrame: Ref<numb
y: e.clientY, y: e.clientY,
}; };
// Add event listeners // Add event listeners with passive: false to ensure preventDefault works
window.addEventListener('mousemove', e => handleCanvasDrag(e, previewZoom), { capture: true }); const boundHandleCanvasDrag = (e: MouseEvent) => handleCanvasDrag(e, previewZoom);
window.addEventListener('mouseup', stopCanvasDrag, { capture: true }); window.addEventListener('mousemove', boundHandleCanvasDrag, { capture: true, passive: false });
window.addEventListener('mouseup', stopCanvasDrag, { capture: true, passive: false });
// Store the bound function for later removal
(window as any).__boundHandleCanvasDrag = boundHandleCanvasDrag;
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -82,9 +93,11 @@ export function useSpritePosition(sprites: Ref<Sprite[]>, currentFrame: Ref<numb
const sprite = sprites.value[currentFrame.value]; const sprite = sprites.value[currentFrame.value];
if (!sprite) return; if (!sprite) return;
// Check if cellSize is properly defined // More robust check for cellSize validity
if (!cellSize.value || typeof cellSize.value.width !== 'number' || typeof cellSize.value.height !== 'number') { if (!cellSize.value || typeof cellSize.value.width !== 'number' || typeof cellSize.value.height !== 'number' || cellSize.value.width <= 0 || cellSize.value.height <= 0) {
console.warn('Invalid cell dimensions during drag operation'); console.warn('Invalid cell dimensions during drag operation', cellSize.value ? `width: ${cellSize.value.width}, height: ${cellSize.value.height}` : 'cellSize is undefined');
showNotification('Cannot drag sprite - invalid cell dimensions', 'error');
isCanvasDragging.value = false;
return; return;
} }
@ -127,9 +140,13 @@ export function useSpritePosition(sprites: Ref<Sprite[]>, currentFrame: Ref<numb
if (!isCanvasDragging.value) return; if (!isCanvasDragging.value) return;
isCanvasDragging.value = false; isCanvasDragging.value = false;
window.removeEventListener('mousemove', handleCanvasDrag as any, { capture: true }); // Use the stored bound function for removal
window.removeEventListener('mousemove', (window as any).__boundHandleCanvasDrag, { capture: true });
window.removeEventListener('mouseup', stopCanvasDrag, { capture: true }); window.removeEventListener('mouseup', stopCanvasDrag, { capture: true });
// Clean up the reference
delete (window as any).__boundHandleCanvasDrag;
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
}; };

View File

@ -56,9 +56,9 @@ export function useViewport(animation: Ref<AnimationState>, onUpdateFrame: () =>
y: e.clientY, y: e.clientY,
}; };
// Add temporary event listeners // Add temporary event listeners with passive: false to ensure preventDefault works
window.addEventListener('mousemove', handleViewportDrag); window.addEventListener('mousemove', handleViewportDrag, { passive: false });
window.addEventListener('mouseup', stopViewportDrag); window.addEventListener('mouseup', stopViewportDrag, { passive: false });
// Prevent default to avoid text selection // Prevent default to avoid text selection
e.preventDefault(); e.preventDefault();
@ -67,6 +67,10 @@ export function useViewport(animation: Ref<AnimationState>, onUpdateFrame: () =>
const handleViewportDrag = (e: MouseEvent) => { const handleViewportDrag = (e: MouseEvent) => {
if (!isViewportDragging.value) return; if (!isViewportDragging.value) return;
// Prevent default browser behavior
e.preventDefault();
e.stopPropagation();
const deltaX = e.clientX - viewportDragStart.value.x; const deltaX = e.clientX - viewportDragStart.value.x;
const deltaY = e.clientY - viewportDragStart.value.y; const deltaY = e.clientY - viewportDragStart.value.y;
@ -83,10 +87,18 @@ export function useViewport(animation: Ref<AnimationState>, onUpdateFrame: () =>
}; };
}; };
const stopViewportDrag = () => { const stopViewportDrag = (e: MouseEvent) => {
if (!isViewportDragging.value) return;
isViewportDragging.value = false; isViewportDragging.value = false;
window.removeEventListener('mousemove', handleViewportDrag); window.removeEventListener('mousemove', handleViewportDrag);
window.removeEventListener('mouseup', stopViewportDrag); window.removeEventListener('mouseup', stopViewportDrag);
// Prevent default browser behavior
if (e) {
e.preventDefault();
e.stopPropagation();
}
}; };
const handleCanvasWheel = (e: WheelEvent) => { const handleCanvasWheel = (e: WheelEvent) => {