Added context menu for easier editing

This commit is contained in:
2025-09-15 21:02:15 +02:00
parent d1ddf5c256
commit 6d4622e109
4 changed files with 315 additions and 126 deletions

View File

@@ -103,7 +103,7 @@
</button>
</div>
<sprite-canvas :sprites="sprites" :columns="columns" @update-sprite="updateSpritePosition" @update-sprite-cell="updateSpriteCell" />
<sprite-canvas :sprites="sprites" :columns="columns" @update-sprite="updateSpritePosition" @update-sprite-cell="updateSpriteCell" @remove-sprite="removeSprite" @replace-sprite="replaceSprite" @add-sprite="addSprite" />
</div>
</div>
</div>
@@ -122,25 +122,11 @@
<div class="bg-white dark:bg-gray-800 rounded-xl p-6 max-w-md mx-4 shadow-xl border border-gray-600">
<div class="text-center">
<div class="text-4xl mb-4">💬</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3">
Help us improve!
</h3>
<p class="text-gray-600 dark:text-gray-300 mb-6">
We'd love to hear your thoughts about the spritesheet generator. Would you like to share your feedback?
</p>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3">Help us improve!</h3>
<p class="text-gray-600 dark:text-gray-300 mb-6">We'd love to hear your thoughts about the spritesheet generator. Would you like to share your feedback?</p>
<div class="flex gap-3 justify-center">
<button
@click="handleFeedbackPopupResponse(false)"
class="px-4 py-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
>
Maybe later
</button>
<button
@click="handleFeedbackPopupResponse(true)"
class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors"
>
Share feedback
</button>
<button @click="handleFeedbackPopupResponse(false)" class="px-4 py-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors">Maybe later</button>
<button @click="handleFeedbackPopupResponse(true)" class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors">Share feedback</button>
</div>
</div>
</div>
@@ -624,6 +610,84 @@
sprites.value = newSprites;
};
const removeSprite = (id: string) => {
const spriteIndex = sprites.value.findIndex(sprite => sprite.id === id);
if (spriteIndex !== -1) {
const sprite = sprites.value[spriteIndex];
// Revoke the blob URL to prevent memory leaks
if (sprite.url && sprite.url.startsWith('blob:')) {
try {
URL.revokeObjectURL(sprite.url);
} catch {}
}
// Remove the sprite from the array
sprites.value.splice(spriteIndex, 1);
}
};
const replaceSprite = (id: string, file: File) => {
const spriteIndex = sprites.value.findIndex(sprite => sprite.id === id);
if (spriteIndex !== -1) {
const oldSprite = sprites.value[spriteIndex];
// Revoke the old blob URL to prevent memory leaks
if (oldSprite.url && oldSprite.url.startsWith('blob:')) {
try {
URL.revokeObjectURL(oldSprite.url);
} catch {}
}
// Create new sprite from the replacement file
const url = URL.createObjectURL(file);
const img = new Image();
img.onload = () => {
const newSprite: Sprite = {
id: oldSprite.id, // Keep the same ID
file,
img,
url,
width: img.width,
height: img.height,
x: oldSprite.x, // Keep the same position
y: oldSprite.y,
};
// Create a new array to trigger Vue's reactivity
const newSprites = [...sprites.value];
newSprites[spriteIndex] = newSprite;
sprites.value = newSprites;
};
img.onerror = () => {
console.error('Failed to load replacement image:', file.name);
URL.revokeObjectURL(url);
};
img.src = url;
}
};
const addSprite = (file: File) => {
const url = URL.createObjectURL(file);
const img = new Image();
img.onload = () => {
const newSprite: Sprite = {
id: crypto.randomUUID(),
file,
img,
url,
width: img.width,
height: img.height,
x: 0,
y: 0,
};
sprites.value = [...sprites.value, newSprite];
};
img.onerror = () => {
console.error('Failed to load new sprite image:', file.name);
URL.revokeObjectURL(url);
};
img.src = url;
};
// Download as GIF with specified FPS
const downloadAsGif = (fps: number) => {
if (sprites.value.length === 0) {