[FEAT] Add drop event listener for new images
This commit is contained in:
75
src/App.vue
75
src/App.vue
@@ -103,7 +103,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<sprite-canvas :sprites="sprites" :columns="columns" @update-sprite="updateSpritePosition" @update-sprite-cell="updateSpriteCell" @remove-sprite="removeSprite" @replace-sprite="replaceSprite" @add-sprite="addSprite" />
|
||||
<sprite-canvas :sprites="sprites" :columns="columns" @update-sprite="updateSpritePosition" @update-sprite-cell="updateSpriteCell" @remove-sprite="removeSprite" @replace-sprite="replaceSprite" @add-sprite="addSprite" @add-sprite-with-resize="addSpriteWithResize" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -688,6 +688,79 @@
|
||||
img.src = url;
|
||||
};
|
||||
|
||||
const addSpriteWithResize = (file: File) => {
|
||||
const url = URL.createObjectURL(file);
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
// Find current max dimensions
|
||||
let maxWidth = 0;
|
||||
let maxHeight = 0;
|
||||
sprites.value.forEach(sprite => {
|
||||
maxWidth = Math.max(maxWidth, sprite.width);
|
||||
maxHeight = Math.max(maxHeight, sprite.height);
|
||||
});
|
||||
|
||||
// Create new sprite
|
||||
const newSprite: Sprite = {
|
||||
id: crypto.randomUUID(),
|
||||
file,
|
||||
img,
|
||||
url,
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
|
||||
// Calculate new max dimensions after adding the new sprite
|
||||
const newMaxWidth = Math.max(maxWidth, img.width);
|
||||
const newMaxHeight = Math.max(maxHeight, img.height);
|
||||
|
||||
// Resize existing sprites if the new image is larger
|
||||
if (img.width > maxWidth || img.height > maxHeight) {
|
||||
// Update all existing sprites to center them in the new larger cells
|
||||
sprites.value = sprites.value.map(sprite => {
|
||||
let newX = sprite.x;
|
||||
let newY = sprite.y;
|
||||
|
||||
// Adjust x position if width increased
|
||||
if (img.width > maxWidth) {
|
||||
const widthDiff = newMaxWidth - maxWidth;
|
||||
// Try to keep the sprite in the same relative position
|
||||
const relativeX = maxWidth > 0 ? sprite.x / maxWidth : 0;
|
||||
newX = Math.floor(relativeX * newMaxWidth);
|
||||
// Make sure it doesn't go out of bounds
|
||||
newX = Math.max(0, Math.min(newX, newMaxWidth - sprite.width));
|
||||
}
|
||||
|
||||
// Adjust y position if height increased
|
||||
if (img.height > maxHeight) {
|
||||
const heightDiff = newMaxHeight - maxHeight;
|
||||
const relativeY = maxHeight > 0 ? sprite.y / maxHeight : 0;
|
||||
newY = Math.floor(relativeY * newMaxHeight);
|
||||
newY = Math.max(0, Math.min(newY, newMaxHeight - sprite.height));
|
||||
}
|
||||
|
||||
return { ...sprite, x: newX, y: newY };
|
||||
});
|
||||
}
|
||||
|
||||
// Add the new sprite
|
||||
sprites.value = [...sprites.value, newSprite];
|
||||
|
||||
// Force redraw of the canvas
|
||||
setTimeout(() => {
|
||||
const event = new Event('forceRedraw');
|
||||
window.dispatchEvent(event);
|
||||
}, 0);
|
||||
};
|
||||
img.onerror = () => {
|
||||
console.error('Failed to load new sprite image:', file.name);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
img.src = url;
|
||||
};
|
||||
|
||||
// Download as GIF with specified FPS
|
||||
const downloadAsGif = (fps: number) => {
|
||||
if (sprites.value.length === 0) {
|
||||
|
||||
@@ -46,7 +46,12 @@
|
||||
@touchmove="handleTouchMove"
|
||||
@touchend="stopDrag"
|
||||
@contextmenu.prevent
|
||||
class="w-full"
|
||||
@dragover="handleDragOver"
|
||||
@dragenter="handleDragEnter"
|
||||
@dragleave="handleDragLeave"
|
||||
@drop="handleDrop"
|
||||
class="w-full transition-all"
|
||||
:class="{ 'ring-4 ring-blue-500 ring-opacity-50': isDragOver }"
|
||||
:style="settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}"
|
||||
></canvas>
|
||||
|
||||
@@ -119,6 +124,7 @@
|
||||
(e: 'removeSprite', id: string): void;
|
||||
(e: 'replaceSprite', id: string, file: File): void;
|
||||
(e: 'addSprite', file: File): void;
|
||||
(e: 'addSpriteWithResize', file: File): void;
|
||||
}>();
|
||||
|
||||
// Get settings from store
|
||||
@@ -149,6 +155,7 @@
|
||||
const contextMenuSpriteId = ref<string | null>(null);
|
||||
const replacingSpriteId = ref<string | null>(null);
|
||||
const fileInput = ref<HTMLInputElement | null>(null);
|
||||
const isDragOver = ref(false);
|
||||
|
||||
const spritePositions = computed(() => {
|
||||
const { maxWidth, maxHeight } = calculateMaxDimensions();
|
||||
@@ -446,6 +453,88 @@
|
||||
contextMenuSpriteId.value = null;
|
||||
};
|
||||
|
||||
// Drag and drop handlers
|
||||
const handleDragOver = (event: DragEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
isDragOver.value = true;
|
||||
};
|
||||
|
||||
const handleDragEnter = (event: DragEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
isDragOver.value = true;
|
||||
};
|
||||
|
||||
const handleDragLeave = (event: DragEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
// Only set to false if we're leaving the canvas entirely
|
||||
const rect = canvasRef.value?.getBoundingClientRect();
|
||||
if (rect && (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom)) {
|
||||
isDragOver.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDrop = async (event: DragEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
isDragOver.value = false;
|
||||
|
||||
if (!event.dataTransfer?.files || event.dataTransfer.files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const files = Array.from(event.dataTransfer.files).filter(file => file.type.startsWith('image/'));
|
||||
|
||||
if (files.length === 0) {
|
||||
alert('Please drop image files only.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Process each dropped file
|
||||
for (const file of files) {
|
||||
await processDroppedImage(file);
|
||||
}
|
||||
};
|
||||
|
||||
const processDroppedImage = (file: File): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = e => {
|
||||
if (e.target?.result) {
|
||||
img.src = e.target.result as string;
|
||||
}
|
||||
};
|
||||
|
||||
img.onload = () => {
|
||||
const { maxWidth, maxHeight } = calculateMaxDimensions();
|
||||
|
||||
// Check if the dropped image is larger than current cells
|
||||
if (img.naturalWidth > maxWidth || img.naturalHeight > maxHeight) {
|
||||
// Emit event with resize flag
|
||||
emit('addSpriteWithResize', file);
|
||||
} else {
|
||||
// Normal add
|
||||
emit('addSprite', file);
|
||||
}
|
||||
resolve();
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
console.error('Failed to load image:', file.name);
|
||||
reject(new Error('Failed to load image'));
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
};
|
||||
|
||||
const handleTouchMove = (event: TouchEvent) => {
|
||||
// Only prevent default when we're actually dragging
|
||||
if (isDragging.value) {
|
||||
|
||||
Reference in New Issue
Block a user