Tooltips, donation, new help window

This commit is contained in:
Dennis Postma 2025-04-03 04:38:23 +02:00
parent 8fdb0dbae3
commit 376bf00ad4
7 changed files with 279 additions and 32 deletions

View File

@ -38,6 +38,7 @@
<preview-modal ref="previewModalRef" />
<settings-modal />
<sprites-modal />
<help-modal />
<notification />
<help-button @show-help="showHelpModal" />
</div>
@ -51,6 +52,7 @@
import PreviewModal from './components/PreviewModal.vue';
import SettingsModal from './components/SettingsModal.vue';
import SpritesModal from './components/SpritesModal.vue';
import HelpModal from './components/HelpModal.vue';
import Navigation from './components/Navigation.vue';
import Notification from './components/Notification.vue';
import HelpButton from './components/HelpButton.vue';
@ -71,6 +73,7 @@
);
const showHelpModal = () => {
alert('Keyboard shortcuts:\n\n' + 'Shift + Drag: Fine-tune sprite position\n' + 'Space: Play/Pause animation\n' + 'Esc: Close preview modal\n' + 'Arrow Keys: Navigate frames when paused');
// Open the help modal instead of showing an alert
store.isHelpModalOpen.value = true;
};
</script>

View File

@ -23,9 +23,11 @@
</button>
<!-- Help button -->
<button @click="emit('toggleHelp')" class="p-2 bg-gray-700 border border-gray-600 rounded hover:border-blue-500 transition-colors" title="Keyboard shortcuts">
<i class="fas fa-keyboard"></i>
</button>
<tooltip text="Keyboard Shortcuts" position="bottom">
<button @click="openHelpModal" class="p-2 bg-gray-700 border border-gray-600 rounded hover:border-blue-500 transition-colors">
<i class="fas fa-keyboard"></i>
</button>
</tooltip>
</div>
</header>
</template>
@ -33,6 +35,7 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
import Tooltip from './Tooltip.vue';
const store = useSpritesheetStore();
const zoomLevel = computed(() => store.zoomLevel.value);
@ -41,6 +44,10 @@
(e: 'toggleHelp'): void;
}>();
const openHelpModal = () => {
store.isHelpModalOpen.value = true;
};
// Expose store methods directly
const { zoomIn, zoomOut } = store;
</script>

View File

@ -1,11 +1,22 @@
<template>
<button @click="emit('showHelp')" class="fixed bottom-5 right-5 w-12 h-12 bg-blue-500 text-white rounded-full flex items-center justify-center text-xl shadow-lg cursor-pointer transition-all hover:bg-blue-600 hover:-translate-y-1 z-40">
<i class="fas fa-question"></i>
</button>
<tooltip text="Help & Support" position="left">
<button @click="openHelpModal" class="fixed bottom-5 right-5 w-12 h-12 bg-blue-500 text-white rounded-full flex items-center justify-center text-xl shadow-lg cursor-pointer transition-all hover:bg-blue-600 hover:-translate-y-1 z-40">
<i class="fas fa-question"></i>
</button>
</tooltip>
</template>
<script setup lang="ts">
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
import Tooltip from './Tooltip.vue';
const store = useSpritesheetStore();
const emit = defineEmits<{
showHelp: [];
}>();
const openHelpModal = () => {
store.isHelpModalOpen.value = true;
emit('showHelp');
};
</script>

View File

@ -0,0 +1,150 @@
<template>
<!-- Help Modal -->
<div class="fixed inset-0 flex items-center justify-center z-50 pointer-events-none" v-if="isModalOpen">
<!-- Modal backdrop with semi-transparent background -->
<div class="absolute inset-0 bg-black bg-opacity-50 pointer-events-auto" @click="closeModal"></div>
<!-- Modal content -->
<div class="bg-gray-800 rounded-lg max-w-2xl w-full max-h-[90vh] overflow-auto shadow-lg pointer-events-auto relative">
<div class="flex items-center justify-between p-4 bg-gray-700 border-b border-gray-600">
<div class="flex items-center gap-2 text-lg font-semibold">
<i class="fas fa-question-circle text-blue-500"></i>
<span>Help</span>
</div>
<button @click="closeModal" class="text-gray-400 hover:text-white">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-6">
<!-- Help content tabs -->
<div class="mb-6">
<div class="flex border-b border-gray-600 mb-4">
<button v-for="tab in tabs" :key="tab.id" @click="activeTab = tab.id" class="px-4 py-2 font-medium text-sm transition-colors" :class="activeTab === tab.id ? 'text-blue-500 border-b-2 border-blue-500' : 'text-gray-400 hover:text-gray-200'">
<i :class="tab.icon" class="mr-2"></i>{{ tab.label }}
</button>
</div>
<!-- Keyboard Shortcuts Tab -->
<div v-if="activeTab === 'shortcuts'" class="space-y-4">
<h3 class="text-lg font-semibold mb-2">Keyboard Shortcuts</h3>
<div class="bg-gray-700 rounded-lg p-4">
<div class="grid grid-cols-1 gap-4">
<div v-for="(shortcut, index) in shortcuts" :key="index" class="flex justify-between">
<span class="font-medium">{{ shortcut.key }}</span>
<span class="text-gray-300">{{ shortcut.description }}</span>
</div>
</div>
</div>
</div>
<!-- Usage Guide Tab -->
<div v-if="activeTab === 'guide'" class="space-y-4">
<h3 class="text-lg font-semibold mb-2">Usage Guide</h3>
<div class="bg-gray-700 rounded-lg p-4 space-y-3">
<p>This tool helps you create spritesheets from individual sprite images.</p>
<ol class="list-decimal pl-5 space-y-2">
<li>Upload your sprite images using the upload area</li>
<li>Arrange sprites by dragging them to desired positions</li>
<li>Adjust settings like column count in the Settings panel</li>
<li>Preview animation by clicking the Play button</li>
<li>Download your spritesheet when ready</li>
</ol>
<p class="bg-blue-600 p-2 rounded">Questions? Add me on discord: <b>nu11ed</b></p>
</div>
</div>
<!-- Donation Tab -->
<div v-if="activeTab === 'donate'" class="space-y-4">
<h3 class="text-lg font-semibold mb-2">Buy me a coffee</h3>
<p class="text-gray-300 mb-4">If you find this tool useful, please consider supporting its development with a donation.</p>
<div class="space-y-4">
<div v-for="wallet in wallets" :key="wallet.type" class="bg-gray-700 rounded-lg p-4">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center">
<i :class="wallet.icon" class="text-xl mr-2 bg-gray-800 p-1 px-2 rounded-lg" :style="{ color: wallet.color }"></i>
<span class="font-medium">{{ wallet.name }}</span>
</div>
<button @click="copyToClipboard(wallet.address)" class="text-xs bg-gray-600 hover:bg-gray-500 px-2 py-1 rounded transition-colors">Copy</button>
</div>
<div class="bg-gray-800 p-2 rounded text-xs font-mono break-all">
{{ wallet.address }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
const store = useSpritesheetStore();
const isModalOpen = computed(() => store.isHelpModalOpen.value);
const activeTab = ref('shortcuts');
const tabs = [
{ id: 'shortcuts', label: 'Shortcuts', icon: 'fas fa-keyboard' },
{ id: 'guide', label: 'Guide', icon: 'fas fa-book' },
{ id: 'donate', label: 'Donate', icon: 'fas fa-heart' },
];
const shortcuts = [
{ key: 'Shift + Drag', description: 'Fine-tune sprite position' },
{ key: 'Space', description: 'Play/Pause animation' },
{ key: 'Esc', description: 'Close preview modal' },
{ key: 'Arrow Keys', description: 'Navigate frames when paused' },
];
const wallets = [
{
type: 'paypal',
name: 'PayPal',
address: 'https://www.paypal.com/paypalme/DennisPostma298',
icon: 'fab fa-paypal',
color: '#00457c',
},
{
type: 'btc',
name: 'Bitcoin native segwit (BTC)',
address: 'bc1ql2a3nxnhfwft7qex0cclj5ar2lfsslvs0aygeq',
icon: 'fab fa-bitcoin',
color: '#f7931a',
},
{
type: 'eth',
name: 'Ethereum (ETH)',
address: '0x30843c72DF6E9A9226d967bf2403602f1C2aB67b',
icon: 'fab fa-ethereum',
color: '#627eea',
},
{
type: 'ltc',
name: 'Litecoin native segwit (LTC)',
address: 'ltc1qdkn46hpt39ppmhk25ed2eycu7m2pj5cdzuxw84',
icon: 'fas fa-litecoin-sign',
color: '#345d9d',
},
];
const closeModal = () => {
store.isHelpModalOpen.value = false;
};
const copyToClipboard = (text: string) => {
navigator.clipboard
.writeText(text)
.then(() => {
store.showNotification('Address copied to clipboard');
})
.catch(err => {
console.error('Failed to copy: ', err);
store.showNotification('Failed to copy address', 'error');
});
};
</script>

View File

@ -8,40 +8,44 @@
<!-- Navigation Items -->
<div class="flex flex-col gap-6 items-center">
<!-- Dashboard/Home -->
<button class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors" :class="{ 'bg-gray-700 text-blue-500': activeSection === 'dashboard' }" @click="setActiveSection('dashboard')" title="Dashboard">
<i class="fas fa-home"></i>
</button>
<tooltip text="Dashboard" position="right">
<button class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors" :class="{ 'bg-gray-700 text-blue-500': activeSection === 'dashboard' }" @click="setActiveSection('dashboard')">
<i class="fas fa-home"></i>
</button>
</tooltip>
<!-- Sprites -->
<button class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors relative" :class="{ 'bg-gray-700 text-blue-500': activeSection === 'sprites' }" @click="openSpritesModal" title="Sprites">
<i class="fas fa-images"></i>
<span v-if="sprites.length > 0" class="absolute -top-1 -right-1 bg-blue-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
{{ sprites.length }}
</span>
</button>
<tooltip text="Manage sprites" position="right">
<button class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors relative" :class="{ 'bg-gray-700 text-blue-500': activeSection === 'sprites' }" @click="openSpritesModal">
<i class="fas fa-images"></i>
<span v-if="sprites.length > 0" class="absolute -top-1 -right-1 bg-blue-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
{{ sprites.length }}
</span>
</button>
</tooltip>
<!-- Preview -->
<button
class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors"
:class="{ 'bg-gray-700 text-blue-500': activeSection === 'preview' }"
@click="openPreviewModal"
title="Preview Animation"
:disabled="sprites.length === 0"
>
<i class="fas fa-play"></i>
</button>
<tooltip text="Preview animation" position="right">
<button class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors" :class="{ 'bg-gray-700 text-blue-500': activeSection === 'preview' }" @click="openPreviewModal" :disabled="sprites.length === 0">
<i class="fas fa-play"></i>
</button>
</tooltip>
<!-- Settings -->
<button class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors" :class="{ 'bg-gray-700 text-blue-500': activeSection === 'settings' }" @click="openSettingsModal" title="Settings">
<i class="fas fa-cog"></i>
</button>
<tooltip text="Settings" position="right">
<button class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors" :class="{ 'bg-gray-700 text-blue-500': activeSection === 'settings' }" @click="openSettingsModal">
<i class="fas fa-cog"></i>
</button>
</tooltip>
</div>
<!-- Help Button at Bottom -->
<div class="mt-auto">
<button class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors" @click="showHelp" title="Help">
<i class="fas fa-question-circle"></i>
</button>
<tooltip text="Help & Support" position="right">
<button class="w-10 h-10 rounded-lg flex items-center justify-center text-gray-200 hover:bg-gray-700 hover:text-blue-500 transition-colors" @click="showHelp">
<i class="fas fa-question-circle"></i>
</button>
</tooltip>
</div>
</nav>
</template>
@ -49,6 +53,7 @@
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
import Tooltip from './Tooltip.vue';
const store = useSpritesheetStore();
const sprites = computed(() => store.sprites.value);
@ -83,6 +88,7 @@
}>();
const showHelp = () => {
emit('showHelp');
// Instead of just emitting, we'll now open the help modal directly
store.isHelpModalOpen.value = true;
};
</script>

View File

@ -0,0 +1,68 @@
<template>
<div class="relative inline-block">
<div @mouseenter="showTooltip" @mouseleave="hideTooltip">
<slot></slot>
</div>
<div v-show="isVisible" class="tooltip absolute z-50 px-2 py-1 text-xs font-medium text-white bg-gray-800 rounded shadow-lg whitespace-nowrap" :class="[positionClass]" :style="customStyle">
{{ text }}
<div class="absolute w-2 h-2 bg-gray-800 transform rotate-45" :class="[arrowPositionClass]"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
const props = defineProps<{
text: string;
position?: 'top' | 'right' | 'bottom' | 'left';
offset?: number;
}>();
const isVisible = ref(false);
const customStyle = ref({});
// Default position is top if not specified
const position = computed(() => props.position || 'top');
const offset = computed(() => props.offset || 8);
// Compute position classes based on the position prop
const positionClass = computed(() => {
switch (position.value) {
case 'top':
return 'bottom-full mb-2';
case 'right':
return 'left-full ml-2';
case 'bottom':
return 'top-full mt-2';
case 'left':
return 'right-full mr-2';
default:
return 'bottom-full mb-2';
}
});
// Compute arrow position classes based on the position prop
const arrowPositionClass = computed(() => {
switch (position.value) {
case 'top':
return 'bottom-[-4px] left-1/2 -translate-x-1/2';
case 'right':
return 'left-[-4px] top-1/2 -translate-y-1/2';
case 'bottom':
return 'top-[-4px] left-1/2 -translate-x-1/2';
case 'left':
return 'right-[-4px] top-1/2 -translate-y-1/2';
default:
return 'bottom-[-4px] left-1/2 -translate-x-1/2';
}
});
const showTooltip = () => {
isVisible.value = true;
};
const hideTooltip = () => {
isVisible.value = false;
};
</script>

View File

@ -39,6 +39,7 @@ const isShiftPressed = ref(false);
const isModalOpen = ref(false);
const isSettingsModalOpen = ref(false);
const isSpritesModalOpen = ref(false);
const isHelpModalOpen = ref(false);
const zoomLevel = ref(1); // Default zoom level (1 = 100%)
// Preview border settings
@ -543,6 +544,7 @@ export function useSpritesheetStore() {
isModalOpen,
isSettingsModalOpen,
isSpritesModalOpen,
isHelpModalOpen,
animation,
notification,
zoomLevel,