Multiple fixes
This commit is contained in:
parent
ca28f66997
commit
7cb5605b54
@ -1,6 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen bg-gray-900 text-gray-200 font-sans">
|
<div class="min-h-screen bg-gray-900 text-gray-200 font-sans">
|
||||||
<app-header @toggle-help="showHelpModal" />
|
<app-header @toggle-help="showHelpModal" />
|
||||||
|
<div class="max-w-7xl mx-auto px-6">
|
||||||
|
<div class="bg-blue-500 bg-opacity-10 border-l-4 border-blue-500 p-4 mt-6 rounded-r">
|
||||||
|
<p>Container size will adjust to fit the largest sprite. All sprites will be placed in cells of the same size.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="max-w-7xl mx-auto p-6 grid grid-cols-1 lg:grid-cols-4 gap-6">
|
<div class="max-w-7xl mx-auto p-6 grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||||
<sidebar class="lg:col-span-1" />
|
<sidebar class="lg:col-span-1" />
|
||||||
<main-content class="lg:col-span-3" />
|
<main-content class="lg:col-span-3" />
|
||||||
|
@ -47,9 +47,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleFiles = async (files: FileList) => {
|
const handleFiles = async (files: FileList) => {
|
||||||
console.log('Handling files:', files.length);
|
|
||||||
const imageFiles = Array.from(files).filter(file => file.type.startsWith('image/'));
|
const imageFiles = Array.from(files).filter(file => file.type.startsWith('image/'));
|
||||||
console.log('Image files filtered:', imageFiles.length);
|
|
||||||
|
|
||||||
if (imageFiles.length === 0) {
|
if (imageFiles.length === 0) {
|
||||||
store.showNotification('Please upload image files only', 'error');
|
store.showNotification('Please upload image files only', 'error');
|
||||||
@ -61,12 +59,10 @@
|
|||||||
|
|
||||||
for (let i = 0; i < imageFiles.length; i++) {
|
for (let i = 0; i < imageFiles.length; i++) {
|
||||||
const file = imageFiles[i];
|
const file = imageFiles[i];
|
||||||
console.log(`Processing file ${i+1}/${imageFiles.length}:`, file.name, file.type, file.size);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sprite = await createSpriteFromFile(file, i);
|
const sprite = await createSpriteFromFile(file, i);
|
||||||
newSprites.push(sprite);
|
newSprites.push(sprite);
|
||||||
console.log(`Successfully processed ${file.name}`);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorCount++;
|
errorCount++;
|
||||||
console.error('Error loading sprite:', error);
|
console.error('Error loading sprite:', error);
|
||||||
@ -75,7 +71,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newSprites.length > 0) {
|
if (newSprites.length > 0) {
|
||||||
console.log('Adding sprites to store:', newSprites.length);
|
|
||||||
store.addSprites(newSprites);
|
store.addSprites(newSprites);
|
||||||
emit('files-uploaded', newSprites);
|
emit('files-uploaded', newSprites);
|
||||||
store.showNotification(`Added ${newSprites.length} sprites successfully`);
|
store.showNotification(`Added ${newSprites.length} sprites successfully`);
|
||||||
@ -86,30 +81,23 @@
|
|||||||
|
|
||||||
const createSpriteFromFile = (file: File, index: number): Promise<Sprite> => {
|
const createSpriteFromFile = (file: File, index: number): Promise<Sprite> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
// Create a URL for the file
|
||||||
|
const objectUrl = URL.createObjectURL(file);
|
||||||
reader.onload = e => {
|
|
||||||
if (!e.target?.result) {
|
|
||||||
console.error('Failed to read file:', file.name);
|
|
||||||
reject(new Error(`Failed to read file: ${file.name}`));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
|
|
||||||
// Set crossOrigin to anonymous to handle CORS issues
|
// Set up event handlers
|
||||||
img.crossOrigin = 'anonymous';
|
|
||||||
|
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
// Verify the image has loaded properly
|
// Verify the image has loaded properly
|
||||||
if (img.width === 0 || img.height === 0) {
|
if (img.width === 0 || img.height === 0) {
|
||||||
console.error('Image loaded with invalid dimensions:', file.name, img.width, img.height);
|
console.error('Image loaded with invalid dimensions:', file.name, img.width, img.height);
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
reject(new Error(`Image has invalid dimensions: ${file.name}`));
|
reject(new Error(`Image has invalid dimensions: ${file.name}`));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Sprite created successfully:', file.name, img.width, img.height);
|
// Create the sprite object
|
||||||
resolve({
|
const sprite: Sprite = {
|
||||||
img,
|
img,
|
||||||
width: img.width,
|
width: img.width,
|
||||||
height: img.height,
|
height: img.height,
|
||||||
@ -118,24 +106,23 @@
|
|||||||
name: file.name,
|
name: file.name,
|
||||||
id: `sprite-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
id: `sprite-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||||
uploadOrder: index,
|
uploadOrder: index,
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
img.onerror = (error) => {
|
// Keep the objectUrl reference and don't revoke it yet
|
||||||
|
// The image is still needed for rendering later
|
||||||
|
// URL.revokeObjectURL(objectUrl); - Don't do this anymore
|
||||||
|
|
||||||
|
resolve(sprite);
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = error => {
|
||||||
console.error('Error loading image:', file.name, error);
|
console.error('Error loading image:', file.name, error);
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
reject(new Error(`Failed to load image: ${file.name}`));
|
reject(new Error(`Failed to load image: ${file.name}`));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set the source after setting up event handlers
|
// Set the source to the object URL
|
||||||
img.src = e.target.result as string;
|
img.src = objectUrl;
|
||||||
};
|
|
||||||
|
|
||||||
reader.onerror = (error) => {
|
|
||||||
console.error('Error reading file:', file.name, error);
|
|
||||||
reject(new Error(`Error reading file: ${file.name}`));
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -39,8 +39,12 @@
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const setupCheckerboardPattern = () => {
|
const setupCheckerboardPattern = () => {
|
||||||
if (!canvasEl.value) return;
|
if (!canvasEl.value) {
|
||||||
|
console.error('MainContent: Canvas element not available for checkerboard pattern');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
// This will be done with CSS using Tailwind's bg utilities
|
// This will be done with CSS using Tailwind's bg utilities
|
||||||
canvasEl.value.style.backgroundImage = `
|
canvasEl.value.style.backgroundImage = `
|
||||||
linear-gradient(45deg, #1a1a1a 25%, transparent 25%),
|
linear-gradient(45deg, #1a1a1a 25%, transparent 25%),
|
||||||
@ -50,6 +54,9 @@
|
|||||||
`;
|
`;
|
||||||
canvasEl.value.style.backgroundSize = '20px 20px';
|
canvasEl.value.style.backgroundSize = '20px 20px';
|
||||||
canvasEl.value.style.backgroundPosition = '0 0, 0 10px, 10px -10px, -10px 0px';
|
canvasEl.value.style.backgroundPosition = '0 0, 0 10px, 10px -10px, -10px 0px';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('MainContent: Error setting up checkerboard pattern:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseDown = (e: MouseEvent) => {
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
@ -85,8 +92,9 @@
|
|||||||
if (canvasEl.value && cellX >= 0 && cellX < canvasEl.value.width / store.cellSize.width && cellY >= 0 && cellY < canvasEl.value.height / store.cellSize.height) {
|
if (canvasEl.value && cellX >= 0 && cellX < canvasEl.value.width / store.cellSize.width && cellY >= 0 && cellY < canvasEl.value.height / store.cellSize.height) {
|
||||||
isTooltipVisible.value = true;
|
isTooltipVisible.value = true;
|
||||||
tooltipText.value = `Cell: (${cellX}, ${cellY})`;
|
tooltipText.value = `Cell: (${cellX}, ${cellY})`;
|
||||||
tooltipPosition.value.x = e.clientX;
|
// Use pageX and pageY instead of clientX and clientY
|
||||||
tooltipPosition.value.y = e.clientY;
|
tooltipPosition.value.x = e.pageX;
|
||||||
|
tooltipPosition.value.y = e.pageY;
|
||||||
} else {
|
} else {
|
||||||
isTooltipVisible.value = false;
|
isTooltipVisible.value = false;
|
||||||
}
|
}
|
||||||
@ -106,12 +114,15 @@
|
|||||||
// Calculate cell boundaries
|
// Calculate cell boundaries
|
||||||
const cellLeft = cellX * store.cellSize.width;
|
const cellLeft = cellX * store.cellSize.width;
|
||||||
const cellTop = cellY * store.cellSize.height;
|
const cellTop = cellY * store.cellSize.height;
|
||||||
const cellRight = cellLeft + store.cellSize.width - store.draggedSprite.value.width;
|
const cellRight = cellLeft + store.cellSize.width - store.draggedSprite.value.img.width;
|
||||||
const cellBottom = cellTop + store.cellSize.height - store.draggedSprite.value.height;
|
const cellBottom = cellTop + store.cellSize.height - store.draggedSprite.value.img.height;
|
||||||
|
|
||||||
// Constrain position to stay within the cell
|
// Constrain position to stay within the cell
|
||||||
store.draggedSprite.value.x = Math.max(cellLeft, Math.min(newX, cellRight));
|
store.draggedSprite.value.x = Math.max(cellLeft, Math.min(newX, cellRight));
|
||||||
store.draggedSprite.value.y = Math.max(cellTop, Math.min(newY, cellBottom));
|
store.draggedSprite.value.y = Math.max(cellTop, Math.min(newY, cellBottom));
|
||||||
|
|
||||||
|
// Trigger a re-render
|
||||||
|
store.renderSpritesheetPreview();
|
||||||
} else {
|
} else {
|
||||||
// Calculate new position based on grid cells (snap to grid)
|
// Calculate new position based on grid cells (snap to grid)
|
||||||
const newCellX = Math.floor((x - store.dragOffset.x) / store.cellSize.width);
|
const newCellX = Math.floor((x - store.dragOffset.x) / store.cellSize.width);
|
||||||
@ -127,6 +138,9 @@
|
|||||||
|
|
||||||
store.draggedSprite.value.x = boundedCellX * store.cellSize.width;
|
store.draggedSprite.value.x = boundedCellX * store.cellSize.width;
|
||||||
store.draggedSprite.value.y = boundedCellY * store.cellSize.height;
|
store.draggedSprite.value.y = boundedCellY * store.cellSize.height;
|
||||||
|
|
||||||
|
// Trigger a re-render
|
||||||
|
store.renderSpritesheetPreview();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,31 +168,80 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const setupCanvasEvents = () => {
|
const setupCanvasEvents = () => {
|
||||||
if (!canvasEl.value) return;
|
if (!canvasEl.value) {
|
||||||
|
console.error('MainContent: Canvas element not available for event setup');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
canvasEl.value.addEventListener('mousedown', handleMouseDown);
|
canvasEl.value.addEventListener('mousedown', handleMouseDown);
|
||||||
canvasEl.value.addEventListener('mousemove', handleMouseMove);
|
canvasEl.value.addEventListener('mousemove', handleMouseMove);
|
||||||
canvasEl.value.addEventListener('mouseup', handleMouseUp);
|
canvasEl.value.addEventListener('mouseup', handleMouseUp);
|
||||||
canvasEl.value.addEventListener('mouseout', handleMouseOut);
|
canvasEl.value.addEventListener('mouseout', handleMouseOut);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('MainContent: Error setting up canvas events:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (canvasEl.value) {
|
// Initialize canvas immediately
|
||||||
|
initializeCanvas();
|
||||||
|
|
||||||
|
// Also set up a MutationObserver to ensure the canvas is properly initialized
|
||||||
|
// even if there are DOM changes
|
||||||
|
const observer = new MutationObserver(mutations => {
|
||||||
|
initializeCanvas();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start observing the document with the configured parameters
|
||||||
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
|
||||||
|
// Clean up the observer after a short time
|
||||||
|
setTimeout(() => {
|
||||||
|
observer.disconnect();
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
const initializeCanvas = () => {
|
||||||
|
if (!canvasEl.value) {
|
||||||
|
console.error('MainContent: Canvas element not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get the 2D context
|
||||||
|
const context = canvasEl.value.getContext('2d');
|
||||||
|
if (!context) {
|
||||||
|
console.error('MainContent: Failed to get 2D context from canvas');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the store references
|
||||||
store.canvas.value = canvasEl.value;
|
store.canvas.value = canvasEl.value;
|
||||||
store.ctx.value = canvasEl.value.getContext('2d');
|
store.ctx.value = context;
|
||||||
|
|
||||||
// Initialize canvas size
|
// Initialize canvas size
|
||||||
canvasEl.value.width = 400;
|
canvasEl.value.width = 400;
|
||||||
canvasEl.value.height = 300;
|
canvasEl.value.height = 300;
|
||||||
|
|
||||||
|
// Setup the canvas
|
||||||
setupCheckerboardPattern();
|
setupCheckerboardPattern();
|
||||||
setupCanvasEvents();
|
setupCanvasEvents();
|
||||||
|
|
||||||
// Setup keyboard events for modifiers
|
// Setup keyboard events for modifiers
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
window.addEventListener('keyup', handleKeyUp);
|
window.addEventListener('keyup', handleKeyUp);
|
||||||
|
|
||||||
|
// Check if we have sprites that need rendering
|
||||||
|
if (store.sprites.value.length > 0) {
|
||||||
|
store.updateCellSize();
|
||||||
|
store.autoArrangeSprites();
|
||||||
|
store.renderSpritesheetPreview();
|
||||||
}
|
}
|
||||||
});
|
} catch (error) {
|
||||||
|
console.error('MainContent: Error initializing canvas:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50 transition-all duration-300" :class="{ 'opacity-0 invisible': !isModalOpen, 'opacity-100 visible': isModalOpen }" @click.self="closeModal">
|
<div class="fixed inset-0 flex items-center justify-center z-50" :class="{ 'pointer-events-none': !isModalOpen }">
|
||||||
<div class="bg-gray-800 rounded-lg max-w-4xl max-h-[90vh] overflow-auto shadow-lg transform transition-transform duration-300" :class="{ '-translate-y-5': !isModalOpen, 'translate-y-0': isModalOpen }">
|
<div class="bg-gray-800 rounded-lg max-w-4xl max-h-[90vh] overflow-auto scrollbar-hide shadow-lg pointer-events-auto" :class="{ invisible: !isModalOpen, visible: isModalOpen }" :style="{ transform: `translate3d(${position.x}px, ${position.y + (isModalOpen ? 0 : -20)}px, 0)` }">
|
||||||
<div class="flex items-center justify-between p-4 bg-gray-700 border-b border-gray-600">
|
<div class="flex items-center justify-between p-4 bg-gray-700 border-b border-gray-600 cursor-move" @mousedown="startDrag">
|
||||||
<div class="flex items-center gap-2 text-lg font-semibold">
|
<div class="flex items-center gap-2 text-lg font-semibold select-none">
|
||||||
<i class="fas fa-film text-blue-500"></i>
|
<i class="fas fa-film text-blue-500"></i>
|
||||||
<span>Animation Preview</span>
|
<span>Animation Preview</span>
|
||||||
</div>
|
</div>
|
||||||
@ -104,17 +104,37 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const openModal = () => {
|
const openModal = async () => {
|
||||||
if (sprites.value.length === 0) {
|
if (sprites.value.length === 0) {
|
||||||
store.showNotification('Please add sprites first', 'error');
|
store.showNotification('Please add sprites first', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset position when opening
|
||||||
|
position.value = { x: 0, y: 0 };
|
||||||
|
|
||||||
|
// Reset to first frame
|
||||||
|
currentFrame.value = 0;
|
||||||
|
animation.value.currentFrame = 0;
|
||||||
|
|
||||||
|
// Make sure the canvas is initialized
|
||||||
|
await initializeCanvas();
|
||||||
|
|
||||||
|
// Set modal open state
|
||||||
store.isModalOpen.value = true;
|
store.isModalOpen.value = true;
|
||||||
|
|
||||||
// Show the current frame
|
// Wait for next frame to ensure DOM is updated
|
||||||
if (!animation.value.isPlaying && sprites.value.length > 0) {
|
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||||
store.renderAnimationFrame(currentFrame.value);
|
|
||||||
|
// Set proper canvas size before rendering
|
||||||
|
if (animCanvas.value && store.cellSize.width && store.cellSize.height) {
|
||||||
|
animCanvas.value.width = store.cellSize.width;
|
||||||
|
animCanvas.value.height = store.cellSize.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force render the first frame
|
||||||
|
if (sprites.value.length > 0) {
|
||||||
|
store.renderAnimationFrame(0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -159,23 +179,72 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (animCanvas.value) {
|
initializeCanvas();
|
||||||
store.animation.canvas = animCanvas.value;
|
|
||||||
store.animation.ctx = animCanvas.value.getContext('2d');
|
|
||||||
|
|
||||||
// Initialize canvas size
|
|
||||||
animCanvas.value.width = 200;
|
|
||||||
animCanvas.value.height = 200;
|
|
||||||
|
|
||||||
// Setup keyboard shortcuts for the modal
|
// Setup keyboard shortcuts for the modal
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const initializeCanvas = async () => {
|
||||||
|
if (!animCanvas.value) {
|
||||||
|
console.error('PreviewModal: Animation canvas not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get the 2D context
|
||||||
|
const context = animCanvas.value.getContext('2d');
|
||||||
|
if (!context) {
|
||||||
|
console.error('PreviewModal: Failed to get 2D context from animation canvas');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the store references
|
||||||
|
store.animation.canvas = animCanvas.value;
|
||||||
|
store.animation.ctx = context;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('PreviewModal: Error initializing animation canvas:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add these new refs for dragging functionality
|
||||||
|
const position = ref({ x: 0, y: 0 });
|
||||||
|
const isDragging = ref(false);
|
||||||
|
const dragOffset = ref({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
const startDrag = (e: MouseEvent) => {
|
||||||
|
isDragging.value = true;
|
||||||
|
dragOffset.value = {
|
||||||
|
x: e.clientX - position.value.x,
|
||||||
|
y: e.clientY - position.value.y,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add temporary event listeners
|
||||||
|
window.addEventListener('mousemove', handleDrag);
|
||||||
|
window.addEventListener('mouseup', stopDrag);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrag = (e: MouseEvent) => {
|
||||||
|
if (!isDragging.value) return;
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
position.value = {
|
||||||
|
x: e.clientX - dragOffset.value.x,
|
||||||
|
y: e.clientY - dragOffset.value.y,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopDrag = () => {
|
||||||
|
isDragging.value = false;
|
||||||
|
window.removeEventListener('mousemove', handleDrag);
|
||||||
|
window.removeEventListener('mouseup', stopDrag);
|
||||||
|
};
|
||||||
|
|
||||||
// Keep currentFrame in sync with animation.currentFrame
|
// Keep currentFrame in sync with animation.currentFrame
|
||||||
watch(
|
watch(
|
||||||
() => animation.value.currentFrame,
|
() => animation.value.currentFrame,
|
||||||
@ -205,4 +274,19 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Optional: Prevent text selection while dragging */
|
||||||
|
.cursor-move {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add these new styles */
|
||||||
|
.scrollbar-hide {
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-hide::-webkit-scrollbar {
|
||||||
|
display: none; /* Chrome, Safari and Opera */
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -10,10 +10,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<drop-zone @files-uploaded="handleUpload" />
|
<drop-zone @files-uploaded="handleUpload" />
|
||||||
|
|
||||||
<div class="bg-blue-500 bg-opacity-10 border-l-4 border-blue-500 p-4 mt-6 rounded-r">
|
|
||||||
<p>Container size will adjust to fit the largest sprite. All sprites will be placed in cells of the same size.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -75,14 +71,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
|
import { type Sprite, useSpritesheetStore } from '../composables/useSpritesheetStore';
|
||||||
import DropZone from './DropZone.vue';
|
import DropZone from './DropZone.vue';
|
||||||
import SpriteList from './SpriteList.vue';
|
import SpriteList from './SpriteList.vue';
|
||||||
|
|
||||||
const store = useSpritesheetStore();
|
const store = useSpritesheetStore();
|
||||||
const sprites = computed(() => store.sprites.value);
|
const sprites = computed(() => store.sprites.value);
|
||||||
|
|
||||||
const handleUpload = () => {
|
const handleUpload = (sprites: Sprite[]) => {
|
||||||
// The dropzone component handles adding sprites to the store
|
// The dropzone component handles adding sprites to the store
|
||||||
// This is just for event handling if needed
|
// This is just for event handling if needed
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="sprites.length === 0" class="text-center text-gray-400 py-8">No sprites uploaded yet</div>
|
<div v-if="sprites.length === 0" class="text-center text-gray-400 py-8">No sprites uploaded yet</div>
|
||||||
|
|
||||||
<div v-else class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 max-h-72 overflow-y-auto pr-2">
|
<div v-else class="grid grid-cols-2 gap-3 max-h-72 overflow-y-auto pr-2">
|
||||||
<div v-for="(sprite, index) in sprites" :key="sprite.id" @click="$emit('spriteClicked', sprite.id)" class="border border-gray-600 rounded bg-gray-700 p-2 text-center transition-all cursor-pointer hover:border-blue-500 hover:-translate-y-0.5 hover:shadow-md">
|
<div v-for="(sprite, index) in sprites" :key="sprite.id" @click="$emit('spriteClicked', sprite.id)" class="border border-gray-600 rounded bg-gray-700 p-2 text-center transition-all cursor-pointer hover:border-blue-500 hover:-translate-y-0.5 hover:shadow-md">
|
||||||
<img :src="sprite.img.src" :alt="sprite.name" class="max-w-full max-h-16 mx-auto mb-2 bg-black bg-opacity-20 rounded" />
|
<img :src="sprite.img.src" :alt="sprite.name" class="max-w-full max-h-16 mx-auto mb-2 bg-black bg-opacity-20 rounded" />
|
||||||
<div class="text-xs text-gray-400 truncate">{{ index + 1 }}. {{ truncateName(sprite.name) }}</div>
|
<div class="text-xs text-gray-400 truncate">{{ index + 1 }}. {{ truncateName(sprite.name) }}</div>
|
||||||
|
@ -28,7 +28,6 @@ export interface AnimationState {
|
|||||||
manualUpdate: boolean;
|
manualUpdate: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSpritesheetStore() {
|
|
||||||
const sprites = ref<Sprite[]>([]);
|
const sprites = ref<Sprite[]>([]);
|
||||||
const canvas = ref<HTMLCanvasElement | null>(null);
|
const canvas = ref<HTMLCanvasElement | null>(null);
|
||||||
const ctx = ref<CanvasRenderingContext2D | null>(null);
|
const ctx = ref<CanvasRenderingContext2D | null>(null);
|
||||||
@ -39,6 +38,7 @@ export function useSpritesheetStore() {
|
|||||||
const isShiftPressed = ref(false);
|
const isShiftPressed = ref(false);
|
||||||
const isModalOpen = ref(false);
|
const isModalOpen = ref(false);
|
||||||
|
|
||||||
|
export function useSpritesheetStore() {
|
||||||
const animation = reactive<AnimationState>({
|
const animation = reactive<AnimationState>({
|
||||||
canvas: null,
|
canvas: null,
|
||||||
ctx: null,
|
ctx: null,
|
||||||
@ -58,7 +58,6 @@ export function useSpritesheetStore() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function addSprites(newSprites: Sprite[]) {
|
function addSprites(newSprites: Sprite[]) {
|
||||||
console.log('Store: Adding sprites', newSprites.length);
|
|
||||||
if (newSprites.length === 0) {
|
if (newSprites.length === 0) {
|
||||||
console.warn('Store: Attempted to add empty sprites array');
|
console.warn('Store: Attempted to add empty sprites array');
|
||||||
return;
|
return;
|
||||||
@ -79,21 +78,17 @@ export function useSpritesheetStore() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Store: Adding valid sprites', validSprites.length);
|
|
||||||
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);
|
||||||
updateCellSize();
|
updateCellSize();
|
||||||
autoArrangeSprites();
|
autoArrangeSprites();
|
||||||
console.log('Store: Sprites added successfully, total count:', sprites.value.length);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Store: Error adding sprites:', error);
|
console.error('Store: Error adding sprites:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCellSize() {
|
function updateCellSize() {
|
||||||
console.log('Store: Updating cell size');
|
|
||||||
if (sprites.value.length === 0) {
|
if (sprites.value.length === 0) {
|
||||||
console.log('Store: No sprites to update cell size');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +110,6 @@ export function useSpritesheetStore() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Store: Cell size calculated', maxWidth, maxHeight);
|
|
||||||
cellSize.width = maxWidth;
|
cellSize.width = maxWidth;
|
||||||
cellSize.height = maxHeight;
|
cellSize.height = maxHeight;
|
||||||
|
|
||||||
@ -126,14 +120,11 @@ export function useSpritesheetStore() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateCanvasSize() {
|
function updateCanvasSize() {
|
||||||
console.log('Store: Updating canvas size');
|
|
||||||
if (!canvas.value) {
|
if (!canvas.value) {
|
||||||
console.error('Store: Canvas not available for size update');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sprites.value.length === 0) {
|
if (sprites.value.length === 0) {
|
||||||
console.log('Store: No sprites to determine canvas size');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +141,6 @@ export function useSpritesheetStore() {
|
|||||||
const newWidth = cols * cellSize.width;
|
const newWidth = cols * cellSize.width;
|
||||||
const newHeight = rows * cellSize.height;
|
const newHeight = rows * cellSize.height;
|
||||||
|
|
||||||
console.log('Store: Setting canvas size to', newWidth, newHeight);
|
|
||||||
canvas.value.width = newWidth;
|
canvas.value.width = newWidth;
|
||||||
canvas.value.height = newHeight;
|
canvas.value.height = newHeight;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -159,9 +149,7 @@ export function useSpritesheetStore() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function autoArrangeSprites() {
|
function autoArrangeSprites() {
|
||||||
console.log('Store: Auto-arranging sprites');
|
|
||||||
if (sprites.value.length === 0) {
|
if (sprites.value.length === 0) {
|
||||||
console.log('Store: No sprites to arrange');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,52 +165,75 @@ export function useSpritesheetStore() {
|
|||||||
|
|
||||||
sprite.x = column * cellSize.width;
|
sprite.x = column * cellSize.width;
|
||||||
sprite.y = row * cellSize.height;
|
sprite.y = row * cellSize.height;
|
||||||
console.log(`Store: Positioned sprite ${index} at (${sprite.x}, ${sprite.y})`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if the canvas is ready before attempting to render
|
||||||
|
if (!ctx.value || !canvas.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
renderSpritesheetPreview();
|
renderSpritesheetPreview();
|
||||||
|
|
||||||
if (!animation.isPlaying && animation.manualUpdate && isModalOpen.value) {
|
if (!animation.isPlaying && animation.manualUpdate && isModalOpen.value) {
|
||||||
renderAnimationFrame(animation.currentFrame);
|
renderAnimationFrame(animation.currentFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Store: Sprites arranged successfully');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Store: Error auto-arranging sprites:', error);
|
console.error('Store: Error auto-arranging sprites:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSpritesheetPreview(showGrid = true) {
|
function renderSpritesheetPreview(showGrid = true) {
|
||||||
console.log('Store: Rendering spritesheet preview');
|
|
||||||
if (!ctx.value || !canvas.value) {
|
if (!ctx.value || !canvas.value) {
|
||||||
console.error('Store: Canvas or context not available for rendering');
|
console.error('Store: Canvas or context not available for rendering, will retry when ready');
|
||||||
|
|
||||||
|
// Set up a small delay and retry when elements might be ready
|
||||||
|
setTimeout(() => {
|
||||||
|
if (ctx.value && canvas.value) {
|
||||||
|
renderSpritesheetPreview(showGrid);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sprites.value.length === 0) {
|
if (sprites.value.length === 0) {
|
||||||
console.log('Store: No sprites to render');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Make sure canvas dimensions are set correctly
|
||||||
|
updateCanvasSize();
|
||||||
|
|
||||||
|
// Clear the canvas
|
||||||
ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height);
|
ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height);
|
||||||
|
|
||||||
if (showGrid) {
|
if (showGrid) {
|
||||||
drawGrid();
|
drawGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw each sprite
|
||||||
sprites.value.forEach((sprite, index) => {
|
sprites.value.forEach((sprite, index) => {
|
||||||
try {
|
try {
|
||||||
if (!sprite.img || sprite.img.width === 0 || sprite.img.height === 0) {
|
if (!sprite.img) {
|
||||||
console.warn(`Store: Invalid sprite at index ${index}, skipping render`);
|
console.warn(`Store: Sprite at index ${index} has no image, skipping render`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sprite.img.complete && sprite.img.naturalWidth !== 0) {
|
||||||
ctx.value!.drawImage(sprite.img, sprite.x, sprite.y);
|
ctx.value!.drawImage(sprite.img, sprite.x, sprite.y);
|
||||||
|
} else {
|
||||||
|
console.warn(`Store: Sprite image ${index} not fully loaded, setting onload handler`);
|
||||||
|
|
||||||
|
// If image isn't loaded yet, set an onload handler
|
||||||
|
sprite.img.onload = () => {
|
||||||
|
if (ctx.value && canvas.value) {
|
||||||
|
ctx.value.drawImage(sprite.img, sprite.x, sprite.y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
} catch (spriteError) {
|
} catch (spriteError) {
|
||||||
console.error(`Store: Error rendering sprite at index ${index}:`, spriteError);
|
console.error(`Store: Error rendering sprite at index ${index}:`, spriteError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log('Store: Spritesheet preview rendered successfully');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Store: Error rendering spritesheet preview:', error);
|
console.error('Store: Error rendering spritesheet preview:', error);
|
||||||
}
|
}
|
||||||
@ -314,7 +325,7 @@ export function useSpritesheetStore() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.download = 'noxious-spritesheet.png';
|
link.download = 'spritesheet.png';
|
||||||
link.href = tempCanvas.toDataURL('image/png');
|
link.href = tempCanvas.toDataURL('image/png');
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user