[FEAT] Add add new frame btn

This commit is contained in:
2026-01-03 17:21:15 +01:00
parent 2f0404d698
commit e290eb21a4
2 changed files with 260 additions and 274 deletions

View File

@@ -15,12 +15,7 @@
class="aspect-square bg-white dark:bg-gray-800 rounded-lg border-2 border-transparent hover:border-indigo-500 transition-all overflow-hidden group relative"
:class="{ 'opacity-50': !sprite.url }"
>
<img
v-if="sprite.url"
:src="sprite.url"
class="w-full h-full object-contain relative z-10"
:style="{ imageRendering: settingsStore.pixelPerfect ? 'pixelated' : 'auto' }"
/>
<img v-if="sprite.url" :src="sprite.url" class="w-full h-full object-contain relative z-10" :style="{ imageRendering: settingsStore.pixelPerfect ? 'pixelated' : 'auto' }" />
<div v-else class="w-full h-full flex items-center justify-center text-gray-400 relative z-10">
<i class="fas fa-image"></i>
</div>
@@ -29,6 +24,17 @@
</span>
<i class="fas fa-edit text-indigo-500 opacity-0 group-hover:opacity-100 transition-opacity text-[10px] absolute top-1 left-1 z-20"></i>
</button>
<!-- Add New Frame Button -->
<button
@click="addNewFrame(layer)"
class="aspect-square bg-gray-50 dark:bg-gray-800/50 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-700 hover:border-indigo-500 hover:bg-gray-100 dark:hover:bg-gray-800 transition-all flex items-center justify-center text-gray-400 hover:text-indigo-500 group"
>
<div class="flex flex-col items-center gap-1">
<i class="fas fa-plus text-xl group-hover:scale-110 transition-transform"></i>
<span class="text-[10px] font-medium uppercase tracking-wide">Add</span>
</div>
</button>
</div>
</div>
</div>
@@ -41,20 +47,12 @@
<!-- Tools -->
<div class="flex bg-gray-100 dark:bg-gray-700 rounded-lg p-1">
<Tooltip text="Pencil (P)">
<button
@click="editor.currentTool.value = 'pencil'"
:class="editor.currentTool.value === 'pencil' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-200 dark:hover:bg-gray-600'"
class="p-2 rounded-md transition-all"
>
<button @click="editor.currentTool.value = 'pencil'" :class="editor.currentTool.value === 'pencil' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-200 dark:hover:bg-gray-600'" class="p-2 rounded-md transition-all">
<i class="fas fa-pencil-alt text-gray-600 dark:text-gray-300"></i>
</button>
</Tooltip>
<Tooltip text="Eraser (E)">
<button
@click="editor.currentTool.value = 'eraser'"
:class="editor.currentTool.value === 'eraser' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-200 dark:hover:bg-gray-600'"
class="p-2 rounded-md transition-all"
>
<button @click="editor.currentTool.value = 'eraser'" :class="editor.currentTool.value === 'eraser' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-200 dark:hover:bg-gray-600'" class="p-2 rounded-md transition-all">
<i class="fas fa-eraser text-gray-600 dark:text-gray-300"></i>
</button>
</Tooltip>
@@ -64,29 +62,15 @@
<div class="flex items-center gap-2">
<div class="relative group">
<Tooltip text="Current Color">
<div
class="w-8 h-8 rounded-lg border-2 border-gray-300 dark:border-gray-600 cursor-pointer shadow-sm overflow-hidden"
:style="{ backgroundColor: editor.currentColor.value }"
>
<input
type="color"
v-model="editor.currentColor.value"
class="absolute inset-0 opacity-0 cursor-pointer w-full h-full"
/>
<div class="w-8 h-8 rounded-lg border-2 border-gray-300 dark:border-gray-600 cursor-pointer shadow-sm overflow-hidden" :style="{ backgroundColor: editor.currentColor.value }">
<input type="color" v-model="editor.currentColor.value" class="absolute inset-0 opacity-0 cursor-pointer w-full h-full" />
</div>
</Tooltip>
</div>
<!-- Recent Colors -->
<div class="flex flex-wrap gap-1 w-20">
<div
v-for="color in recentColors.slice(0, 8)"
:key="color"
class="w-3 h-3 rounded-full border border-gray-300 dark:border-gray-600 cursor-pointer hover:scale-110 transition-transform"
:style="{ backgroundColor: color }"
@click="editor.currentColor.value = color"
:title="color"
></div>
<div v-for="color in recentColors.slice(0, 8)" :key="color" class="w-3 h-3 rounded-full border border-gray-300 dark:border-gray-600 cursor-pointer hover:scale-110 transition-transform" :style="{ backgroundColor: color }" @click="editor.currentColor.value = color" :title="color"></div>
</div>
</div>
@@ -113,18 +97,12 @@
<!-- Canvas Size -->
<Tooltip text="Resize Canvas">
<button
@click="showResizeModal = true"
class="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<button @click="showResizeModal = true" class="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors">
<i class="fas fa-expand-arrows-alt text-gray-600 dark:text-gray-300"></i>
</button>
</Tooltip>
<Tooltip text="Trim Empty Space">
<button
@click="editor.trimCanvas()"
class="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<button @click="editor.trimCanvas()" class="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors">
<i class="fas fa-crop-alt text-gray-600 dark:text-gray-300"></i>
</button>
</Tooltip>
@@ -134,22 +112,12 @@
<!-- History -->
<Tooltip text="Undo (Ctrl+Z)">
<button
@click="editor.undo()"
:disabled="!editor.canUndo.value"
:class="editor.canUndo.value ? 'hover:bg-gray-100 dark:hover:bg-gray-700' : 'opacity-40 cursor-not-allowed'"
class="p-2 rounded-lg transition-colors"
>
<button @click="editor.undo()" :disabled="!editor.canUndo.value" :class="editor.canUndo.value ? 'hover:bg-gray-100 dark:hover:bg-gray-700' : 'opacity-40 cursor-not-allowed'" class="p-2 rounded-lg transition-colors">
<i class="fas fa-undo text-gray-600 dark:text-gray-300"></i>
</button>
</Tooltip>
<Tooltip text="Redo (Ctrl+Shift+Z)">
<button
@click="editor.redo()"
:disabled="!editor.canRedo.value"
:class="editor.canRedo.value ? 'hover:bg-gray-100 dark:hover:bg-gray-700' : 'opacity-40 cursor-not-allowed'"
class="p-2 rounded-lg transition-colors"
>
<button @click="editor.redo()" :disabled="!editor.canRedo.value" :class="editor.canRedo.value ? 'hover:bg-gray-100 dark:hover:bg-gray-700' : 'opacity-40 cursor-not-allowed'" class="p-2 rounded-lg transition-colors">
<i class="fas fa-redo text-gray-600 dark:text-gray-300"></i>
</button>
</Tooltip>
@@ -167,14 +135,10 @@
</label>
<!-- Pointer Location Display -->
<div v-if="editor.showPointerLocation.value" class="text-xs font-mono text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded min-w-[60px] text-center">
{{ editor.pointerX.value }}, {{ editor.pointerY.value }}
</div>
<div v-if="editor.showPointerLocation.value" class="text-xs font-mono text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded min-w-[60px] text-center">{{ editor.pointerX.value }}, {{ editor.pointerY.value }}</div>
<!-- Canvas Size -->
<div class="text-xs font-mono text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">
{{ editor.canvasWidth.value }} × {{ editor.canvasHeight.value }}
</div>
<div class="text-xs font-mono text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">{{ editor.canvasWidth.value }} × {{ editor.canvasHeight.value }}</div>
</div>
</div>
@@ -189,10 +153,7 @@
}"
>
<!-- Background (checkerboard or solid) -->
<div
class="absolute inset-0 pointer-events-none"
:style="getBackgroundStyle()"
></div>
<div class="absolute inset-0 pointer-events-none" :style="getBackgroundStyle()"></div>
<!-- Canvas -->
<canvas
@@ -228,20 +189,10 @@
<!-- Footer Actions -->
<div class="flex items-center justify-between px-4 py-3 bg-gray-50 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 rounded-b-xl">
<Tooltip text="Discard changes">
<button
@click="cancelEdit"
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 font-medium text-sm"
>
Cancel
</button>
<button @click="cancelEdit" 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 font-medium text-sm">Cancel</button>
</Tooltip>
<Tooltip text="Save changes to frame">
<button
@click="saveEdit"
class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors font-medium text-sm"
>
<i class="fas fa-save mr-2"></i>Save
</button>
<button @click="saveEdit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors font-medium text-sm"><i class="fas fa-save mr-2"></i>Save</button>
</Tooltip>
</div>
</div>
@@ -252,23 +203,11 @@
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Width</label>
<input
type="number"
v-model.number="resizeWidth"
min="1"
max="1024"
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"
/>
<input type="number" v-model.number="resizeWidth" min="1" max="1024" 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" />
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Height</label>
<input
type="number"
v-model.number="resizeHeight"
min="1"
max="1024"
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"
/>
<input type="number" v-model.number="resizeHeight" min="1" max="1024" 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" />
</div>
</div>
@@ -288,12 +227,8 @@
</div>
<div class="flex justify-end gap-3 pt-4 border-t border-gray-100 dark:border-gray-700">
<button @click="showResizeModal = false" 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 font-medium text-sm">
Cancel
</button>
<button @click="applyResize" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors font-medium text-sm">
Apply
</button>
<button @click="showResizeModal = false" 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 font-medium text-sm">Cancel</button>
<button @click="applyResize" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors font-medium text-sm">Apply</button>
</div>
</div>
</Modal>
@@ -311,7 +246,7 @@ import type { Layer, Sprite } from '@/types/sprites';
interface SelectedFrame {
layerId: string;
frameIndex: number;
sprite: Sprite;
sprite?: Sprite;
}
const props = defineProps<{
@@ -339,11 +274,11 @@ const recentColors = ref<string[]>(['#ffffff', '#000000', '#ff0000', '#00ff00',
watch(
() => editor.currentColor.value,
(newColor) => {
newColor => {
if (!newColor) return;
// Add to recent colors if not already at the start
if (recentColors.value[0] !== newColor) {
recentColors.value = [newColor, ...recentColors.value.filter((c) => c !== newColor)].slice(0, 10);
recentColors.value = [newColor, ...recentColors.value.filter(c => c !== newColor)].slice(0, 10);
}
}
);
@@ -396,6 +331,12 @@ const selectFrame = async (layerId: string, frameIndex: number, sprite: Sprite)
await nextTick();
if (canvasRef.value) {
// If sprite exists, use its dimensions
if (sprite.width && sprite.height) {
editor.canvasWidth.value = sprite.width;
editor.canvasHeight.value = sprite.height;
}
editor.initCanvas(canvasRef.value);
if (sprite.url) {
await editor.loadFromImage(sprite.url);
@@ -403,6 +344,54 @@ const selectFrame = async (layerId: string, frameIndex: number, sprite: Sprite)
}
};
const addNewFrame = async (layer: Layer) => {
// Determine canvas size based on "biggest layer" logic
// Priority 1: Max dimensions of sprites in current layer
// Priority 2: Max dimensions of sprites in any layer
// Priority 3: Default 32x32
let targetWidth = 0;
let targetHeight = 0;
// Check current layer
if (layer.sprites.length > 0) {
targetWidth = Math.max(...layer.sprites.map(s => s.width));
targetHeight = Math.max(...layer.sprites.map(s => s.height));
}
// If still 0, check all layers
if (targetWidth === 0 || targetHeight === 0) {
props.layers.forEach(l => {
if (l.sprites.length > 0) {
targetWidth = Math.max(targetWidth, ...l.sprites.map(s => s.width));
targetHeight = Math.max(targetHeight, ...l.sprites.map(s => s.height));
}
});
}
// Default if fully empty project
if (targetWidth === 0) targetWidth = 32;
if (targetHeight === 0) targetHeight = 32;
// Set editor size
editor.canvasWidth.value = targetWidth;
editor.canvasHeight.value = targetHeight;
// Set selected frame (new)
selectedFrame.value = {
layerId: layer.id,
frameIndex: layer.sprites.length,
sprite: undefined,
};
await nextTick();
if (canvasRef.value) {
editor.initCanvas(canvasRef.value);
editor.clearCanvas(); // Ensure clean start
}
};
const cancelEdit = () => {
selectedFrame.value = null;
};
@@ -455,7 +444,7 @@ const handleKeyDown = (event: KeyboardEvent) => {
};
// Watch for resize modal opening to set current dimensions
watch(showResizeModal, (isOpen) => {
watch(showResizeModal, isOpen => {
if (isOpen) {
resizeWidth.value = editor.canvasWidth.value;
resizeHeight.value = editor.canvasHeight.value;
@@ -465,7 +454,7 @@ watch(showResizeModal, (isOpen) => {
// Handle initial frame passed from context menu
watch(
() => props.initialFrame,
async (frame) => {
async frame => {
if (frame) {
const layer = props.layers.find(l => l.id === frame.layerId);
if (layer && layer.sprites[frame.frameIndex]) {

View File

@@ -329,12 +329,7 @@
/>
</div>
<div v-else-if="activeTab === 'draw'" class="h-full">
<draw-tab
:layers="layers"
:initial-frame="pixelEditorFrame"
@save-frame="handleSaveFrame"
@close="pixelEditorFrame = null"
/>
<draw-tab :layers="layers" :initial-frame="pixelEditorFrame" @save-frame="handleSaveFrame" @close="pixelEditorFrame = null" />
</div>
</div>
</div>
@@ -676,6 +671,8 @@
if (frameIndex < layer.sprites.length) {
layer.sprites[frameIndex] = sprite;
} else {
layer.sprites.push(sprite);
}
};
img.src = url;