Tooltips, donation, new help window
This commit is contained in:
parent
8fdb0dbae3
commit
376bf00ad4
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
150
src/components/HelpModal.vue
Normal file
150
src/components/HelpModal.vue
Normal 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>
|
@ -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>
|
||||
|
68
src/components/Tooltip.vue
Normal file
68
src/components/Tooltip.vue
Normal 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>
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user