[FEAT] Move sprite with arrow in preview, UI enhancement btns
This commit is contained in:
@@ -1,32 +1,64 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="showContextMenu" @click.stop class="fixed bg-white dark:bg-gray-800 border-2 border-gray-300 dark:border-gray-600 rounded-xl shadow-2xl z-50 py-2 min-w-[200px] overflow-hidden" :style="{ left: contextMenuX + 'px', top: contextMenuY + 'px' }">
|
||||
<button @click="addSprite" class="w-full px-5 py-3 text-left hover:bg-blue-50 dark:hover:bg-blue-900/30 text-gray-700 dark:text-gray-200 flex items-center gap-3 transition-colors font-medium">
|
||||
<i class="fas fa-plus text-blue-600 dark:text-blue-400"></i>
|
||||
<div v-if="showContextMenu" @click.stop class="fixed bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg shadow-xl z-50 py-1 min-w-[160px] overflow-hidden" :style="{ left: contextMenuX + 'px', top: contextMenuY + 'px' }">
|
||||
<button @click="addSprite" class="w-full px-3 py-1.5 text-left hover:bg-blue-50 dark:hover:bg-blue-900/30 text-gray-700 dark:text-gray-200 flex items-center gap-2 transition-colors text-sm">
|
||||
<i class="fas fa-plus text-blue-600 dark:text-blue-400 text-xs w-4"></i>
|
||||
<span>Add Sprite</span>
|
||||
</button>
|
||||
<button v-if="contextMenuSpriteId" @click="rotateSpriteInMenu(90)" class="w-full px-5 py-3 text-left hover:bg-green-50 dark:hover:bg-green-900/30 text-gray-700 dark:text-gray-200 flex items-center gap-3 transition-colors font-medium">
|
||||
<i class="fas fa-redo text-green-600 dark:text-green-400"></i>
|
||||
<button v-if="contextMenuSpriteId" @click="rotateSpriteInMenu(90)" class="w-full px-3 py-1.5 text-left hover:bg-green-50 dark:hover:bg-green-900/30 text-gray-700 dark:text-gray-200 flex items-center gap-2 transition-colors text-sm">
|
||||
<i class="fas fa-redo text-green-600 dark:text-green-400 text-xs w-4"></i>
|
||||
<span>Rotate +90°</span>
|
||||
</button>
|
||||
<button v-if="contextMenuSpriteId" @click="flipSpriteInMenu('horizontal')" class="w-full px-5 py-3 text-left hover:bg-orange-50 dark:hover:bg-orange-900/30 text-gray-700 dark:text-gray-200 flex items-center gap-3 transition-colors font-medium">
|
||||
<i class="fas fa-arrows-alt-h text-orange-600 dark:text-orange-400"></i>
|
||||
<button v-if="contextMenuSpriteId" @click="flipSpriteInMenu('horizontal')" class="w-full px-3 py-1.5 text-left hover:bg-orange-50 dark:hover:bg-orange-900/30 text-gray-700 dark:text-gray-200 flex items-center gap-2 transition-colors text-sm">
|
||||
<i class="fas fa-arrows-alt-h text-orange-600 dark:text-orange-400 text-xs w-4"></i>
|
||||
<span>Flip Horizontal</span>
|
||||
</button>
|
||||
<button v-if="contextMenuSpriteId" @click="flipSpriteInMenu('vertical')" class="w-full px-5 py-3 text-left hover:bg-orange-50 dark:hover:bg-orange-900/30 text-gray-700 dark:text-gray-200 flex items-center gap-3 transition-colors font-medium">
|
||||
<i class="fas fa-arrows-alt-v text-orange-600 dark:text-orange-400"></i>
|
||||
<button v-if="contextMenuSpriteId" @click="flipSpriteInMenu('vertical')" class="w-full px-3 py-1.5 text-left hover:bg-orange-50 dark:hover:bg-orange-900/30 text-gray-700 dark:text-gray-200 flex items-center gap-2 transition-colors text-sm">
|
||||
<i class="fas fa-arrows-alt-v text-orange-600 dark:text-orange-400 text-xs w-4"></i>
|
||||
<span>Flip Vertical</span>
|
||||
</button>
|
||||
<button v-if="contextMenuSpriteId" @click="replaceSprite" class="w-full px-5 py-3 text-left hover:bg-purple-50 dark:hover:bg-purple-900/30 text-gray-700 dark:text-gray-200 flex items-center gap-3 transition-colors font-medium">
|
||||
<i class="fas fa-exchange-alt text-purple-600 dark:text-purple-400"></i>
|
||||
<button v-if="contextMenuSpriteId" @click="replaceSprite" class="w-full px-3 py-1.5 text-left hover:bg-purple-50 dark:hover:bg-purple-900/30 text-gray-700 dark:text-gray-200 flex items-center gap-2 transition-colors text-sm">
|
||||
<i class="fas fa-exchange-alt text-purple-600 dark:text-purple-400 text-xs w-4"></i>
|
||||
<span>Replace Sprite</span>
|
||||
</button>
|
||||
<button v-if="contextMenuSpriteId" @click="openCopyToFrameModal" class="w-full px-3 py-1.5 text-left hover:bg-cyan-50 dark:hover:bg-cyan-900/30 text-gray-700 dark:text-gray-200 flex items-center gap-2 transition-colors text-sm">
|
||||
<i class="fas fa-copy text-cyan-600 dark:text-cyan-400 text-xs w-4"></i>
|
||||
<span>Copy to Frame...</span>
|
||||
</button>
|
||||
<div v-if="contextMenuSpriteId" class="h-px bg-gray-200 dark:bg-gray-600 my-1"></div>
|
||||
<button v-if="contextMenuSpriteId" @click="removeSprite" class="w-full px-5 py-3 text-left hover:bg-red-50 dark:hover:bg-red-900/30 text-red-600 dark:text-red-400 flex items-center gap-3 transition-colors font-medium">
|
||||
<i class="fas fa-trash"></i>
|
||||
<button v-if="contextMenuSpriteId" @click="removeSprite" class="w-full px-3 py-1.5 text-left hover:bg-red-50 dark:hover:bg-red-900/30 text-red-600 dark:text-red-400 flex items-center gap-2 transition-colors text-sm">
|
||||
<i class="fas fa-trash text-xs w-4"></i>
|
||||
<span>{{ selectedSpriteIds.size > 1 ? `Remove ${selectedSpriteIds.size} Sprites` : 'Remove Sprite' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Copy to Frame Modal -->
|
||||
<div v-if="showCopyToFrameModal" class="fixed inset-0 bg-black/50 z-[60] flex items-center justify-center" @click.self="closeCopyToFrameModal">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl p-6 min-w-[320px] max-w-md" @click.stop>
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100 mb-4">Copy Sprite to Frame</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Target Frame</label>
|
||||
<select v-model.number="copyTargetFrame" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500">
|
||||
<option v-for="i in maxFrameCount" :key="i" :value="i - 1">Frame {{ i }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Target Layer</label>
|
||||
<select v-model="copyTargetLayerId" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500">
|
||||
<option v-for="layer in props.layers" :key="layer.id" :value="layer.id">{{ layer.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 mt-6">
|
||||
<button @click="closeCopyToFrameModal" class="px-4 py-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors">Cancel</button>
|
||||
<button @click="confirmCopyToFrame" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<div class="h-full w-full flex flex-col p-4">
|
||||
@@ -197,6 +229,7 @@
|
||||
(e: 'addSpriteWithResize', file: File): void;
|
||||
(e: 'rotateSprite', id: string, angle: number): void;
|
||||
(e: 'flipSprite', id: string, direction: 'horizontal' | 'vertical'): void;
|
||||
(e: 'copySpriteToFrame', spriteId: string, targetLayerId: string, targetFrameIndex: number): void;
|
||||
}>();
|
||||
|
||||
// Get settings from store
|
||||
@@ -263,6 +296,17 @@
|
||||
const replacingSpriteId = ref<string | null>(null);
|
||||
const fileInput = ref<HTMLInputElement | null>(null);
|
||||
|
||||
// Copy to frame modal state
|
||||
const showCopyToFrameModal = ref(false);
|
||||
const copyTargetFrame = ref(0);
|
||||
const copyTargetLayerId = ref(props.activeLayerId);
|
||||
const copySpriteId = ref<string | null>(null);
|
||||
|
||||
const maxFrameCount = computed(() => {
|
||||
const maxLen = Math.max(1, ...props.layers.map(l => l.sprites.length));
|
||||
return maxLen + 1; // Allow copying to one frame beyond current max
|
||||
});
|
||||
|
||||
// Clear selection when toggling multi-select mode
|
||||
watch(
|
||||
() => props.isMultiSelectMode,
|
||||
@@ -510,6 +554,28 @@
|
||||
contextMenuSpriteId.value = null;
|
||||
};
|
||||
|
||||
const openCopyToFrameModal = () => {
|
||||
if (contextMenuSpriteId.value) {
|
||||
copySpriteId.value = contextMenuSpriteId.value;
|
||||
copyTargetLayerId.value = props.activeLayerId;
|
||||
copyTargetFrame.value = 0;
|
||||
showCopyToFrameModal.value = true;
|
||||
showContextMenu.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const closeCopyToFrameModal = () => {
|
||||
showCopyToFrameModal.value = false;
|
||||
copySpriteId.value = null;
|
||||
};
|
||||
|
||||
const confirmCopyToFrame = () => {
|
||||
if (copySpriteId.value) {
|
||||
emit('copySpriteToFrame', copySpriteId.value, copyTargetLayerId.value, copyTargetFrame.value);
|
||||
closeCopyToFrameModal();
|
||||
}
|
||||
};
|
||||
|
||||
const onDragLeave = (event: DragEvent) => {
|
||||
handleDragLeave(event, gridContainerRef.value);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user