From 07c4379d81f8ef0f34fb670ff67504d1456f5dcd Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Wed, 28 Jan 2026 19:50:00 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Improve=20code=20readability=20b?= =?UTF-8?q?y=20reformatting=20functions=20and=20cleaning=20up=20whitespace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/composables/useClipboard.js | 65 ++++++++++++++ .../src/composables/useKeyboardShortcuts.js | 57 +++++++++++++ .../src/composables/useLocalStorage.js | 81 ++++++++++++++++++ .../src/composables/useUrlState.js | 84 +++++++++++++++++++ .../src/utilities/json-utils.js | 42 +++++++--- .../src/utilities/performance-utils.js | 4 +- 6 files changed, 319 insertions(+), 14 deletions(-) create mode 100644 code/websites/pokedex.online/src/composables/useClipboard.js create mode 100644 code/websites/pokedex.online/src/composables/useKeyboardShortcuts.js create mode 100644 code/websites/pokedex.online/src/composables/useLocalStorage.js create mode 100644 code/websites/pokedex.online/src/composables/useUrlState.js diff --git a/code/websites/pokedex.online/src/composables/useClipboard.js b/code/websites/pokedex.online/src/composables/useClipboard.js new file mode 100644 index 0000000..3a5975b --- /dev/null +++ b/code/websites/pokedex.online/src/composables/useClipboard.js @@ -0,0 +1,65 @@ +/** + * Clipboard Composable + * Handles clipboard operations with feedback + */ + +import { ref } from 'vue'; + +/** + * Clipboard operations composable + * @returns {Object} Clipboard functions and state + */ +export function useClipboard() { + const copied = ref(false); + const error = ref(null); + + /** + * Copy text to clipboard + * @param {string} text - Text to copy + * @returns {Promise} Success status + */ + const copyToClipboard = async (text) => { + error.value = null; + copied.value = false; + + try { + await navigator.clipboard.writeText(text); + copied.value = true; + + // Reset after 2 seconds + setTimeout(() => { + copied.value = false; + }, 2000); + + return true; + } catch (err) { + error.value = err.message; + console.error('Failed to copy to clipboard:', err); + return false; + } + }; + + /** + * Read text from clipboard + * @returns {Promise} Clipboard text or null + */ + const readFromClipboard = async () => { + error.value = null; + + try { + const text = await navigator.clipboard.readText(); + return text; + } catch (err) { + error.value = err.message; + console.error('Failed to read from clipboard:', err); + return null; + } + }; + + return { + copied, + error, + copyToClipboard, + readFromClipboard + }; +} diff --git a/code/websites/pokedex.online/src/composables/useKeyboardShortcuts.js b/code/websites/pokedex.online/src/composables/useKeyboardShortcuts.js new file mode 100644 index 0000000..6c9d9ba --- /dev/null +++ b/code/websites/pokedex.online/src/composables/useKeyboardShortcuts.js @@ -0,0 +1,57 @@ +/** + * Keyboard Shortcuts Composable + * Manages keyboard event listeners and shortcuts + */ + +import { onMounted, onUnmounted } from 'vue'; + +/** + * Register keyboard shortcuts + * @param {Object} shortcuts - Map of key combinations to handlers + * @returns {Object} Control functions + * + * Example shortcuts object: + * { + * 'ctrl+f': () => focusSearch(), + * 'ctrl+c': () => copySelected(), + * 'escape': () => clearSelection() + * } + */ +export function useKeyboardShortcuts(shortcuts = {}) { + const handleKeyDown = (event) => { + const key = event.key.toLowerCase(); + const ctrl = event.ctrlKey || event.metaKey; // Support both Ctrl and Cmd + const shift = event.shiftKey; + const alt = event.altKey; + + // Build combination string + let combination = ''; + if (ctrl) combination += 'ctrl+'; + if (shift) combination += 'shift+'; + if (alt) combination += 'alt+'; + combination += key; + + // Also check without modifiers + const simpleKey = key; + + // Try to find and execute handler + const handler = shortcuts[combination] || shortcuts[simpleKey]; + + if (handler) { + event.preventDefault(); + handler(event); + } + }; + + onMounted(() => { + window.addEventListener('keydown', handleKeyDown); + }); + + onUnmounted(() => { + window.removeEventListener('keydown', handleKeyDown); + }); + + return { + // Can add control functions here if needed + }; +} diff --git a/code/websites/pokedex.online/src/composables/useLocalStorage.js b/code/websites/pokedex.online/src/composables/useLocalStorage.js new file mode 100644 index 0000000..d4dcef4 --- /dev/null +++ b/code/websites/pokedex.online/src/composables/useLocalStorage.js @@ -0,0 +1,81 @@ +/** + * LocalStorage Composable + * Type-safe localStorage operations with Vue reactivity + */ + +import { ref, watch } from 'vue'; + +/** + * Use localStorage with reactivity + * @param {string} key - Storage key + * @param {any} defaultValue - Default value + * @returns {Ref} Reactive ref synced with localStorage + */ +export function useLocalStorage(key, defaultValue) { + // Try to load from localStorage + const loadValue = () => { + try { + const item = localStorage.getItem(key); + if (item !== null) { + return JSON.parse(item); + } + } catch (error) { + console.error(`Error loading ${key} from localStorage:`, error); + } + return defaultValue; + }; + + const storedValue = ref(loadValue()); + + // Watch for changes and save to localStorage + watch( + storedValue, + (newValue) => { + try { + localStorage.setItem(key, JSON.stringify(newValue)); + } catch (error) { + console.error(`Error saving ${key} to localStorage:`, error); + } + }, + { deep: true } + ); + + return storedValue; +} + +/** + * Manage search history in localStorage + * @param {string} key - Storage key + * @param {number} maxItems - Maximum number of items to store + * @returns {Object} History management functions + */ +export function useSearchHistory(key = 'searchHistory', maxItems = 5) { + const history = useLocalStorage(key, []); + + const addToHistory = (query) => { + if (!query || query.trim() === '') return; + + // Remove duplicates and add to front + const newHistory = [ + query, + ...history.value.filter(item => item !== query) + ].slice(0, maxItems); + + history.value = newHistory; + }; + + const removeFromHistory = (query) => { + history.value = history.value.filter(item => item !== query); + }; + + const clearHistory = () => { + history.value = []; + }; + + return { + history, + addToHistory, + removeFromHistory, + clearHistory + }; +} diff --git a/code/websites/pokedex.online/src/composables/useUrlState.js b/code/websites/pokedex.online/src/composables/useUrlState.js new file mode 100644 index 0000000..7449501 --- /dev/null +++ b/code/websites/pokedex.online/src/composables/useUrlState.js @@ -0,0 +1,84 @@ +/** + * URL State Composable + * Synchronizes component state with URL query parameters + */ + +import { watch, onMounted } from 'vue'; +import { useRouter, useRoute } from 'vue-router'; + +/** + * Sync state with URL query parameters + * @param {Object} stateRefs - Object of refs to sync {key: ref} + * @returns {Object} Control functions + */ +export function useUrlState(stateRefs) { + const router = useRouter(); + const route = useRoute(); + + /** + * Update URL with current state + */ + const updateUrl = () => { + const query = {}; + + Object.entries(stateRefs).forEach(([key, ref]) => { + const value = ref.value; + + if (value !== null && value !== undefined && value !== '') { + if (Array.isArray(value)) { + query[key] = value.join(','); + } else { + query[key] = String(value); + } + } + }); + + router.replace({ query }); + }; + + /** + * Load state from URL + */ + const loadFromUrl = () => { + Object.entries(stateRefs).forEach(([key, ref]) => { + const value = route.query[key]; + + if (value) { + if (Array.isArray(ref.value)) { + ref.value = value.split(','); + } else if (typeof ref.value === 'number') { + ref.value = parseInt(value, 10) || 0; + } else if (typeof ref.value === 'boolean') { + ref.value = value === 'true'; + } else { + ref.value = value; + } + } + }); + }; + + /** + * Handle browser back/forward + */ + const handlePopState = () => { + loadFromUrl(); + }; + + onMounted(() => { + // Load initial state from URL + loadFromUrl(); + + // Listen for browser back/forward + window.addEventListener('popstate', handlePopState); + }); + + // Watch for state changes and update URL + Object.values(stateRefs).forEach(ref => { + watch(ref, updateUrl, { deep: true }); + }); + + return { + updateUrl, + loadFromUrl + }; +} diff --git a/code/websites/pokedex.online/src/utilities/json-utils.js b/code/websites/pokedex.online/src/utilities/json-utils.js index 0097e2c..7b2adb2 100644 --- a/code/websites/pokedex.online/src/utilities/json-utils.js +++ b/code/websites/pokedex.online/src/utilities/json-utils.js @@ -11,7 +11,13 @@ * @param {number} maxDepth - Maximum recursion depth * @param {number} currentDepth - Current recursion depth */ -function extractPathsRecursive(obj, prefix = '', paths = new Set(), maxDepth = 5, currentDepth = 0) { +function extractPathsRecursive( + obj, + prefix = '', + paths = new Set(), + maxDepth = 5, + currentDepth = 0 +) { if (currentDepth >= maxDepth || obj === null || typeof obj !== 'object') { return; } @@ -20,7 +26,11 @@ function extractPathsRecursive(obj, prefix = '', paths = new Set(), maxDepth = 5 const path = prefix ? `${prefix}.${key}` : key; paths.add(path); - if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) { + if ( + typeof obj[key] === 'object' && + obj[key] !== null && + !Array.isArray(obj[key]) + ) { extractPathsRecursive(obj[key], path, paths, maxDepth, currentDepth + 1); } }); @@ -56,12 +66,18 @@ export function extractJsonPaths(data, sampleSize = 100) { * @param {Function} callback - Callback when new paths found * @param {number} chunkSize - Items to process per chunk */ -export function extractJsonPathsLazy(data, startIndex, existingPaths, callback, chunkSize = 100) { +export function extractJsonPathsLazy( + data, + startIndex, + existingPaths, + callback, + chunkSize = 100 +) { if (startIndex >= data.length) { return; } - const processChunk = (index) => { + const processChunk = index => { const end = Math.min(index + chunkSize, data.length); const chunk = data.slice(index, end); const newPaths = new Set(existingPaths); @@ -71,13 +87,15 @@ export function extractJsonPathsLazy(data, startIndex, existingPaths, callback, }); const addedPaths = Array.from(newPaths).filter(p => !existingPaths.has(p)); - + if (addedPaths.length > 0) { addedPaths.forEach(p => existingPaths.add(p)); - callback(addedPaths.map(path => ({ - path, - breadcrumb: path.replace(/\./g, ' › ') - }))); + callback( + addedPaths.map(path => ({ + path, + breadcrumb: path.replace(/\./g, ' › ') + })) + ); } if (end < data.length) { @@ -119,13 +137,13 @@ export function truncateMiddle(text, matchIndex = 0, maxLength = 100) { const end = Math.min(text.length, matchIndex + halfLength); let result = ''; - + if (start > 0) { result += '...'; } - + result += text.substring(start, end); - + if (end < text.length) { result += '...'; } diff --git a/code/websites/pokedex.online/src/utilities/performance-utils.js b/code/websites/pokedex.online/src/utilities/performance-utils.js index 383b081..7f44160 100644 --- a/code/websites/pokedex.online/src/utilities/performance-utils.js +++ b/code/websites/pokedex.online/src/utilities/performance-utils.js @@ -65,7 +65,7 @@ export function getDevicePerformance() { */ export function debounce(fn, delay = 300) { let timeoutId; - + return function (...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => fn.apply(this, args), delay); @@ -80,7 +80,7 @@ export function debounce(fn, delay = 300) { */ export function throttle(fn, limit = 100) { let inThrottle; - + return function (...args) { if (!inThrottle) { fn.apply(this, args);