This commit is contained in:
Dennis Postma 2025-04-03 04:02:12 +02:00
parent 085fbde0a3
commit 183ea2b9eb
7 changed files with 396 additions and 92 deletions

View File

@ -1,33 +1,64 @@
<template>
<div class="min-h-screen bg-gray-900 text-gray-200 font-sans">
<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>
<!-- Navigation sidebar -->
<navigation @show-help="showHelpModal" />
<!-- Main content area -->
<div class="pl-16">
<!-- Add left padding to accommodate the fixed navigation -->
<app-header @toggle-help="showHelpModal" />
<!-- Admin panel-like layout -->
<div class="max-w-7xl mx-auto px-6 py-4">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold">Spritesheet creator</h1>
<div class="flex gap-3">
<button @click="store.isSpritesModalOpen.value = true" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500">
<i class="fas fa-images"></i> Sprites
<span v-if="sprites.length > 0" class="ml-1 bg-blue-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
{{ sprites.length }}
</span>
</button>
<button @click="store.isSettingsModalOpen.value = true" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500"><i class="fas fa-cog"></i> Settings</button>
</div>
</div>
<div class="bg-blue-500 bg-opacity-10 border-l-4 border-blue-500 p-4 mb-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 class="grid grid-cols-1 lg:grid-cols-4 gap-6">
<sidebar class="lg:col-span-1" />
<main-content class="lg:col-span-3" />
</div>
</div>
</div>
<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" />
<main-content class="lg:col-span-3" />
</div>
<!-- Modals -->
<preview-modal ref="previewModalRef" />
<settings-modal />
<sprites-modal />
<notification />
<help-button @show-help="showHelpModal" />
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { ref, watch, computed } from 'vue';
import AppHeader from './components/AppHeader.vue';
import Sidebar from './components/Sidebar.vue';
import MainContent from './components/MainContent.vue';
import PreviewModal from './components/PreviewModal.vue';
import SettingsModal from './components/SettingsModal.vue';
import SpritesModal from './components/SpritesModal.vue';
import Navigation from './components/Navigation.vue';
import Notification from './components/Notification.vue';
import HelpButton from './components/HelpButton.vue';
import { useSpritesheetStore } from './composables/useSpritesheetStore';
const store = useSpritesheetStore();
const previewModalRef = ref<InstanceType<typeof PreviewModal> | null>(null);
const sprites = computed(() => store.sprites.value);
// Watch for changes to isModalOpen and call the openModal method when it becomes true
watch(

View File

@ -1,10 +1,28 @@
<template>
<header class="flex items-center justify-between bg-gray-800 p-3 shadow-md sticky top-0 z-50">
<div class="flex items-center gap-3">
<i class="fas fa-gamepad text-blue-500 text-2xl"></i>
<h1 class="text-xl font-semibold text-gray-200">Spritesheet Creator</h1>
<header class="flex items-center justify-between bg-gray-800 p-3 shadow-md sticky top-0 z-40">
<!-- Breadcrumb navigation -->
<div class="flex items-center gap-2">
<!-- <span class="text-gray-400">Dashboard</span>-->
<!-- <i class="fas fa-chevron-right text-xs text-gray-500"></i>-->
<span class="text-gray-200">Spritesheet editor</span>
</div>
<div class="flex gap-3">
<!-- Right side actions -->
<div class="flex gap-3 items-center">
<!-- Zoom display -->
<div class="text-sm text-gray-400 mr-2">
<span>Zoom: {{ Math.round(zoomLevel * 100) }}%</span>
</div>
<!-- Quick zoom controls -->
<button @click="zoomIn" class="p-2 bg-gray-700 border border-gray-600 rounded hover:border-blue-500 transition-colors" title="Zoom In">
<i class="fas fa-search-plus"></i>
</button>
<button @click="zoomOut" class="p-2 bg-gray-700 border border-gray-600 rounded hover:border-blue-500 transition-colors" title="Zoom Out">
<i class="fas fa-search-minus"></i>
</button>
<!-- Help button -->
<button @click="emit('toggleHelp')" class="p-2 bg-gray-700 border border-gray-600 rounded hover:border-blue-500 transition-colors" title="Keyboard Shortcuts">
<i class="fas fa-keyboard"></i>
</button>
@ -13,7 +31,16 @@
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
const store = useSpritesheetStore();
const zoomLevel = computed(() => store.zoomLevel.value);
const emit = defineEmits<{
(e: 'toggleHelp'): void;
}>();
// Expose store methods directly
const { zoomIn, zoomOut } = store;
</script>

View File

@ -0,0 +1,88 @@
<template>
<nav class="fixed left-0 top-0 bottom-0 w-16 bg-gray-800 shadow-lg z-40 flex flex-col items-center py-4">
<!-- Logo -->
<div class="mb-8">
<i class="fas fa-gamepad text-blue-500 text-2xl"></i>
</div>
<!-- Navigation Items -->
<div class="flex flex-col gap-6 items-center">
<!-- Dashboard/Home -->
<button class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors" :class="{ 'bg-gray-700 text-blue-500': activeSection === 'dashboard' }" @click="setActiveSection('dashboard')" title="Dashboard">
<i class="fas fa-home"></i>
</button>
<!-- Sprites -->
<button class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors relative" :class="{ 'bg-gray-700 text-blue-500': activeSection === 'sprites' }" @click="openSpritesModal" title="Sprites">
<i class="fas fa-images"></i>
<span v-if="sprites.length > 0" class="absolute -top-1 -right-1 bg-blue-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
{{ sprites.length }}
</span>
</button>
<!-- Preview -->
<button
class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors"
:class="{ 'bg-gray-700 text-blue-500': activeSection === 'preview' }"
@click="openPreviewModal"
title="Preview Animation"
:disabled="sprites.length === 0"
>
<i class="fas fa-play"></i>
</button>
<!-- Settings -->
<button class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors" :class="{ 'bg-gray-700 text-blue-500': activeSection === 'settings' }" @click="openSettingsModal" title="Settings">
<i class="fas fa-cog"></i>
</button>
</div>
<!-- Help Button at Bottom -->
<div class="mt-auto">
<button class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors" @click="showHelp" title="Help">
<i class="fas fa-question-circle"></i>
</button>
</div>
</nav>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
const store = useSpritesheetStore();
const sprites = computed(() => store.sprites.value);
const activeSection = ref('dashboard');
const setActiveSection = (section: string) => {
activeSection.value = section;
};
const openSpritesModal = () => {
setActiveSection('sprites');
store.isSpritesModalOpen.value = true;
};
const openSettingsModal = () => {
setActiveSection('settings');
store.isSettingsModalOpen.value = true;
};
const openPreviewModal = () => {
if (sprites.value.length === 0) {
store.showNotification('Please add sprites first', 'error');
return;
}
setActiveSection('preview');
store.isModalOpen.value = true;
};
const emit = defineEmits<{
(e: 'showHelp'): void;
}>();
const showHelp = () => {
emit('showHelp');
};
</script>

View File

@ -0,0 +1,108 @@
<template>
<!-- Settings Modal -->
<div class="fixed inset-0 flex items-center justify-center z-50 pointer-events-none" v-if="isModalOpen">
<!-- Modal backdrop with semi-transparent background -->
<div class="absolute inset-0 bg-black bg-opacity-50 pointer-events-auto" @click="closeModal"></div>
<!-- Modal content -->
<div class="bg-gray-800 rounded-lg max-w-2xl w-full max-h-[90vh] overflow-auto shadow-lg pointer-events-auto relative">
<div class="flex items-center justify-between p-4 bg-gray-700 border-b border-gray-600">
<div class="flex items-center gap-2 text-lg font-semibold">
<i class="fas fa-cog text-blue-500"></i>
<span>Settings</span>
</div>
<button @click="closeModal" class="text-gray-400 hover:text-white">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-6">
<!-- Tools Section -->
<div class="mb-8">
<h3 class="text-lg font-medium text-gray-200 mb-4 flex items-center gap-2"><i class="fas fa-tools text-blue-500"></i> Tools</h3>
<div class="flex flex-wrap gap-3 mb-6">
<button @click="autoArrangeSprites" :disabled="sprites.length === 0" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors disabled:opacity-60 disabled:cursor-not-allowed hover:border-blue-500">
<i class="fas fa-th"></i> Auto Arrange
</button>
<button @click="openPreviewModal" :disabled="sprites.length === 0" class="flex items-center gap-2 bg-blue-500 border border-blue-500 text-white rounded px-4 py-2 text-sm transition-colors disabled:opacity-60 disabled:cursor-not-allowed hover:bg-blue-600 hover:border-blue-600">
<i class="fas fa-play"></i> Preview Animation
</button>
<button @click="downloadSpritesheet" :disabled="sprites.length === 0" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors disabled:opacity-60 disabled:cursor-not-allowed hover:border-blue-500">
<i class="fas fa-download"></i> Download
</button>
<button @click="confirmClearAll" :disabled="sprites.length === 0" class="flex items-center gap-2 bg-red-600 border border-red-600 text-white rounded px-4 py-2 text-sm transition-colors disabled:opacity-60 disabled:cursor-not-allowed hover:bg-red-700 hover:border-red-700">
<i class="fas fa-trash-alt"></i> Clear All
</button>
</div>
</div>
<!-- Zoom Controls Section -->
<div class="mb-8">
<h3 class="text-lg font-medium text-gray-200 mb-4 flex items-center gap-2"><i class="fas fa-search text-blue-500"></i> Zoom Controls</h3>
<div class="flex flex-wrap gap-3 mb-6">
<button @click="zoomIn" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500"><i class="fas fa-search-plus"></i> Zoom In</button>
<button @click="zoomOut" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500"><i class="fas fa-search-minus"></i> Zoom Out</button>
<button @click="resetZoom" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500"><i class="fas fa-undo"></i> Reset Zoom</button>
</div>
</div>
<!-- Keyboard Shortcuts Section -->
<div>
<h3 class="text-lg font-medium text-gray-200 mb-4 flex items-center gap-2"><i class="fas fa-keyboard text-blue-500"></i> Keyboard Shortcuts</h3>
<div class="grid grid-cols-2 gap-4">
<div class="flex items-center gap-2 text-sm text-gray-400">
<kbd class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-xs font-mono">Shift</kbd>
<span>Fine-tune position</span>
</div>
<div class="flex items-center gap-2 text-sm text-gray-400">
<kbd class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-xs font-mono">Space</kbd>
<span>Play/Pause animation</span>
</div>
<div class="flex items-center gap-2 text-sm text-gray-400">
<kbd class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-xs font-mono">Esc</kbd>
<span>Close preview</span>
</div>
<div class="flex items-center gap-2 text-sm text-gray-400">
<kbd class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-xs font-mono">/</kbd>
<span>Navigate frames</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
const store = useSpritesheetStore();
const sprites = computed(() => store.sprites.value);
const isModalOpen = computed(() => store.isSettingsModalOpen.value);
const closeModal = () => {
store.isSettingsModalOpen.value = false;
};
const openPreviewModal = () => {
if (store.sprites.value.length === 0) {
store.showNotification('Please add sprites first', 'error');
return;
}
// Close settings modal and open preview modal
closeModal();
store.isModalOpen.value = true;
};
const confirmClearAll = () => {
if (confirm('Are you sure you want to clear all sprites?')) {
store.clearAllSprites();
store.showNotification('All sprites cleared');
}
};
// Expose store methods directly
const { autoArrangeSprites, downloadSpritesheet, zoomIn, zoomOut, resetZoom } = store;
</script>

View File

@ -13,88 +13,57 @@
</div>
</div>
<!-- Sprites Card -->
<!-- Quick Actions -->
<div class="bg-gray-800 rounded-lg shadow-md overflow-hidden">
<div class="flex items-center justify-between p-4 bg-gray-700 border-b border-gray-600">
<div class="flex items-center gap-2 text-lg font-semibold">
<i class="fas fa-images text-blue-500"></i>
<span>Sprites</span>
<i class="fas fa-bolt text-blue-500"></i>
<span>Quick Actions</span>
</div>
</div>
<div class="p-6">
<sprite-list :sprites="sprites" @sprite-clicked="handleSpriteClick" />
</div>
</div>
<!-- Tools Card -->
<div class="bg-gray-800 rounded-lg shadow-md overflow-hidden">
<div class="flex items-center justify-between p-4 bg-gray-700 border-b border-gray-600">
<div class="flex items-center gap-2 text-lg font-semibold">
<i class="fas fa-tools text-blue-500"></i>
<span>Tools</span>
</div>
</div>
<div class="p-6">
<div class="flex flex-wrap gap-3 mb-6">
<button @click="autoArrangeSprites" :disabled="sprites.length === 0" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors disabled:opacity-60 disabled:cursor-not-allowed hover:border-blue-500">
<i class="fas fa-th"></i> Auto Arrange
<div class="flex flex-col gap-3">
<button @click="openSpritesModal" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500">
<i class="fas fa-images"></i> Manage Sprites
<span v-if="sprites.length > 0" class="ml-auto bg-blue-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
{{ sprites.length }}
</span>
</button>
<button @click="openSettingsModal" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500"><i class="fas fa-cog"></i> Settings & Tools</button>
<button @click="openPreviewModal" :disabled="sprites.length === 0" class="flex items-center gap-2 bg-blue-500 border border-blue-500 text-white rounded px-4 py-2 text-sm transition-colors disabled:opacity-60 disabled:cursor-not-allowed hover:bg-blue-600 hover:border-blue-600">
<i class="fas fa-play"></i> Preview Animation
</button>
<button @click="downloadSpritesheet" :disabled="sprites.length === 0" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors disabled:opacity-60 disabled:cursor-not-allowed hover:border-blue-500">
<i class="fas fa-download"></i> Download
</button>
<button @click="confirmClearAll" :disabled="sprites.length === 0" class="flex items-center gap-2 bg-red-600 border border-red-600 text-white rounded px-4 py-2 text-sm transition-colors disabled:opacity-60 disabled:cursor-not-allowed hover:bg-red-700 hover:border-red-700">
<i class="fas fa-trash-alt"></i> Clear All
</button>
</div>
</div>
</div>
<!-- Zoom Controls -->
<div class="flex flex-wrap gap-3 mb-6">
<div class="text-sm font-medium text-gray-300 mb-2 w-full">Zoom Controls</div>
<button @click="zoomIn" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500"><i class="fas fa-search-plus"></i> Zoom In</button>
<button @click="zoomOut" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500"><i class="fas fa-search-minus"></i> Zoom Out</button>
<button @click="resetZoom" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500"><i class="fas fa-sync-alt"></i> Reset Zoom</button>
<!-- Stats Card -->
<div v-if="sprites.length > 0" class="bg-gray-800 rounded-lg shadow-md overflow-hidden">
<div class="flex items-center justify-between p-4 bg-gray-700 border-b border-gray-600">
<div class="flex items-center gap-2 text-lg font-semibold">
<i class="fas fa-chart-bar text-blue-500"></i>
<span>Stats</span>
</div>
<div class="flex flex-wrap gap-3 mt-4">
<div class="text-sm font-medium text-gray-300 mb-2 w-full">Keyboard Shortcuts</div>
<div class="flex items-center gap-2 text-sm text-gray-400">
<kbd class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-xs font-mono">Shift</kbd>
<span>Fine-tune position</span>
</div>
<div class="p-4">
<div class="grid grid-cols-2 gap-4">
<div class="bg-gray-700 p-3 rounded">
<div class="text-sm text-gray-400">Sprites</div>
<div class="text-xl font-semibold">{{ sprites.length }}</div>
</div>
<div class="flex items-center gap-2 text-sm text-gray-400">
<kbd class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-xs font-mono">Space</kbd>
<span>Play/Pause animation</span>
<div class="bg-gray-700 p-3 rounded">
<div class="text-sm text-gray-400">Cell Size</div>
<div class="text-xl font-semibold">{{ cellSize.width }}×{{ cellSize.height }}</div>
</div>
<div class="flex items-center gap-2 text-sm text-gray-400">
<kbd class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-xs font-mono">Esc</kbd>
<span>Close preview</span>
<div class="bg-gray-700 p-3 rounded">
<div class="text-sm text-gray-400">Zoom</div>
<div class="text-xl font-semibold">{{ Math.round(zoomLevel * 100) }}%</div>
</div>
<div class="flex items-center gap-2 text-sm text-gray-400">
<div class="flex items-center">
<kbd class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-xs font-mono">Ctrl</kbd>
<span class="mx-1">+</span>
<kbd class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-xs font-mono">+</kbd>
</div>
<span>Zoom in</span>
</div>
<div class="flex items-center gap-2 text-sm text-gray-400">
<div class="flex items-center">
<kbd class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-xs font-mono">Ctrl</kbd>
<span class="mx-1">+</span>
<kbd class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-xs font-mono">-</kbd>
</div>
<span>Zoom out</span>
</div>
<div class="flex items-center gap-2 text-sm text-gray-400">
<div class="flex items-center">
<kbd class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-xs font-mono">Ctrl</kbd>
<span class="mx-1">+</span>
<kbd class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-xs font-mono">0</kbd>
</div>
<span>Reset zoom</span>
<div class="bg-gray-700 p-3 rounded">
<div class="text-sm text-gray-400">Columns</div>
<div class="text-xl font-semibold">{{ columns }}</div>
</div>
</div>
</div>
@ -106,20 +75,18 @@
import { computed } from 'vue';
import { type Sprite, useSpritesheetStore } from '../composables/useSpritesheetStore';
import DropZone from './DropZone.vue';
import SpriteList from './SpriteList.vue';
const store = useSpritesheetStore();
const sprites = computed(() => store.sprites.value);
const cellSize = computed(() => store.cellSize);
const zoomLevel = computed(() => store.zoomLevel.value);
const columns = computed(() => store.columns.value);
const handleUpload = (sprites: Sprite[]) => {
// The dropzone component handles adding sprites to the store
// This is just for event handling if needed
};
const handleSpriteClick = (spriteId: string) => {
store.highlightSprite(spriteId);
};
const openPreviewModal = () => {
if (store.sprites.value.length === 0) {
store.showNotification('Please add sprites first', 'error');
@ -129,13 +96,11 @@
store.isModalOpen.value = true;
};
const confirmClearAll = () => {
if (confirm('Are you sure you want to clear all sprites?')) {
store.clearAllSprites();
store.showNotification('All sprites cleared');
}
const openSpritesModal = () => {
store.isSpritesModalOpen.value = true;
};
// Expose store methods directly
const { autoArrangeSprites, downloadSpritesheet, zoomIn, zoomOut, resetZoom } = store;
const openSettingsModal = () => {
store.isSettingsModalOpen.value = true;
};
</script>

View File

@ -0,0 +1,81 @@
<template>
<!-- Sprites Modal -->
<div class="fixed inset-0 flex items-center justify-center z-50 pointer-events-none" v-if="isModalOpen">
<!-- Modal backdrop with semi-transparent background -->
<div class="absolute inset-0 bg-black bg-opacity-50 pointer-events-auto" @click="closeModal"></div>
<!-- Modal content -->
<div class="bg-gray-800 rounded-lg max-w-2xl w-full max-h-[90vh] overflow-auto shadow-lg pointer-events-auto relative">
<div class="flex items-center justify-between p-4 bg-gray-700 border-b border-gray-600">
<div class="flex items-center gap-2 text-lg font-semibold">
<i class="fas fa-images text-blue-500"></i>
<span>Sprites</span>
</div>
<button @click="closeModal" class="text-gray-400 hover:text-white">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-6">
<!-- Sprites List -->
<div v-if="sprites.length === 0" class="text-center text-gray-400 py-8">
<i class="fas fa-image text-4xl mb-4 opacity-30"></i>
<p>No sprites uploaded yet</p>
<button @click="showUploadSection" class="mt-4 flex items-center gap-2 bg-blue-500 border border-blue-500 text-white rounded px-4 py-2 text-sm transition-colors mx-auto hover:bg-blue-600 hover:border-blue-600"><i class="fas fa-upload"></i> Upload Sprites</button>
</div>
<div v-else>
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-200 flex items-center gap-2"><i class="fas fa-images text-blue-500"></i> Uploaded Sprites</h3>
<span class="text-sm text-gray-400">{{ sprites.length }} sprites</span>
</div>
<div class="grid grid-cols-3 gap-4 max-h-96 overflow-y-auto p-2">
<div v-for="(sprite, index) in sprites" :key="sprite.id" @click="handleSpriteClick(sprite.id)" class="border border-gray-600 rounded bg-gray-700 p-3 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-20 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>
</div>
<div class="flex justify-end gap-3 mt-6">
<button @click="showUploadSection" class="flex items-center gap-2 bg-gray-700 text-gray-200 border border-gray-600 rounded px-4 py-2 text-sm transition-colors hover:border-blue-500"><i class="fas fa-upload"></i> Upload More</button>
<button @click="confirmClearAll" class="flex items-center gap-2 bg-red-600 border border-red-600 text-white rounded px-4 py-2 text-sm transition-colors hover:bg-red-700 hover:border-red-700"><i class="fas fa-trash-alt"></i> Clear All</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
const store = useSpritesheetStore();
const sprites = computed(() => store.sprites.value);
const isModalOpen = computed(() => store.isSpritesModalOpen.value);
const closeModal = () => {
store.isSpritesModalOpen.value = false;
};
const handleSpriteClick = (spriteId: string) => {
store.highlightSprite(spriteId);
};
const showUploadSection = () => {
// Close sprites modal and focus on upload section
closeModal();
};
const confirmClearAll = () => {
if (confirm('Are you sure you want to clear all sprites?')) {
store.clearAllSprites();
store.showNotification('All sprites cleared');
}
};
const truncateName = (name: string) => {
return name.length > 15 ? `${name.substring(0, 15)}...` : name;
};
</script>

View File

@ -37,6 +37,8 @@ const draggedSprite = ref<Sprite | null>(null);
const dragOffset = reactive({ x: 0, y: 0 });
const isShiftPressed = ref(false);
const isModalOpen = ref(false);
const isSettingsModalOpen = ref(false);
const isSpritesModalOpen = ref(false);
const zoomLevel = ref(1); // Default zoom level (1 = 100%)
export function useSpritesheetStore() {
@ -446,6 +448,8 @@ export function useSpritesheetStore() {
dragOffset,
isShiftPressed,
isModalOpen,
isSettingsModalOpen,
isSpritesModalOpen,
animation,
notification,
zoomLevel,