🎨 Improve code readability by reformatting functions and cleaning up whitespace
This commit is contained in:
65
code/websites/pokedex.online/src/composables/useClipboard.js
Normal file
65
code/websites/pokedex.online/src/composables/useClipboard.js
Normal file
@@ -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<boolean>} 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<string|null>} 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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
84
code/websites/pokedex.online/src/composables/useUrlState.js
Normal file
84
code/websites/pokedex.online/src/composables/useUrlState.js
Normal file
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
@@ -74,10 +90,12 @@ export function extractJsonPathsLazy(data, startIndex, existingPaths, callback,
|
||||
|
||||
if (addedPaths.length > 0) {
|
||||
addedPaths.forEach(p => existingPaths.add(p));
|
||||
callback(addedPaths.map(path => ({
|
||||
callback(
|
||||
addedPaths.map(path => ({
|
||||
path,
|
||||
breadcrumb: path.replace(/\./g, ' › ')
|
||||
})));
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
if (end < data.length) {
|
||||
|
||||
Reference in New Issue
Block a user