2025-04-05 22:40:40 +02:00

150 lines
4.8 KiB
Vue

<template>
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 p-6">
<div class="max-w-6xl mx-auto">
<h1 class="text-4xl font-bold text-center mb-8 text-gray-900 tracking-tight">Spritesheet Generator</h1>
<div class="bg-white rounded-2xl shadow-soft p-8">
<file-uploader @upload-sprites="handleSpritesUpload" />
<div v-if="sprites.length > 0" class="mt-8">
<div class="flex flex-wrap items-center gap-6 mb-8">
<div class="flex items-center space-x-3">
<label for="columns" class="text-gray-700 font-medium">Columns:</label>
<input id="columns" type="number" v-model="columns" min="1" max="10" class="w-20 px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all" />
</div>
<button @click="downloadSpritesheet" class="px-6 py-2.5 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors flex items-center space-x-2">
<i class="fas fa-download"></i>
<span>Download Spritesheet</span>
</button>
<button @click="openPreviewModal" class="px-6 py-2.5 bg-green-500 hover:bg-green-600 text-white font-medium rounded-lg transition-colors flex items-center space-x-2">
<i class="fas fa-play"></i>
<span>Preview Animation</span>
</button>
</div>
<sprite-canvas :sprites="sprites" :columns="columns" @update-sprite="updateSpritePosition" />
</div>
</div>
</div>
<Modal :is-open="isPreviewModalOpen" @close="closePreviewModal" :initial-width="800" :initial-height="600" title="Animation Preview">
<sprite-preview :sprites="sprites" :columns="columns" @update-sprite="updateSpritePosition" />
</Modal>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import FileUploader from './components/FileUploader.vue';
import SpriteCanvas from './components/SpriteCanvas.vue';
import Modal from './components/utilities/Modal.vue';
import SpritePreview from './components/SpritePreview.vue';
interface Sprite {
id: string;
file: File;
img: HTMLImageElement;
url: string;
width: number;
height: number;
x: number;
y: number;
}
const sprites = ref<Sprite[]>([]);
const columns = ref(4);
const isPreviewModalOpen = ref(false);
const handleSpritesUpload = (files: File[]) => {
Promise.all(
files.map(file => {
return new Promise<Sprite>(resolve => {
const url = URL.createObjectURL(file);
const img = new Image();
img.onload = () => {
resolve({
id: crypto.randomUUID(),
file,
img,
url,
width: img.width,
height: img.height,
x: 0,
y: 0,
});
};
img.src = url;
});
})
).then(newSprites => {
sprites.value = [...sprites.value, ...newSprites];
});
};
const updateSpritePosition = (id: string, x: number, y: number) => {
const spriteIndex = sprites.value.findIndex(sprite => sprite.id === id);
if (spriteIndex !== -1) {
sprites.value[spriteIndex].x = x;
sprites.value[spriteIndex].y = y;
}
};
const downloadSpritesheet = () => {
if (sprites.value.length === 0) return;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Find max dimensions
let maxWidth = 0;
let maxHeight = 0;
sprites.value.forEach(sprite => {
if (sprite.width > maxWidth) maxWidth = sprite.width;
if (sprite.height > maxHeight) maxHeight = sprite.height;
});
// Set canvas size
const rows = Math.ceil(sprites.value.length / columns.value);
canvas.width = maxWidth * columns.value;
canvas.height = maxHeight * rows;
// Apply pixel art optimization for the export canvas
ctx.imageSmoothingEnabled = false;
// Draw sprites
sprites.value.forEach((sprite, index) => {
const col = index % columns.value;
const row = Math.floor(index / columns.value);
const cellX = col * maxWidth;
const cellY = row * maxHeight;
ctx.drawImage(sprite.img, cellX + sprite.x, cellY + sprite.y);
});
// Create download link
const link = document.createElement('a');
link.download = 'spritesheet.png';
link.href = canvas.toDataURL('image/png');
link.click();
};
// Preview modal control
const openPreviewModal = () => {
console.log('Opening preview modal');
if (sprites.value.length === 0) {
console.log('No sprites to preview');
return;
}
isPreviewModalOpen.value = true;
};
const closePreviewModal = () => {
isPreviewModalOpen.value = false;
};
</script>