use options api
This commit is contained in:
parent
a989c8719f
commit
22781b1883
@ -2,11 +2,11 @@
|
||||
<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">Noxious Spritesheet Creator</h1>
|
||||
<h1 class="text-xl font-semibold text-gray-200">Spritesheet Creator</h1>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
@click="$emit('toggleHelp')"
|
||||
@click="emit('toggleHelp')"
|
||||
class="p-2 bg-gray-700 border border-gray-600 rounded hover:border-blue-500 transition-colors"
|
||||
title="Keyboard Shortcuts"
|
||||
>
|
||||
@ -16,11 +16,8 @@
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AppHeader',
|
||||
emits: ['toggleHelp']
|
||||
});
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits<{
|
||||
(e: 'toggleHelp'): void
|
||||
}>()
|
||||
</script>
|
@ -22,15 +22,14 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { Sprite } from '../composables/useSpritesheetStore';
|
||||
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { type Sprite, useSpritesheetStore } from '../composables/useSpritesheetStore';
|
||||
|
||||
const emit = defineEmits<{
|
||||
'files-uploaded': [sprites: Sprite[]]
|
||||
}>();
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DropZone',
|
||||
emits: ['files-uploaded'],
|
||||
setup(props, { emit }) {
|
||||
const store = useSpritesheetStore();
|
||||
const fileInput = ref<HTMLInputElement | null>(null);
|
||||
const isDragOver = ref(false);
|
||||
@ -118,16 +117,4 @@ export default defineComponent({
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
fileInput,
|
||||
isDragOver,
|
||||
openFileDialog,
|
||||
onDragOver,
|
||||
onDragLeave,
|
||||
onDrop,
|
||||
onFileChange
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,17 +1,14 @@
|
||||
<template>
|
||||
<button
|
||||
@click="$emit('showHelp')"
|
||||
@click="emit('showHelp')"
|
||||
class="fixed bottom-5 right-5 w-12 h-12 bg-blue-500 text-white rounded-full flex items-center justify-center text-xl shadow-lg cursor-pointer transition-all hover:bg-blue-600 hover:-translate-y-1 z-40"
|
||||
>
|
||||
<i class="fas fa-question"></i>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'HelpButton',
|
||||
emits: ['showHelp']
|
||||
});
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits<{
|
||||
showHelp: []
|
||||
}>();
|
||||
</script>
|
@ -26,13 +26,10 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, onMounted, computed, onBeforeUnmount } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MainContent',
|
||||
setup() {
|
||||
const store = useSpritesheetStore();
|
||||
const canvasEl = ref<HTMLCanvasElement | null>(null);
|
||||
|
||||
@ -46,38 +43,6 @@ export default defineComponent({
|
||||
top: `${tooltipPosition.value.y + 15}px`
|
||||
}));
|
||||
|
||||
onMounted(() => {
|
||||
if (canvasEl.value) {
|
||||
store.canvas.value = canvasEl.value;
|
||||
store.ctx.value = canvasEl.value.getContext('2d');
|
||||
|
||||
// Initialize canvas size
|
||||
canvasEl.value.width = 400;
|
||||
canvasEl.value.height = 300;
|
||||
|
||||
// Set up checkerboard background pattern
|
||||
setupCheckerboardPattern();
|
||||
|
||||
setupCanvasEvents();
|
||||
|
||||
// Setup keyboard events for modifiers
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
window.addEventListener('keyup', handleKeyUp);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
window.removeEventListener('keyup', handleKeyUp);
|
||||
|
||||
if (canvasEl.value) {
|
||||
canvasEl.value.removeEventListener('mousedown', handleMouseDown);
|
||||
canvasEl.value.removeEventListener('mousemove', handleMouseMove);
|
||||
canvasEl.value.removeEventListener('mouseup', handleMouseUp);
|
||||
canvasEl.value.removeEventListener('mouseout', handleMouseOut);
|
||||
}
|
||||
});
|
||||
|
||||
const setupCheckerboardPattern = () => {
|
||||
if (!canvasEl.value) return;
|
||||
|
||||
@ -92,15 +57,6 @@ export default defineComponent({
|
||||
canvasEl.value.style.backgroundPosition = '0 0, 0 10px, 10px -10px, -10px 0px';
|
||||
};
|
||||
|
||||
const setupCanvasEvents = () => {
|
||||
if (!canvasEl.value) return;
|
||||
|
||||
canvasEl.value.addEventListener('mousedown', handleMouseDown);
|
||||
canvasEl.value.addEventListener('mousemove', handleMouseMove);
|
||||
canvasEl.value.addEventListener('mouseup', handleMouseUp);
|
||||
canvasEl.value.addEventListener('mouseout', handleMouseOut);
|
||||
};
|
||||
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
if (!canvasEl.value || store.sprites.value.length === 0) return;
|
||||
|
||||
@ -181,18 +137,10 @@ export default defineComponent({
|
||||
const boundedCellX = Math.max(0, Math.min(newCellX, maxCellX));
|
||||
const boundedCellY = Math.max(0, Math.min(newCellY, maxCellY));
|
||||
|
||||
// Update sprite position to snap to grid
|
||||
store.draggedSprite.value.x = boundedCellX * store.cellSize.width;
|
||||
store.draggedSprite.value.y = boundedCellY * store.cellSize.height;
|
||||
}
|
||||
}
|
||||
|
||||
store.renderSpritesheetPreview();
|
||||
|
||||
// Update animation preview if paused
|
||||
if (!store.animation.isPlaying && store.sprites.value.length > 0 && store.isModalOpen.value) {
|
||||
store.renderAnimationFrame(store.animation.currentFrame);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -201,8 +149,8 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
const handleMouseOut = () => {
|
||||
store.draggedSprite.value = null;
|
||||
isTooltipVisible.value = false;
|
||||
store.draggedSprite.value = null;
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
@ -217,12 +165,42 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
canvasEl,
|
||||
isTooltipVisible,
|
||||
tooltipText,
|
||||
tooltipStyle
|
||||
const setupCanvasEvents = () => {
|
||||
if (!canvasEl.value) return;
|
||||
|
||||
canvasEl.value.addEventListener('mousedown', handleMouseDown);
|
||||
canvasEl.value.addEventListener('mousemove', handleMouseMove);
|
||||
canvasEl.value.addEventListener('mouseup', handleMouseUp);
|
||||
canvasEl.value.addEventListener('mouseout', handleMouseOut);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (canvasEl.value) {
|
||||
store.canvas.value = canvasEl.value;
|
||||
store.ctx.value = canvasEl.value.getContext('2d');
|
||||
|
||||
// Initialize canvas size
|
||||
canvasEl.value.width = 400;
|
||||
canvasEl.value.height = 300;
|
||||
|
||||
setupCheckerboardPattern();
|
||||
setupCanvasEvents();
|
||||
|
||||
// Setup keyboard events for modifiers
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
window.addEventListener('keyup', handleKeyUp);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
window.removeEventListener('keyup', handleKeyUp);
|
||||
|
||||
if (canvasEl.value) {
|
||||
canvasEl.value.removeEventListener('mousedown', handleMouseDown);
|
||||
canvasEl.value.removeEventListener('mousemove', handleMouseMove);
|
||||
canvasEl.value.removeEventListener('mouseup', handleMouseUp);
|
||||
canvasEl.value.removeEventListener('mouseout', handleMouseOut);
|
||||
}
|
||||
});
|
||||
</script>
|
@ -25,25 +25,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useSpritesheetStore } from '../composables/useSpritesheetStore'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Notification',
|
||||
setup() {
|
||||
const store = useSpritesheetStore();
|
||||
const store = useSpritesheetStore()
|
||||
|
||||
const notification = computed(() => store.notification);
|
||||
const notification = computed(() => store.notification)
|
||||
|
||||
const closeNotification = () => {
|
||||
store.notification.isVisible = false;
|
||||
};
|
||||
|
||||
return {
|
||||
notification,
|
||||
closeNotification
|
||||
};
|
||||
store.notification.isVisible = false
|
||||
}
|
||||
});
|
||||
</script>
|
@ -80,147 +80,128 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, onMounted, computed, watch, onBeforeUnmount } from 'vue';
|
||||
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useSpritesheetStore } from '../composables/useSpritesheetStore'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PreviewModal',
|
||||
setup() {
|
||||
const store = useSpritesheetStore();
|
||||
const animCanvas = ref<HTMLCanvasElement | null>(null);
|
||||
const store = useSpritesheetStore()
|
||||
const animCanvas = ref<HTMLCanvasElement | null>(null)
|
||||
|
||||
const isModalOpen = computed(() => store.isModalOpen.value);
|
||||
const sprites = computed(() => store.sprites.value);
|
||||
const animation = computed(() => store.animation);
|
||||
const isModalOpen = computed(() => store.isModalOpen.value)
|
||||
const sprites = computed(() => store.sprites.value)
|
||||
const animation = computed(() => store.animation)
|
||||
|
||||
const currentFrame = ref(0);
|
||||
const currentFrame = ref(0)
|
||||
|
||||
const currentFrameDisplay = computed(() => {
|
||||
const totalFrames = Math.max(1, sprites.value.length);
|
||||
const frame = Math.min(currentFrame.value + 1, totalFrames);
|
||||
return `${frame} / ${totalFrames}`;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (animCanvas.value) {
|
||||
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
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
const totalFrames = Math.max(1, sprites.value.length)
|
||||
const frame = Math.min(currentFrame.value + 1, totalFrames)
|
||||
return `${frame} / ${totalFrames}`
|
||||
})
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (!isModalOpen.value) return;
|
||||
if (!isModalOpen.value) return
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
closeModal();
|
||||
closeModal()
|
||||
} else if (e.key === ' ' || e.key === 'Spacebar') {
|
||||
// Toggle play/pause
|
||||
if (animation.value.isPlaying) {
|
||||
stopAnimation();
|
||||
stopAnimation()
|
||||
} else if (sprites.value.length > 0) {
|
||||
startAnimation();
|
||||
startAnimation()
|
||||
}
|
||||
e.preventDefault();
|
||||
e.preventDefault()
|
||||
} else if (e.key === 'ArrowRight' && !animation.value.isPlaying && sprites.value.length > 0) {
|
||||
// Next frame
|
||||
currentFrame.value = (currentFrame.value + 1) % sprites.value.length;
|
||||
updateFrame();
|
||||
currentFrame.value = (currentFrame.value + 1) % sprites.value.length
|
||||
updateFrame()
|
||||
} else if (e.key === 'ArrowLeft' && !animation.value.isPlaying && sprites.value.length > 0) {
|
||||
// Previous frame
|
||||
currentFrame.value = (currentFrame.value - 1 + sprites.value.length) % sprites.value.length;
|
||||
updateFrame();
|
||||
currentFrame.value = (currentFrame.value - 1 + sprites.value.length) % sprites.value.length
|
||||
updateFrame()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const openModal = () => {
|
||||
if (sprites.value.length === 0) {
|
||||
store.showNotification('Please add sprites first', 'error');
|
||||
return;
|
||||
store.showNotification('Please add sprites first', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
store.isModalOpen.value = true;
|
||||
store.isModalOpen.value = true
|
||||
|
||||
// Show the current frame
|
||||
if (!animation.value.isPlaying && sprites.value.length > 0) {
|
||||
store.renderAnimationFrame(currentFrame.value);
|
||||
store.renderAnimationFrame(currentFrame.value)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
store.isModalOpen.value = false;
|
||||
store.isModalOpen.value = false
|
||||
|
||||
// Stop animation if it's playing
|
||||
if (animation.value.isPlaying) {
|
||||
stopAnimation();
|
||||
stopAnimation()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const startAnimation = () => {
|
||||
if (sprites.value.length === 0) return;
|
||||
|
||||
store.startAnimation();
|
||||
};
|
||||
if (sprites.value.length === 0) return
|
||||
store.startAnimation()
|
||||
}
|
||||
|
||||
const stopAnimation = () => {
|
||||
store.stopAnimation();
|
||||
};
|
||||
store.stopAnimation()
|
||||
}
|
||||
|
||||
const handleFrameChange = () => {
|
||||
// Stop any running animation
|
||||
if (animation.value.isPlaying) {
|
||||
stopAnimation();
|
||||
stopAnimation()
|
||||
}
|
||||
updateFrame()
|
||||
}
|
||||
|
||||
updateFrame();
|
||||
};
|
||||
|
||||
const updateFrame = () => {
|
||||
animation.value.currentFrame = currentFrame.value;
|
||||
animation.value.manualUpdate = true;
|
||||
store.renderAnimationFrame(currentFrame.value);
|
||||
};
|
||||
animation.value.currentFrame = currentFrame.value
|
||||
animation.value.manualUpdate = true
|
||||
store.renderAnimationFrame(currentFrame.value)
|
||||
}
|
||||
|
||||
const handleFrameRateChange = () => {
|
||||
// If animation is currently playing, restart it with the new frame rate
|
||||
if (animation.value.isPlaying) {
|
||||
stopAnimation();
|
||||
startAnimation();
|
||||
stopAnimation()
|
||||
startAnimation()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (animCanvas.value) {
|
||||
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
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
})
|
||||
|
||||
// Keep currentFrame in sync with animation.currentFrame
|
||||
watch(() => animation.value.currentFrame, (newVal) => {
|
||||
currentFrame.value = newVal;
|
||||
});
|
||||
currentFrame.value = newVal
|
||||
})
|
||||
|
||||
return {
|
||||
animCanvas,
|
||||
isModalOpen,
|
||||
sprites,
|
||||
animation,
|
||||
currentFrame,
|
||||
currentFrameDisplay,
|
||||
openModal,
|
||||
closeModal,
|
||||
startAnimation,
|
||||
stopAnimation,
|
||||
handleFrameChange,
|
||||
handleFrameRateChange
|
||||
};
|
||||
}
|
||||
});
|
||||
// Expose openModal for external use
|
||||
defineExpose({ openModal })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -89,20 +89,14 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
|
||||
import DropZone from './DropZone.vue';
|
||||
import SpriteList from './SpriteList.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Sidebar',
|
||||
components: {
|
||||
DropZone,
|
||||
SpriteList
|
||||
},
|
||||
setup() {
|
||||
const store = useSpritesheetStore();
|
||||
const sprites = computed(() => store.sprites.value);
|
||||
|
||||
const handleUpload = () => {
|
||||
// The dropzone component handles adding sprites to the store
|
||||
@ -129,15 +123,6 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
sprites: computed(() => store.sprites.value),
|
||||
autoArrangeSprites: store.autoArrangeSprites,
|
||||
downloadSpritesheet: store.downloadSpritesheet,
|
||||
confirmClearAll,
|
||||
handleUpload,
|
||||
handleSpriteClick,
|
||||
openPreviewModal
|
||||
};
|
||||
}
|
||||
});
|
||||
// Expose store methods directly
|
||||
const { autoArrangeSprites, downloadSpritesheet } = store;
|
||||
</script>
|
@ -7,7 +7,7 @@
|
||||
<div
|
||||
v-for="(sprite, index) in sprites"
|
||||
:key="sprite.id"
|
||||
@click="$emit('sprite-clicked', 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
|
||||
@ -22,27 +22,18 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from 'vue';
|
||||
import { type Sprite } from '../composables/useSpritesheetStore';
|
||||
<script setup lang="ts">
|
||||
import type { Sprite } from '../composables/useSpritesheetStore'
|
||||
|
||||
defineProps<{
|
||||
sprites: Sprite[]
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
spriteClicked: [id: string]
|
||||
}>()
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SpriteList',
|
||||
props: {
|
||||
sprites: {
|
||||
type: Array as PropType<Sprite[]>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['sprite-clicked'],
|
||||
setup() {
|
||||
const truncateName = (name: string) => {
|
||||
return name.length > 10 ? `${name.substring(0, 10)}...` : name;
|
||||
};
|
||||
|
||||
return {
|
||||
truncateName
|
||||
};
|
||||
return name.length > 10 ? `${name.substring(0, 10)}...` : name
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,12 +0,0 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment }
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user