🎨 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} maxDepth - Maximum recursion depth
|
||||||
* @param {number} currentDepth - Current 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') {
|
if (currentDepth >= maxDepth || obj === null || typeof obj !== 'object') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -20,7 +26,11 @@ function extractPathsRecursive(obj, prefix = '', paths = new Set(), maxDepth = 5
|
|||||||
const path = prefix ? `${prefix}.${key}` : key;
|
const path = prefix ? `${prefix}.${key}` : key;
|
||||||
paths.add(path);
|
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);
|
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 {Function} callback - Callback when new paths found
|
||||||
* @param {number} chunkSize - Items to process per chunk
|
* @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) {
|
if (startIndex >= data.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const processChunk = (index) => {
|
const processChunk = index => {
|
||||||
const end = Math.min(index + chunkSize, data.length);
|
const end = Math.min(index + chunkSize, data.length);
|
||||||
const chunk = data.slice(index, end);
|
const chunk = data.slice(index, end);
|
||||||
const newPaths = new Set(existingPaths);
|
const newPaths = new Set(existingPaths);
|
||||||
@@ -74,10 +90,12 @@ export function extractJsonPathsLazy(data, startIndex, existingPaths, callback,
|
|||||||
|
|
||||||
if (addedPaths.length > 0) {
|
if (addedPaths.length > 0) {
|
||||||
addedPaths.forEach(p => existingPaths.add(p));
|
addedPaths.forEach(p => existingPaths.add(p));
|
||||||
callback(addedPaths.map(path => ({
|
callback(
|
||||||
|
addedPaths.map(path => ({
|
||||||
path,
|
path,
|
||||||
breadcrumb: path.replace(/\./g, ' › ')
|
breadcrumb: path.replace(/\./g, ' › ')
|
||||||
})));
|
}))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end < data.length) {
|
if (end < data.length) {
|
||||||
|
|||||||
Reference in New Issue
Block a user