Updated mobile UX
This commit is contained in:
1088
package-lock.json
generated
1088
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [1.8.0] - 2025-05-18
|
||||||
|
- Improved app for mobile users
|
||||||
|
- Added PayPal donation link
|
||||||
|
- Fixed modals not closing on mobile devices
|
||||||
|
|
||||||
## [1.7.0] - 2025-05-02
|
## [1.7.0] - 2025-05-02
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
46
src/App.vue
46
src/App.vue
@@ -1,20 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen p-6 bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 transition-colors duration-300">
|
<div class="min-h-screen p-3 sm:p-6 bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 transition-colors duration-300">
|
||||||
<div class="max-w-6xl mx-auto">
|
<div class="max-w-6xl mx-auto">
|
||||||
<div class="flex justify-between items-center mb-8">
|
<div class="flex flex-col sm:flex-row justify-between items-center mb-4 sm:mb-8 gap-2">
|
||||||
<h1 class="text-4xl font-bold text-gray-900 dark:text-white tracking-tight">Spritesheet generator</h1>
|
<h1 class="text-2xl sm:text-4xl font-bold text-gray-900 dark:text-white tracking-tight text-center sm:text-left">Spritesheet generator</h1>
|
||||||
<dark-mode-toggle />
|
<dark-mode-toggle />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center space-x-4 mb-8">
|
<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://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="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="#" @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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-soft dark:shadow-gray-900/30 p-8 transition-colors duration-300">
|
<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">
|
||||||
<div class="flex justify-between items-center mb-6">
|
<div class="flex flex-col sm:flex-row justify-between items-center mb-4 sm:mb-6 gap-3">
|
||||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-100">Upload sprites</h2>
|
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-100">Upload sprites</h2>
|
||||||
<button @click="openJSONImportDialog" class="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 space-x-2" data-umami-event="import-json">
|
<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"
|
||||||
|
>
|
||||||
<i class="fas fa-file-import"></i>
|
<i class="fas fa-file-import"></i>
|
||||||
<span>Import JSON</span>
|
<span>Import JSON</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -23,7 +27,7 @@
|
|||||||
<input ref="jsonFileInput" type="file" accept=".json,application/json" class="hidden" @change="handleJSONFileChange" />
|
<input ref="jsonFileInput" type="file" accept=".json,application/json" class="hidden" @change="handleJSONFileChange" />
|
||||||
|
|
||||||
<div v-if="sprites.length > 0" class="mt-8">
|
<div v-if="sprites.length > 0" class="mt-8">
|
||||||
<div class="flex flex-wrap items-center gap-6 mb-8">
|
<div class="flex flex-wrap items-center justify-center sm:justify-start gap-3 sm:gap-6 mb-6 sm:mb-8">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<label for="columns" class="text-gray-700 dark:text-gray-200 font-medium">Columns:</label>
|
<label for="columns" class="text-gray-700 dark:text-gray-200 font-medium">Columns:</label>
|
||||||
<input
|
<input
|
||||||
@@ -32,53 +36,53 @@
|
|||||||
v-model="columns"
|
v-model="columns"
|
||||||
min="1"
|
min="1"
|
||||||
max="10"
|
max="10"
|
||||||
class="w-20 px-3 py-2 border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent outline-none transition-all"
|
class="w-20 px-3 py-2 border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent outline-none transition-all text-base"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add mass position buttons -->
|
<!-- Add mass position buttons -->
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex flex-wrap items-center justify-center gap-2">
|
||||||
<button @click="alignSprites('left')" 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" 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-umami-event="align-left">
|
||||||
<i class="fas fa-arrow-left"></i>
|
<i class="fas fa-arrow-left"></i>
|
||||||
</button>
|
</button>
|
||||||
<button @click="alignSprites('center')" 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" 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-umami-event="align-center">
|
||||||
<i class="fas fa-arrows-left-right"></i>
|
<i class="fas fa-arrows-left-right"></i>
|
||||||
</button>
|
</button>
|
||||||
<button @click="alignSprites('right')" 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" 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-umami-event="align-right">
|
||||||
<i class="fas fa-arrow-right"></i>
|
<i class="fas fa-arrow-right"></i>
|
||||||
</button>
|
</button>
|
||||||
<button @click="alignSprites('top')" 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" 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-umami-event="align-top">
|
||||||
<i class="fas fa-arrow-up"></i>
|
<i class="fas fa-arrow-up"></i>
|
||||||
</button>
|
</button>
|
||||||
<button @click="alignSprites('middle')" 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" 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-umami-event="align-middle">
|
||||||
<i class="fas fa-arrows-up-down"></i>
|
<i class="fas fa-arrows-up-down"></i>
|
||||||
</button>
|
</button>
|
||||||
<button @click="alignSprites('bottom')" 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" 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-umami-event="align-bottom">
|
||||||
<i class="fas fa-arrow-down"></i>
|
<i class="fas fa-arrow-down"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button @click="downloadSpritesheet" class="px-6 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 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-umami-event="download-spritesheet">
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
<span>Download spritesheet</span>
|
<span>Download spritesheet</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button @click="exportSpritesheetJSON" class="px-6 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 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-umami-event="export-json">
|
||||||
<i class="fas fa-file-code"></i>
|
<i class="fas fa-file-code"></i>
|
||||||
<span>Export as JSON</span>
|
<span>Export as JSON</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button @click="openGifFpsModal" class="px-6 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 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-umami-event="download-gif">
|
||||||
<i class="fas fa-film"></i>
|
<i class="fas fa-film"></i>
|
||||||
<span>Download as GIF</span>
|
<span>Download as GIF</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button @click="downloadAsZip" class="px-6 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 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-umami-event="download-zip">
|
||||||
<i class="fas fa-file-archive"></i>
|
<i class="fas fa-file-archive"></i>
|
||||||
<span>Download as ZIP</span>
|
<span>Download as ZIP</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button @click="openPreviewModal" class="px-6 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 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-umami-event="preview-animation">
|
||||||
<i class="fas fa-play"></i>
|
<i class="fas fa-play"></i>
|
||||||
<span>Preview animation</span>
|
<span>Preview animation</span>
|
||||||
</button>
|
</button>
|
||||||
|
@@ -7,6 +7,7 @@ body {
|
|||||||
transition:
|
transition:
|
||||||
background-color 0.3s ease,
|
background-color 0.3s ease,
|
||||||
color 0.3s ease;
|
color 0.3s ease;
|
||||||
|
-webkit-tap-highlight-color: transparent; /* Remove tap highlight on mobile */
|
||||||
}
|
}
|
||||||
|
|
||||||
html.theme-transition * {
|
html.theme-transition * {
|
||||||
@@ -23,3 +24,16 @@ body.dark-mode {
|
|||||||
html.dark {
|
html.dark {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile touch improvements */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
button {
|
||||||
|
font-size: 16px !important; /* Prevent iOS zoom on focus */
|
||||||
|
}
|
||||||
|
|
||||||
|
.touch-manipulation {
|
||||||
|
touch-action: manipulation; /* Improve touch responsiveness */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="border-2 border-dashed rounded-xl p-8 text-center transition-all duration-200"
|
class="border-2 border-dashed rounded-xl p-4 sm:p-8 text-center transition-all duration-200"
|
||||||
:class="{
|
:class="{
|
||||||
'border-blue-300 bg-blue-50 dark:border-blue-500 dark:bg-blue-900/30': isDragging,
|
'border-blue-300 bg-blue-50 dark:border-blue-500 dark:bg-blue-900/30': isDragging,
|
||||||
'border-gray-200 hover:border-blue-300 hover:bg-gray-50 dark:border-gray-600 dark:hover:border-blue-500 dark:hover:bg-gray-700/50': !isDragging,
|
'border-gray-200 hover:border-blue-300 hover:bg-gray-50 dark:border-gray-600 dark:hover:border-blue-500 dark:hover:bg-gray-700/50': !isDragging,
|
||||||
@@ -14,14 +14,14 @@
|
|||||||
>
|
>
|
||||||
<input ref="fileInput" type="file" multiple accept="image/*,.json" class="hidden" @change="handleFileChange" />
|
<input ref="fileInput" type="file" multiple accept="image/*,.json" class="hidden" @change="handleFileChange" />
|
||||||
|
|
||||||
<div class="mb-6">
|
<div class="mb-4 sm:mb-6">
|
||||||
<img src="@/assets/images/file.svg" alt="File upload" class="w-20 h-20 mx-auto mb-4 opacity-75 dark:invert" />
|
<img src="@/assets/images/file.svg" alt="File upload" class="w-16 h-16 sm:w-20 sm:h-20 mx-auto mb-2 sm:mb-4 opacity-75 dark:invert" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="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-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-6">or</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4 sm:mb-6">or</p>
|
||||||
|
|
||||||
<button class="px-6 py-2.5 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors inline-flex items-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-umami-event="select-files">
|
||||||
<i class="fas fa-folder-open"></i>
|
<i class="fas fa-folder-open"></i>
|
||||||
<span>Select files</span>
|
<span>Select files</span>
|
||||||
</button>
|
</button>
|
||||||
|
@@ -4,12 +4,12 @@
|
|||||||
<div class="mb-6">
|
<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>
|
<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">
|
<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" 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-umami-event="gif-fps-input" />
|
||||||
</div>
|
</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>
|
<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>
|
||||||
|
|
||||||
<div class="flex justify-end space-x-3">
|
<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="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="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>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,12 +2,12 @@
|
|||||||
<Modal :is-open="isOpen" @close="close" title="Help & information" :initialWidth="800" :initialHeight="600">
|
<Modal :is-open="isOpen" @close="close" title="Help & information" :initialWidth="800" :initialHeight="600">
|
||||||
<div class="flex flex-col h-full">
|
<div class="flex flex-col h-full">
|
||||||
<!-- Tabs -->
|
<!-- Tabs -->
|
||||||
<div class="flex border-b border-gray-200 dark:border-gray-700">
|
<div class="flex flex-wrap border-b border-gray-200 dark:border-gray-700">
|
||||||
<button
|
<button
|
||||||
v-for="(tab, index) in tabs"
|
v-for="(tab, index) in tabs"
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="activeTab = index"
|
@click="activeTab = index"
|
||||||
class="px-4 py-2 font-medium text-sm transition-colors"
|
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'"
|
: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-umami-event="'help-tab-' + tab.name.toLowerCase().replace(/[^a-z0-9]/g, '-')"
|
||||||
>
|
>
|
||||||
@@ -20,17 +20,17 @@
|
|||||||
<!-- Video Instructions Tab -->
|
<!-- Video Instructions Tab -->
|
||||||
<div v-if="activeTab === 0" class="h-full flex flex-col space-y-6">
|
<div v-if="activeTab === 0" class="h-full flex flex-col space-y-6">
|
||||||
<h3 class="text-lg font-semibold mb-4">Video tutorial</h3>
|
<h3 class="text-lg font-semibold mb-4">Video tutorial</h3>
|
||||||
<div class="flex-1 bg-gray-100 rounded-lg flex items-center justify-center">
|
<div class="flex-1 bg-gray-100 dark:bg-gray-700 rounded-lg flex items-center justify-center">
|
||||||
<div class="w-full aspect-video max-w-3xl mx-auto">
|
<div class="w-full aspect-video max-w-3xl mx-auto">
|
||||||
<video controls class="w-full h-full object-contain rounded-lg shadow-md">
|
<video controls playsinline class="w-full h-full object-contain rounded-lg shadow-md">
|
||||||
<source src="@/assets/tut.mp4" type="video/mp4" />
|
<source src="@/assets/tut.mp4" type="video/mp4" />
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold mb-4">Split spritesheet</h3>
|
<h3 class="text-lg font-semibold mb-4">Split spritesheet</h3>
|
||||||
<div class="flex-1 bg-gray-100 rounded-lg flex items-center justify-center">
|
<div class="flex-1 bg-gray-100 dark:bg-gray-700 rounded-lg flex items-center justify-center">
|
||||||
<div class="w-full aspect-video max-w-3xl mx-auto">
|
<div class="w-full aspect-video max-w-3xl mx-auto">
|
||||||
<video controls class="w-full h-full object-contain rounded-lg shadow-md">
|
<video controls playsinline class="w-full h-full object-contain rounded-lg shadow-md">
|
||||||
<source src="@/assets/tut-split.mp4" type="video/mp4" />
|
<source src="@/assets/tut-split.mp4" type="video/mp4" />
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,6 +76,17 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
|
<!-- PayPal -->
|
||||||
|
<div class="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600">
|
||||||
|
<div class="flex items-center mb-3">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Bitcoin -->
|
<!-- Bitcoin -->
|
||||||
<div class="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600">
|
<div class="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600">
|
||||||
<div class="flex items-center mb-3">
|
<div class="flex items-center mb-3">
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 sm:gap-0">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex flex-col sm:flex-row items-start sm:items-center gap-3 sm:gap-4 w-full sm:w-auto">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<input id="pixel-perfect" type="checkbox" v-model="settingsStore.pixelPerfect" class="mr-2" @change="drawCanvas" />
|
<input id="pixel-perfect" type="checkbox" v-model="settingsStore.pixelPerfect" class="mr-2 w-4 h-4" @change="drawCanvas" />
|
||||||
<label for="pixel-perfect" class="dark:text-gray-200">Pixel perfect rendering (for pixel art)</label>
|
<label for="pixel-perfect" class="dark:text-gray-200 text-sm sm:text-base">Pixel perfect rendering</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<input id="allow-cell-swap" type="checkbox" v-model="allowCellSwap" class="mr-2" />
|
<input id="allow-cell-swap" type="checkbox" v-model="allowCellSwap" class="mr-2 w-4 h-4" />
|
||||||
<label for="allow-cell-swap" class="dark:text-gray-200">Allow moving between cells</label>
|
<label for="allow-cell-swap" class="dark:text-gray-200 text-sm sm:text-base">Allow moving between cells</label>
|
||||||
</div>
|
</div>
|
||||||
<!-- Add new checkbox for showing all sprites -->
|
<!-- Add new checkbox for showing all sprites -->
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -28,8 +28,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative border border-gray-300 dark:border-gray-600 rounded-lg">
|
<div class="relative border border-gray-300 dark:border-gray-600 rounded-lg overflow-auto">
|
||||||
<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 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">
|
||||||
|
<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>
|
||||||
|
<button @click="zoomOut" 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-minus"></i>
|
||||||
|
</button>
|
||||||
|
<button @click="resetZoom" 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-expand"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Offset indicators overlay -->
|
<!-- Offset indicators overlay -->
|
||||||
<div v-if="canvasRef" class="absolute top-0 left-0 pointer-events-none w-full h-full">
|
<div v-if="canvasRef" class="absolute top-0 left-0 pointer-events-none w-full h-full">
|
||||||
@@ -276,10 +291,33 @@
|
|||||||
drawCanvas();
|
drawCanvas();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add zoom functionality for mobile
|
||||||
|
const zoom = ref(1);
|
||||||
|
const minZoom = 0.5;
|
||||||
|
const maxZoom = 3;
|
||||||
|
const zoomStep = 0.25;
|
||||||
|
|
||||||
|
const zoomIn = () => {
|
||||||
|
zoom.value = Math.min(maxZoom, zoom.value + zoomStep);
|
||||||
|
};
|
||||||
|
|
||||||
|
const zoomOut = () => {
|
||||||
|
zoom.value = Math.max(minZoom, zoom.value - zoomStep);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetZoom = () => {
|
||||||
|
zoom.value = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Improved touch handling
|
||||||
const handleTouchStart = (event: TouchEvent) => {
|
const handleTouchStart = (event: TouchEvent) => {
|
||||||
event.preventDefault();
|
// Don't prevent default to allow scrolling
|
||||||
if (event.touches.length === 1) {
|
if (event.touches.length === 1) {
|
||||||
const touch = event.touches[0];
|
const touch = event.touches[0];
|
||||||
|
const rect = canvasRef.value?.getBoundingClientRect();
|
||||||
|
if (!rect) return;
|
||||||
|
|
||||||
|
// Adjust for zoom
|
||||||
const mouseEvent = {
|
const mouseEvent = {
|
||||||
clientX: touch.clientX,
|
clientX: touch.clientX,
|
||||||
clientY: touch.clientY,
|
clientY: touch.clientY,
|
||||||
@@ -290,7 +328,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleTouchMove = (event: TouchEvent) => {
|
const handleTouchMove = (event: TouchEvent) => {
|
||||||
event.preventDefault();
|
// Only prevent default when we're actually dragging
|
||||||
|
if (isDragging.value) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
if (event.touches.length === 1) {
|
if (event.touches.length === 1) {
|
||||||
const touch = event.touches[0];
|
const touch = event.touches[0];
|
||||||
const mouseEvent = {
|
const mouseEvent = {
|
||||||
|
@@ -99,7 +99,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3 relative bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg mb-6 overflow-auto min-h-[520px] shadow-sm hover:shadow-md transition-shadow duration-200">
|
<div class="mt-3 relative bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg mb-4 sm:mb-6 overflow-auto min-h-[300px] sm:min-h-[520px] shadow-sm hover:shadow-md transition-shadow duration-200">
|
||||||
<canvas
|
<canvas
|
||||||
ref="previewCanvasRef"
|
ref="previewCanvasRef"
|
||||||
@mousedown="startDrag"
|
@mousedown="startDrag"
|
||||||
@@ -109,11 +109,21 @@
|
|||||||
@touchstart="handleTouchStart"
|
@touchstart="handleTouchStart"
|
||||||
@touchmove="handleTouchMove"
|
@touchmove="handleTouchMove"
|
||||||
@touchend="stopDrag"
|
@touchend="stopDrag"
|
||||||
class="block"
|
class="block touch-manipulation"
|
||||||
:class="{ 'cursor-move': isDraggable }"
|
:class="{ 'cursor-move': isDraggable }"
|
||||||
:style="{ transform: `scale(${zoom})`, transformOrigin: 'top left', ...(settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}) }"
|
:style="{ transform: `scale(${zoom})`, transformOrigin: 'top left', ...(settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}) }"
|
||||||
>
|
>
|
||||||
</canvas>
|
</canvas>
|
||||||
|
|
||||||
|
<!-- 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">
|
||||||
|
<button @click="increaseZoom" 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>
|
||||||
|
<button @click="decreaseZoom" 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-minus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -352,7 +362,26 @@
|
|||||||
activeSpriteId.value = null;
|
activeSpriteId.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add helper methods for mobile zoom controls
|
||||||
|
const increaseZoom = () => {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const decreaseZoom = () => {
|
||||||
|
const zoomValues = [0.5, 1, 2, 3, 4];
|
||||||
|
const currentIndex = zoomValues.indexOf(Number(zoom.value));
|
||||||
|
if (currentIndex > 0) {
|
||||||
|
zoom.value = zoomValues[currentIndex - 1].toString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleTouchStart = (event: TouchEvent) => {
|
const handleTouchStart = (event: TouchEvent) => {
|
||||||
|
if (!isDraggable.value) return;
|
||||||
|
|
||||||
if (event.touches.length === 1) {
|
if (event.touches.length === 1) {
|
||||||
const touch = event.touches[0];
|
const touch = event.touches[0];
|
||||||
const mouseEvent = new MouseEvent('mousedown', {
|
const mouseEvent = new MouseEvent('mousedown', {
|
||||||
@@ -364,6 +393,12 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleTouchMove = (event: TouchEvent) => {
|
const handleTouchMove = (event: TouchEvent) => {
|
||||||
|
if (!isDraggable.value) return;
|
||||||
|
|
||||||
|
if (isDragging.value) {
|
||||||
|
event.preventDefault(); // Only prevent default when actually dragging
|
||||||
|
}
|
||||||
|
|
||||||
if (event.touches.length === 1) {
|
if (event.touches.length === 1) {
|
||||||
const touch = event.touches[0];
|
const touch = event.touches[0];
|
||||||
const mouseEvent = new MouseEvent('mousemove', {
|
const mouseEvent = new MouseEvent('mousemove', {
|
||||||
|
@@ -3,10 +3,10 @@
|
|||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="flex flex-col space-y-4">
|
<div class="flex flex-col space-y-4">
|
||||||
<div class="flex items-center justify-center mb-4">
|
<div class="flex items-center justify-center mb-4">
|
||||||
<img :src="imageUrl" alt="Spritesheet" class="max-w-full 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 rounded-lg" :style="settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label for="detection-method" class="block text-sm font-medium text-gray-700">Detection Method</label>
|
<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">
|
<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">
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
<div v-if="previewSprites.length > 0" class="space-y-2">
|
<div v-if="previewSprites.length > 0" class="space-y-2">
|
||||||
<h3 class="text-sm font-medium text-gray-700">Preview ({{ previewSprites.length }} sprites)</h3>
|
<h3 class="text-sm font-medium text-gray-700">Preview ({{ previewSprites.length }} sprites)</h3>
|
||||||
<div class="grid grid-cols-4 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 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' }">
|
<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' }">
|
||||||
<img :src="sprite.url" alt="Sprite preview" class="max-w-full max-h-full" :style="settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}" />
|
<img :src="sprite.url" alt="Sprite preview" class="max-w-full max-h-full" :style="settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}" />
|
||||||
</div>
|
</div>
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end space-x-3">
|
<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 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
|
<button
|
||||||
@click="confirm"
|
@click="confirm"
|
||||||
|
@@ -14,8 +14,8 @@
|
|||||||
:class="{ 'rounded-none border-0': isFullScreen }"
|
:class="{ 'rounded-none border-0': isFullScreen }"
|
||||||
>
|
>
|
||||||
<!-- Header with drag handle -->
|
<!-- Header with drag handle -->
|
||||||
<div class="flex justify-between items-center p-4 border-b border-gray-200 dark:border-gray-700" :class="{ 'cursor-move': !isFullScreen }" @mousedown="startDrag" @touchstart.prevent="handleTouchStart">
|
<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-2xl font-semibold text-gray-900 dark:text-gray-100">{{ title }}</h3>
|
<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">
|
<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-umami-event="modal-fullscreen">
|
||||||
<img src="@/assets/images/fullscreen-icon.svg" class="w-4 h-4 dark:invert" alt="Fullscreen" :class="{ 'rotate-180': isFullScreen }" />
|
<img src="@/assets/images/fullscreen-icon.svg" class="w-4 h-4 dark:invert" alt="Fullscreen" :class="{ 'rotate-180': isFullScreen }" />
|
||||||
@@ -27,12 +27,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Body -->
|
<!-- Body -->
|
||||||
<div class="p-6 flex-1 overflow-auto">
|
<div class="p-4 sm:p-6 flex-1 overflow-auto">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Resize handle -->
|
<!-- Resize handle -->
|
||||||
<div v-if="!isFullScreen" class="absolute bottom-0 right-0 w-8 h-8 cursor-se-resize" @mousedown="startResize" @touchstart="startResize"></div>
|
<div v-if="!isFullScreen && !isMobile" class="absolute bottom-0 right-0 w-8 h-8 cursor-se-resize" @mousedown="startResize" @touchstart="startResize"></div>
|
||||||
</div>
|
</div>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
@@ -65,6 +65,7 @@
|
|||||||
|
|
||||||
// Add isFullScreen ref
|
// Add isFullScreen ref
|
||||||
const isFullScreen = ref(false);
|
const isFullScreen = ref(false);
|
||||||
|
const isMobile = ref(false);
|
||||||
|
|
||||||
// Add previous state storage for restoring from full screen
|
// Add previous state storage for restoring from full screen
|
||||||
const previousState = ref({
|
const previousState = ref({
|
||||||
@@ -72,6 +73,23 @@
|
|||||||
size: { width: 0, height: 0 },
|
size: { width: 0, height: 0 },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if device is mobile
|
||||||
|
const checkMobile = () => {
|
||||||
|
isMobile.value = window.innerWidth < 640; // sm breakpoint in Tailwind
|
||||||
|
|
||||||
|
// Auto fullscreen on mobile
|
||||||
|
if (isMobile.value && !isFullScreen.value) {
|
||||||
|
toggleFullScreen();
|
||||||
|
} else if (!isMobile.value && isFullScreen.value && autoFullScreened.value) {
|
||||||
|
// If we're no longer on mobile and were auto-fullscreened, exit fullscreen
|
||||||
|
toggleFullScreen();
|
||||||
|
autoFullScreened.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Track if fullscreen was automatic (for mobile)
|
||||||
|
const autoFullScreened = ref(false);
|
||||||
|
|
||||||
// Add toggleFullScreen function
|
// Add toggleFullScreen function
|
||||||
const toggleFullScreen = () => {
|
const toggleFullScreen = () => {
|
||||||
if (!isFullScreen.value) {
|
if (!isFullScreen.value) {
|
||||||
@@ -80,6 +98,11 @@
|
|||||||
position: { ...position.value },
|
position: { ...position.value },
|
||||||
size: { ...size.value },
|
size: { ...size.value },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If toggling to fullscreen on mobile automatically, track it
|
||||||
|
if (isMobile.value) {
|
||||||
|
autoFullScreened.value = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Restore previous state
|
// Restore previous state
|
||||||
position.value = { ...previousState.value.position };
|
position.value = { ...previousState.value.position };
|
||||||
@@ -138,7 +161,7 @@
|
|||||||
isDragging.value = false;
|
isDragging.value = false;
|
||||||
isResizing.value = false;
|
isResizing.value = false;
|
||||||
document.removeEventListener('mousemove', handleMove);
|
document.removeEventListener('mousemove', handleMove);
|
||||||
document.removeEventListener('touchmove', handleMove);
|
document.removeEventListener('touchmove', handleTouchMove); // Fix: was handleMove
|
||||||
document.removeEventListener('mouseup', stopAction);
|
document.removeEventListener('mouseup', stopAction);
|
||||||
document.removeEventListener('touchend', stopAction);
|
document.removeEventListener('touchend', stopAction);
|
||||||
};
|
};
|
||||||
@@ -194,11 +217,8 @@
|
|||||||
|
|
||||||
const handleTouchMove = (event: TouchEvent) => {
|
const handleTouchMove = (event: TouchEvent) => {
|
||||||
if (!isDragging.value && !isResizing.value) return;
|
if (!isDragging.value && !isResizing.value) return;
|
||||||
event.preventDefault();
|
event.preventDefault(); // Prevent scrolling while dragging
|
||||||
|
handleMove(event);
|
||||||
if (event.touches.length === 1) {
|
|
||||||
handleMove(event);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
@@ -212,12 +232,18 @@
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('keydown', handleKeyDown);
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
|
window.addEventListener('resize', checkMobile);
|
||||||
|
|
||||||
|
// Initial check for mobile
|
||||||
|
checkMobile();
|
||||||
|
|
||||||
if (props.isOpen) centerModal();
|
if (props.isOpen) centerModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('keydown', handleKeyDown);
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
|
window.removeEventListener('resize', checkMobile);
|
||||||
stopAction();
|
stopAction();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
Reference in New Issue
Block a user