App
This commit is contained in:
parent
085fbde0a3
commit
183ea2b9eb
49
src/App.vue
49
src/App.vue
@ -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(
|
||||
|
@ -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>
|
||||
|
88
src/components/Navigation.vue
Normal file
88
src/components/Navigation.vue
Normal 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>
|
108
src/components/SettingsModal.vue
Normal file
108
src/components/SettingsModal.vue
Normal 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>
|
@ -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>
|
||||
|
81
src/components/SpritesModal.vue
Normal file
81
src/components/SpritesModal.vue
Normal 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>
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user