Added context menu for easier editing
This commit is contained in:
102
src/App.vue
102
src/App.vue
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user