Compare commits
4 Commits
b33b172644
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
a54acf447b | ||
a87da7ee38 | |||
a76d195579 | |||
6cf29852e3 |
12
index.html
12
index.html
@@ -23,14 +23,14 @@
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://sprite-gen.xvx.sh/">
|
||||
<meta property="og:url" content="https://spritesheetgenerator.online/">
|
||||
<meta property="og:title" content="Spritesheet Generator - Create Game Spritesheets Online">
|
||||
<meta property="og:description" content="Free online tool to create spritesheets for game development. Upload sprites, arrange them, and export as a spritesheet with animation preview.">
|
||||
<meta property="og:image" content="/og-image.png">
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image">
|
||||
<meta property="twitter:url" content="https://sprite-gen.xvx.sh/">
|
||||
<meta property="twitter:url" content="https://spritesheetgenerator.online/">
|
||||
<meta property="twitter:title" content="Spritesheet Generator - Create Game Spritesheets Online">
|
||||
<meta property="twitter:description" content="Free online tool to create spritesheets for game development. Upload sprites, arrange them, and export as a spritesheet with animation preview.">
|
||||
<meta property="twitter:image" content="/og-image.png">
|
||||
@@ -41,7 +41,13 @@
|
||||
|
||||
<title>Spritesheet generator</title>
|
||||
|
||||
<script defer src="https://stats.xvx.sh/script.js" data-website-id="fb1e3dec-3068-4384-b281-68d29f39c7fc"></script>
|
||||
<script
|
||||
src="https://a.adhd.sh/api/script.js"
|
||||
data-site-id="7"
|
||||
data-track-errors="true"
|
||||
data-session-replay="true"
|
||||
defer
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
1292
package-lock.json
generated
1292
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,8 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [1.9.0] - 2025-05-20
|
||||
- Fixed splitter UI in dark mode
|
||||
|
||||
## [1.8.0] - 2025-05-18
|
||||
- Improved app for mobile users
|
||||
- Added PayPal donation link
|
||||
|
34
src/App.vue
34
src/App.vue
@@ -6,9 +6,9 @@
|
||||
<dark-mode-toggle />
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-center gap-4 mb-4 sm:mb-8">
|
||||
<a href="https://git.xvx.sh/root/spritesheet-generator" target="_blank" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors" data-umami-event="source-link"> <i class="fab fa-github"></i> Source </a>
|
||||
<a href="https://discord.gg/JTev3nzeDa" target="_blank" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors" data-umami-event="discord-link"> <i class="fab fa-discord"></i> Discord </a>
|
||||
<a href="#" @click.prevent="openHelpModal" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors" data-umami-event="help-link"> <i class="fas fa-question-circle"></i> Help </a>
|
||||
<a href="https://git.xvx.sh/root/spritesheet-generator" target="_blank" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors" data-rybbit-event="source-link"> <i class="fab fa-github"></i> Source </a>
|
||||
<a href="https://discord.gg/JTev3nzeDa" target="_blank" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors" data-rybbit-event="discord-link"> <i class="fab fa-discord"></i> Discord </a>
|
||||
<a href="#" @click.prevent="openHelpModal" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors" data-rybbit-event="help-link"> <i class="fas fa-question-circle"></i> Help </a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-soft dark:shadow-gray-900/30 p-4 sm:p-8 transition-colors duration-300">
|
||||
@@ -17,7 +17,7 @@
|
||||
<button
|
||||
@click="openJSONImportDialog"
|
||||
class="w-full sm:w-auto px-4 py-2 bg-indigo-500 hover:bg-indigo-600 dark:bg-indigo-600 dark:hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center sm:justify-start space-x-2"
|
||||
data-umami-event="import-json"
|
||||
data-rybbit-event="import-json"
|
||||
>
|
||||
<i class="fas fa-file-import"></i>
|
||||
<span>Import JSON</span>
|
||||
@@ -42,47 +42,51 @@
|
||||
|
||||
<!-- Add mass position buttons -->
|
||||
<div class="flex flex-wrap items-center justify-center gap-2">
|
||||
<button @click="alignSprites('left')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Left" data-umami-event="align-left">
|
||||
<button @click="alignSprites('left')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Left" data-rybbit-event="align-left">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</button>
|
||||
<button @click="alignSprites('center')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Center" data-umami-event="align-center">
|
||||
<button @click="alignSprites('center')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Center" data-rybbit-event="align-center">
|
||||
<i class="fas fa-arrows-left-right"></i>
|
||||
</button>
|
||||
<button @click="alignSprites('right')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Right" data-umami-event="align-right">
|
||||
<button @click="alignSprites('right')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Right" data-rybbit-event="align-right">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</button>
|
||||
<button @click="alignSprites('top')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Top" data-umami-event="align-top">
|
||||
<button @click="alignSprites('top')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Top" data-rybbit-event="align-top">
|
||||
<i class="fas fa-arrow-up"></i>
|
||||
</button>
|
||||
<button @click="alignSprites('middle')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Middle" data-umami-event="align-middle">
|
||||
<button @click="alignSprites('middle')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Middle" data-rybbit-event="align-middle">
|
||||
<i class="fas fa-arrows-up-down"></i>
|
||||
</button>
|
||||
<button @click="alignSprites('bottom')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Bottom" data-umami-event="align-bottom">
|
||||
<button @click="alignSprites('bottom')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Bottom" data-rybbit-event="align-bottom">
|
||||
<i class="fas fa-arrow-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button @click="downloadSpritesheet" class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2" data-umami-event="download-spritesheet">
|
||||
<button @click="downloadSpritesheet" class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2" data-rybbit-event="download-spritesheet">
|
||||
<i class="fas fa-download"></i>
|
||||
<span>Download spritesheet</span>
|
||||
</button>
|
||||
|
||||
<button @click="exportSpritesheetJSON" class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-purple-500 hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2" data-umami-event="export-json">
|
||||
<button
|
||||
@click="exportSpritesheetJSON"
|
||||
class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-purple-500 hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2"
|
||||
data-rybbit-event="export-json"
|
||||
>
|
||||
<i class="fas fa-file-code"></i>
|
||||
<span>Export as JSON</span>
|
||||
</button>
|
||||
|
||||
<button @click="openGifFpsModal" class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-amber-500 hover:bg-amber-600 dark:bg-amber-600 dark:hover:bg-amber-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2" data-umami-event="download-gif">
|
||||
<button @click="openGifFpsModal" class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-amber-500 hover:bg-amber-600 dark:bg-amber-600 dark:hover:bg-amber-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2" data-rybbit-event="download-gif">
|
||||
<i class="fas fa-film"></i>
|
||||
<span>Download as GIF</span>
|
||||
</button>
|
||||
|
||||
<button @click="downloadAsZip" class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-teal-500 hover:bg-teal-600 dark:bg-teal-600 dark:hover:bg-teal-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2" data-umami-event="download-zip">
|
||||
<button @click="downloadAsZip" class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-teal-500 hover:bg-teal-600 dark:bg-teal-600 dark:hover:bg-teal-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2" data-rybbit-event="download-zip">
|
||||
<i class="fas fa-file-archive"></i>
|
||||
<span>Download as ZIP</span>
|
||||
</button>
|
||||
|
||||
<button @click="openPreviewModal" class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-green-500 hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2" data-umami-event="preview-animation">
|
||||
<button @click="openPreviewModal" class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-green-500 hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2" data-rybbit-event="preview-animation">
|
||||
<i class="fas fa-play"></i>
|
||||
<span>Preview animation</span>
|
||||
</button>
|
||||
|
@@ -10,7 +10,7 @@
|
||||
@dragover.prevent
|
||||
@drop.prevent="handleDrop"
|
||||
@click="openFileDialog"
|
||||
data-umami-event="file-upload-area"
|
||||
data-rybbit-event="file-upload-area"
|
||||
>
|
||||
<input ref="fileInput" type="file" multiple accept="image/*,.json" class="hidden" @change="handleFileChange" />
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<p class="text-lg sm:text-xl font-medium text-gray-700 dark:text-gray-200 mb-2">Drag and drop your sprite images or JSON file here</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4 sm:mb-6">or</p>
|
||||
|
||||
<button class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors flex sm:inline-flex items-center justify-center space-x-2 cursor-pointer" data-umami-event="select-files">
|
||||
<button class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors flex sm:inline-flex items-center justify-center space-x-2 cursor-pointer" data-rybbit-event="select-files">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
<span>Select files</span>
|
||||
</button>
|
||||
|
@@ -4,14 +4,14 @@
|
||||
<div class="mb-6">
|
||||
<label for="fps" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Frames Per Second (FPS)</label>
|
||||
<div class="flex items-center">
|
||||
<input id="fps" v-model="fpsValue" type="number" min="1" max="60" class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm text-base" data-umami-event="gif-fps-input" />
|
||||
<input id="fps" v-model="fpsValue" type="number" min="1" max="60" class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm text-base" data-rybbit-event="gif-fps-input" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">Higher FPS will result in smoother animation but larger file size.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse sm:flex-row sm:justify-end space-y-3 space-y-reverse sm:space-y-0 sm:space-x-3">
|
||||
<button @click="cancel" class="px-4 py-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200 font-medium rounded-lg transition-colors" data-umami-event="gif-cancel">Cancel</button>
|
||||
<button @click="confirm" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors" data-umami-event="gif-generate">Generate GIF</button>
|
||||
<button @click="cancel" class="px-4 py-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200 font-medium rounded-lg transition-colors" data-rybbit-event="gif-cancel">Cancel</button>
|
||||
<button @click="confirm" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors" data-rybbit-event="gif-generate">Generate GIF</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
@@ -9,7 +9,7 @@
|
||||
@click="activeTab = index"
|
||||
class="px-3 sm:px-4 py-2 font-medium text-xs sm:text-sm transition-colors"
|
||||
:class="activeTab === index ? 'text-blue-600 dark:text-blue-400 border-b-2 border-blue-500 dark:border-blue-400' : 'text-gray-600 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400'"
|
||||
:data-umami-event="'help-tab-' + tab.name.toLowerCase().replace(/[^a-z0-9]/g, '-')"
|
||||
:data-rybbit-event="'help-tab-' + tab.name.toLowerCase().replace(/[^a-z0-9]/g, '-')"
|
||||
>
|
||||
{{ tab.name }}
|
||||
</button>
|
||||
@@ -43,7 +43,9 @@
|
||||
|
||||
<div class="max-w-none text-gray-700 dark:text-gray-300">
|
||||
<p class="mb-4 text-base leading-relaxed">Spritesheet generator is a free, open-source tool for creating spritesheets for game development and animation projects. This tool allows you to upload individual sprite images and arrange them into a spritesheet with customizable layout.</p>
|
||||
|
||||
<p class="mb-4 text-base leading-relaxed">
|
||||
Matrix: root@adhd.sh
|
||||
</p>
|
||||
<h4 class="mt-6 mb-3 text-lg font-medium text-gray-900 dark:text-gray-100">How to use:</h4>
|
||||
<ol class="list-decimal pl-6 space-y-2 mb-4">
|
||||
<li>Upload your sprite images by dragging and dropping them or clicking the upload area</li>
|
||||
@@ -68,7 +70,7 @@
|
||||
|
||||
<!-- Donations Tab -->
|
||||
<div v-if="activeTab === 2" class="h-full overflow-y-auto">
|
||||
<h3 class="text-lg font-semibold mb-4">Support my <a href="https://xvx.sh" title="XVX" target="_blank" class="text-blue-500 hover:text-blue-600 transition-colors">projects</a>.</h3>
|
||||
<h3 class="text-lg font-semibold mb-4">Support my <a href="https://adhd.sh" title="XVX" target="_blank" class="text-blue-500 hover:text-blue-600 transition-colors">projects</a>.</h3>
|
||||
|
||||
<p class="mb-6">
|
||||
Spritesheet generator is free and open-source software. If you find it useful, please consider supporting the project with a donation. Check out the source code on
|
||||
@@ -82,8 +84,8 @@
|
||||
<h4 class="text-md font-medium">PayPal</h4>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<a href="https://paypal.me/DennisPostma298" target="_blank" class="flex-1 p-2 text-sm bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-l-md focus:outline-none dark:text-gray-200" data-umami-event="open-paypal">https://paypal.me/DennisPostma298</a>
|
||||
<button @click="copyToClipboard('https://paypal.me/DennisPostma298')" class="px-3 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 transition-colors" data-umami-event="copy-paypal-link">Copy</button>
|
||||
<a href="https://paypal.me/DennisPostma298" target="_blank" class="flex-1 p-2 text-sm bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-l-md focus:outline-none dark:text-gray-200" data-rybbit-event="open-paypal">https://paypal.me/DennisPostma298</a>
|
||||
<button @click="copyToClipboard('https://paypal.me/DennisPostma298')" class="px-3 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 transition-colors" data-rybbit-event="copy-paypal-link">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -101,7 +103,7 @@
|
||||
</div>
|
||||
<div class="flex">
|
||||
<input type="text" readonly value="bc1ql2a3nxnhfwft7qex0cclj5ar2lfsslvs0aygeq" class="flex-1 p-2 text-sm bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-l-md focus:outline-none dark:text-gray-200" />
|
||||
<button @click="copyToClipboard('bc1ql2a3nxnhfwft7qex0cclj5ar2lfsslvs0aygeq')" class="px-3 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 transition-colors" data-umami-event="copy-btc-address">Copy</button>
|
||||
<button @click="copyToClipboard('bc1ql2a3nxnhfwft7qex0cclj5ar2lfsslvs0aygeq')" class="px-3 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 transition-colors" data-rybbit-event="copy-btc-address">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -117,7 +119,7 @@
|
||||
</div>
|
||||
<div class="flex">
|
||||
<input type="text" readonly value="ltc1qdkn46hpt39ppmhk25ed2eycu7m2pj5cdzuxw84" class="flex-1 p-2 text-sm bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-l-md focus:outline-none dark:text-gray-200" />
|
||||
<button @click="copyToClipboard('ltc1qdkn46hpt39ppmhk25ed2eycu7m2pj5cdzuxw84')" class="px-3 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 transition-colors" data-umami-event="copy-ltc-address">Copy</button>
|
||||
<button @click="copyToClipboard('ltc1qdkn46hpt39ppmhk25ed2eycu7m2pj5cdzuxw84')" class="px-3 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 transition-colors" data-rybbit-event="copy-ltc-address">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -133,7 +135,7 @@
|
||||
</div>
|
||||
<div class="flex">
|
||||
<input type="text" readonly value="0x30843c72DF6E9A9226d967bf2403602f1C2aB67b" class="flex-1 p-2 text-sm bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-l-md focus:outline-none dark:text-gray-200" />
|
||||
<button @click="copyToClipboard('0x30843c72DF6E9A9226d967bf2403602f1C2aB67b')" class="px-3 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 transition-colors" data-umami-event="copy-eth-address">Copy</button>
|
||||
<button @click="copyToClipboard('0x30843c72DF6E9A9226d967bf2403602f1C2aB67b')" class="px-3 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 transition-colors" data-rybbit-event="copy-eth-address">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@@ -15,26 +15,12 @@
|
||||
<input id="show-all-sprites" type="checkbox" v-model="showAllSprites" class="mr-2" />
|
||||
<label for="show-all-sprites" class="dark:text-gray-200">Compare sprites</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<select v-model="offsetAnchor" class="px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 rounded">
|
||||
<option value="top-left">Top Left</option>
|
||||
<option value="top-right">Top Right</option>
|
||||
<option value="bottom-left">Bottom Left</option>
|
||||
<option value="bottom-right">Bottom Right</option>
|
||||
</select>
|
||||
<label class="dark:text-gray-200">Offset indicator position</label>
|
||||
<button @click="setNewOffsetBase" class="px-3 py-1 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-200 rounded transition-colors">Set Current as Base</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative border border-gray-300 dark:border-gray-600 rounded-lg overflow-auto">
|
||||
<div class="canvas-container touch-manipulation" :style="{ transform: `scale(${zoom})`, transformOrigin: 'top left' }">
|
||||
<canvas ref="canvasRef" @mousedown="startDrag" @mousemove="drag" @mouseup="stopDrag" @mouseleave="stopDrag" @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="stopDrag" class="w-full" :style="settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Mobile zoom controls -->
|
||||
<div class="absolute bottom-3 right-3 flex space-x-2 sm:hidden bg-white/80 dark:bg-gray-800/80 p-2 rounded-lg shadow-md">
|
||||
<!-- Zoom controls - visible on all screen sizes and positioned outside cells -->
|
||||
<div class="relative flex space-x-2 bg-white/90 dark:bg-gray-800/90 p-2 rounded-lg shadow-md z-20">
|
||||
<button @click="zoomIn" class="p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
@@ -46,11 +32,8 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Offset indicators overlay -->
|
||||
<div v-if="canvasRef" class="absolute top-0 left-0 pointer-events-none w-full h-full">
|
||||
<div v-for="pos in spritePositions" :key="pos.id" class="absolute" :style="getOffsetIndicatorStyle(pos)">
|
||||
<div class="text-xs bg-black/75 dark:bg-white/75 text-white dark:text-gray-900 px-1 rounded whitespace-nowrap">x:{{ pos.x }}, y:{{ pos.y }}</div>
|
||||
</div>
|
||||
<div class="canvas-container touch-manipulation" :style="{ transform: `scale(${zoom})`, transformOrigin: 'top left' }">
|
||||
<canvas ref="canvasRef" @mousedown="startDrag" @mousemove="drag" @mouseup="stopDrag" @mouseleave="stopDrag" @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="stopDrag" class="w-full" :style="settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,9 +89,6 @@
|
||||
const ghostSprite = ref<{ id: string; x: number; y: number } | null>(null);
|
||||
const highlightCell = ref<CellPosition | null>(null);
|
||||
|
||||
// Add these refs at the top with other refs
|
||||
const baseOffsets = ref<Record<string, { x: number; y: number }>>({});
|
||||
|
||||
const showAllSprites = ref(false);
|
||||
|
||||
const spritePositions = computed(() => {
|
||||
@@ -120,11 +100,6 @@
|
||||
const col = index % props.columns;
|
||||
const row = Math.floor(index / props.columns);
|
||||
|
||||
// Calculate relative offset from base
|
||||
const baseOffset = baseOffsets.value[sprite.id] || { x: 0, y: 0 };
|
||||
const relativeX = sprite.x - baseOffset.x;
|
||||
const relativeY = sprite.y - baseOffset.y;
|
||||
|
||||
return {
|
||||
id: sprite.id,
|
||||
canvasX: col * maxWidth + sprite.x,
|
||||
@@ -138,9 +113,8 @@
|
||||
col,
|
||||
row,
|
||||
index,
|
||||
// Use relative offsets for display
|
||||
x: relativeX,
|
||||
y: relativeY,
|
||||
x: sprite.x,
|
||||
y: sprite.y,
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -496,61 +470,6 @@
|
||||
const rect = canvasRef.value.getBoundingClientRect();
|
||||
return rect.width / canvasRef.value.width;
|
||||
});
|
||||
|
||||
// Add new ref for offset anchor
|
||||
const offsetAnchor = ref('top-left');
|
||||
|
||||
// Add new method to calculate indicator position
|
||||
const getOffsetIndicatorStyle = (pos: any) => {
|
||||
const baseX = pos.cellX * scale.value;
|
||||
const baseY = pos.cellY * scale.value;
|
||||
const cellWidth = pos.maxWidth * scale.value;
|
||||
const cellHeight = pos.maxHeight * scale.value;
|
||||
|
||||
switch (offsetAnchor.value) {
|
||||
case 'top-right':
|
||||
return {
|
||||
left: `${baseX + cellWidth}px`,
|
||||
top: `${baseY}px`,
|
||||
transform: 'translateX(-100%)',
|
||||
};
|
||||
case 'bottom-left':
|
||||
return {
|
||||
left: `${baseX}px`,
|
||||
top: `${baseY + cellHeight}px`,
|
||||
transform: 'translateY(-100%)',
|
||||
};
|
||||
case 'bottom-right':
|
||||
return {
|
||||
left: `${baseX + cellWidth}px`,
|
||||
top: `${baseY + cellHeight}px`,
|
||||
transform: 'translate(-100%, -100%)',
|
||||
};
|
||||
default: // top-left
|
||||
return {
|
||||
left: `${baseX}px`,
|
||||
top: `${baseY}px`,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const setNewOffsetBase = () => {
|
||||
// Store current positions as new base positions
|
||||
props.sprites.forEach(sprite => {
|
||||
baseOffsets.value[sprite.id] = {
|
||||
x: Math.floor(sprite.x),
|
||||
y: Math.floor(sprite.y),
|
||||
};
|
||||
});
|
||||
|
||||
// Force redraw to update offset indicators
|
||||
drawCanvas();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Add styles for offset indicators */
|
||||
.pointer-events-none {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
@@ -367,7 +367,7 @@
|
||||
const zoomValues = [0.5, 1, 2, 3, 4];
|
||||
const currentIndex = zoomValues.indexOf(Number(zoom.value));
|
||||
if (currentIndex < zoomValues.length - 1) {
|
||||
zoom.value = zoomValues[currentIndex + 1].toString();
|
||||
zoom.value = zoomValues[currentIndex + 1];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -375,7 +375,7 @@
|
||||
const zoomValues = [0.5, 1, 2, 3, 4];
|
||||
const currentIndex = zoomValues.indexOf(Number(zoom.value));
|
||||
if (currentIndex > 0) {
|
||||
zoom.value = zoomValues[currentIndex - 1].toString();
|
||||
zoom.value = zoomValues[currentIndex - 1];
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -3,49 +3,68 @@
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col space-y-4">
|
||||
<div class="flex items-center justify-center mb-4">
|
||||
<img :src="imageUrl" alt="Spritesheet" class="max-w-full max-h-48 sm:max-h-64 border border-gray-300 rounded-lg" :style="settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}" />
|
||||
<img :src="imageUrl" alt="Spritesheet" class="max-w-full max-h-48 sm:max-h-64 border border-gray-300 dark:border-gray-600 rounded-lg" :style="settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<label for="detection-method" class="block text-sm font-medium text-gray-700">Detection Method</label>
|
||||
<select id="detection-method" v-model="detectionMethod" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" data-umami-event="spritesheet-detection-method">
|
||||
<label for="detection-method" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Detection Method</label>
|
||||
<select
|
||||
id="detection-method"
|
||||
v-model="detectionMethod"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-400 dark:focus:border-blue-400"
|
||||
data-rybbit-event="spritesheet-detection-method"
|
||||
>
|
||||
<option value="manual">Manual (specify rows and columns)</option>
|
||||
<option value="auto">Auto-detect (experimental)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div v-if="detectionMethod === 'auto'" class="space-y-2">
|
||||
<label for="sensitivity" class="block text-sm font-medium text-gray-700">Detection Sensitivity</label>
|
||||
<input type="range" id="sensitivity" v-model="sensitivity" min="1" max="100" class="w-full" data-umami-event="spritesheet-sensitivity" />
|
||||
<div class="text-xs text-gray-500 flex justify-between">
|
||||
<label for="sensitivity" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Detection Sensitivity</label>
|
||||
<input type="range" id="sensitivity" v-model="sensitivity" min="1" max="100" class="w-full dark:accent-blue-400" data-rybbit-event="spritesheet-sensitivity" />
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 flex justify-between">
|
||||
<span>Low</span>
|
||||
<span>High</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="detectionMethod === 'manual'" class="space-y-2">
|
||||
<label for="rows" class="block text-sm font-medium text-gray-700">Rows</label>
|
||||
<input type="number" id="rows" v-model.number="rows" min="1" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" data-umami-event="spritesheet-rows" />
|
||||
<label for="rows" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Rows</label>
|
||||
<input
|
||||
type="number"
|
||||
id="rows"
|
||||
v-model.number="rows"
|
||||
min="1"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-400 dark:focus:border-blue-400"
|
||||
data-rybbit-event="spritesheet-rows"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="detectionMethod === 'manual'" class="space-y-2">
|
||||
<label for="columns" class="block text-sm font-medium text-gray-700">Columns</label>
|
||||
<input type="number" id="columns" v-model.number="columns" min="1" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" data-umami-event="spritesheet-columns" />
|
||||
<label for="columns" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Columns</label>
|
||||
<input
|
||||
type="number"
|
||||
id="columns"
|
||||
v-model.number="columns"
|
||||
min="1"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-400 dark:focus:border-blue-400"
|
||||
data-rybbit-event="spritesheet-columns"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" id="remove-empty" v-model="removeEmpty" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" data-umami-event="spritesheet-remove-empty" />
|
||||
<label for="remove-empty" class="ml-2 block text-sm text-gray-700"> Remove empty sprites (transparent/background color) </label>
|
||||
<input type="checkbox" id="remove-empty" v-model="removeEmpty" class="h-4 w-4 text-blue-600 dark:text-blue-400 focus:ring-blue-500 dark:focus:ring-blue-400 border-gray-300 dark:border-gray-600 rounded" data-rybbit-event="spritesheet-remove-empty" />
|
||||
<label for="remove-empty" class="ml-2 block text-sm text-gray-700 dark:text-gray-300"> Remove empty sprites (transparent/background color) </label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="previewSprites.length > 0" class="space-y-2">
|
||||
<h3 class="text-sm font-medium text-gray-700">Preview ({{ previewSprites.length }} sprites)</h3>
|
||||
<div class="grid grid-cols-3 sm:grid-cols-6 md:grid-cols-8 gap-2 max-h-40 overflow-y-auto p-2 border border-gray-200 rounded-lg">
|
||||
<div v-for="(sprite, index) in previewSprites" :key="index" class="relative border border-gray-300 rounded bg-gray-100 flex items-center justify-center" :style="{ width: '80px', height: '80px' }">
|
||||
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300">Preview ({{ previewSprites.length }} sprites)</h3>
|
||||
<div class="grid grid-cols-3 sm:grid-cols-6 md:grid-cols-8 gap-2 max-h-40 overflow-y-auto p-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800">
|
||||
<div v-for="(sprite, index) in previewSprites" :key="index" class="relative border border-gray-300 dark:border-gray-600 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center" :style="{ width: '80px', height: '80px' }">
|
||||
<img :src="sprite.url" alt="Sprite preview" class="max-w-full max-h-full" :style="settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -53,12 +72,18 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse sm:flex-row sm:justify-end space-y-3 space-y-reverse sm:space-y-0 sm:space-x-3">
|
||||
<button @click="cancel" class="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" data-umami-event="spritesheet-cancel">Cancel</button>
|
||||
<button
|
||||
@click="cancel"
|
||||
class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-800"
|
||||
data-rybbit-event="spritesheet-cancel"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@click="confirm"
|
||||
class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-800"
|
||||
:disabled="previewSprites.length === 0 || isProcessing"
|
||||
data-umami-event="spritesheet-split"
|
||||
data-rybbit-event="spritesheet-split"
|
||||
>
|
||||
Split Spritesheet
|
||||
</button>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button @click="settingsStore.toggleDarkMode()" class="p-2 rounded-lg transition-colors" :class="settingsStore.darkMode ? 'text-yellow-400 hover:bg-gray-700' : 'text-gray-700 hover:bg-gray-100'" aria-label="Toggle dark mode" data-umami-event="toggle-dark-mode">
|
||||
<button @click="settingsStore.toggleDarkMode()" class="p-2 rounded-lg transition-colors" :class="settingsStore.darkMode ? 'text-yellow-400 hover:bg-gray-700' : 'text-gray-700 hover:bg-gray-100'" aria-label="Toggle dark mode" data-rybbit-event="toggle-dark-mode">
|
||||
<i :class="settingsStore.darkMode ? 'fas fa-sun' : 'fas fa-moon'"></i>
|
||||
</button>
|
||||
</template>
|
||||
|
@@ -17,10 +17,10 @@
|
||||
<div class="flex justify-between items-center p-4 border-b border-gray-200 dark:border-gray-700" :class="{ 'cursor-move': !isFullScreen && !isMobile }">
|
||||
<h3 class="text-xl sm:text-2xl font-semibold text-gray-900 dark:text-gray-100 truncate pr-2" @mousedown="startDrag" @touchstart="handleTouchStart">{{ title }}</h3>
|
||||
<div class="flex items-center space-x-2">
|
||||
<button @click="toggleFullScreen" class="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors" data-umami-event="modal-fullscreen">
|
||||
<button @click="toggleFullScreen" class="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors" data-rybbit-event="modal-fullscreen">
|
||||
<img src="@/assets/images/fullscreen-icon.svg" class="w-4 h-4 dark:invert" alt="Fullscreen" :class="{ 'rotate-180': isFullScreen }" />
|
||||
</button>
|
||||
<button @click="close" class="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors" data-umami-event="modal-close">
|
||||
<button @click="close" class="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors" data-rybbit-event="modal-close">
|
||||
<img src="@/assets/images/close-icon.svg" class="w-5 h-5 dark:invert" alt="Close" />
|
||||
</button>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user