✨ Add utility functions for JSON handling and performance optimization
This commit is contained in:
134
code/websites/pokedex.online/src/utilities/json-utils.js
Normal file
134
code/websites/pokedex.online/src/utilities/json-utils.js
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
* JSON Path Extraction Utility
|
||||||
|
* Extracts property paths from JSON objects for filtering
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract all JSON paths from an object recursively
|
||||||
|
* @param {Object} obj - Object to extract paths from
|
||||||
|
* @param {string} prefix - Current path prefix
|
||||||
|
* @param {Set} paths - Accumulated paths
|
||||||
|
* @param {number} maxDepth - Maximum recursion depth
|
||||||
|
* @param {number} currentDepth - Current recursion depth
|
||||||
|
*/
|
||||||
|
function extractPathsRecursive(obj, prefix = '', paths = new Set(), maxDepth = 5, currentDepth = 0) {
|
||||||
|
if (currentDepth >= maxDepth || obj === null || typeof obj !== 'object') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(obj).forEach(key => {
|
||||||
|
const path = prefix ? `${prefix}.${key}` : key;
|
||||||
|
paths.add(path);
|
||||||
|
|
||||||
|
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
||||||
|
extractPathsRecursive(obj[key], path, paths, maxDepth, currentDepth + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract JSON paths from an array of objects
|
||||||
|
* @param {Array} data - Array of objects
|
||||||
|
* @param {number} sampleSize - Number of items to sample (default: 100)
|
||||||
|
* @returns {Array<Object>} Array of {path, breadcrumb} objects
|
||||||
|
*/
|
||||||
|
export function extractJsonPaths(data, sampleSize = 100) {
|
||||||
|
const paths = new Set();
|
||||||
|
const sample = data.slice(0, Math.min(sampleSize, data.length));
|
||||||
|
|
||||||
|
sample.forEach(item => {
|
||||||
|
extractPathsRecursive(item, '', paths);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(paths)
|
||||||
|
.sort()
|
||||||
|
.map(path => ({
|
||||||
|
path,
|
||||||
|
breadcrumb: path.replace(/\./g, ' › ')
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continue extracting paths from remaining items (lazy loading)
|
||||||
|
* @param {Array} data - Full data array
|
||||||
|
* @param {number} startIndex - Where to start processing
|
||||||
|
* @param {Set} existingPaths - Existing paths to add to
|
||||||
|
* @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) {
|
||||||
|
if (startIndex >= data.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const processChunk = (index) => {
|
||||||
|
const end = Math.min(index + chunkSize, data.length);
|
||||||
|
const chunk = data.slice(index, end);
|
||||||
|
const newPaths = new Set(existingPaths);
|
||||||
|
|
||||||
|
chunk.forEach(item => {
|
||||||
|
extractPathsRecursive(item, '', newPaths);
|
||||||
|
});
|
||||||
|
|
||||||
|
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, ' › ')
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end < data.length) {
|
||||||
|
requestIdleCallback(() => processChunk(end));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof requestIdleCallback !== 'undefined') {
|
||||||
|
requestIdleCallback(() => processChunk(startIndex));
|
||||||
|
} else {
|
||||||
|
setTimeout(() => processChunk(startIndex), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get value from object using dot notation path
|
||||||
|
* @param {Object} obj - Object to get value from
|
||||||
|
* @param {string} path - Dot notation path (e.g., 'data.pokemonSettings.pokemonId')
|
||||||
|
* @returns {any} Value at path or undefined
|
||||||
|
*/
|
||||||
|
export function getValueByPath(obj, path) {
|
||||||
|
return path.split('.').reduce((current, key) => current?.[key], obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate text in the middle
|
||||||
|
* @param {string} text - Text to truncate
|
||||||
|
* @param {number} matchIndex - Index of match for context
|
||||||
|
* @param {number} maxLength - Maximum length
|
||||||
|
* @returns {string} Truncated text
|
||||||
|
*/
|
||||||
|
export function truncateMiddle(text, matchIndex = 0, maxLength = 100) {
|
||||||
|
if (text.length <= maxLength) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
const halfLength = Math.floor(maxLength / 2);
|
||||||
|
const start = Math.max(0, matchIndex - halfLength);
|
||||||
|
const end = Math.min(text.length, matchIndex + halfLength);
|
||||||
|
|
||||||
|
let result = '';
|
||||||
|
|
||||||
|
if (start > 0) {
|
||||||
|
result += '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
result += text.substring(start, end);
|
||||||
|
|
||||||
|
if (end < text.length) {
|
||||||
|
result += '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* Performance Monitoring Utility
|
||||||
|
* Wraps operations with console.time/timeEnd for development and production feature flag
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if performance monitoring is enabled
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isPerfMonitoringEnabled() {
|
||||||
|
return (
|
||||||
|
import.meta.env.DEV ||
|
||||||
|
localStorage.getItem('enablePerfMonitoring') === 'true'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitor performance of an operation
|
||||||
|
* @param {string} label - Operation label
|
||||||
|
* @param {Function} fn - Function to monitor
|
||||||
|
* @returns {Promise<any>|any} Result of the function
|
||||||
|
*/
|
||||||
|
export async function perfMonitor(label, fn) {
|
||||||
|
if (!isPerfMonitoringEnabled()) {
|
||||||
|
return typeof fn === 'function' ? await fn() : fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
const perfLabel = `⚡ ${label}`;
|
||||||
|
console.time(perfLabel);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = typeof fn === 'function' ? await fn() : fn;
|
||||||
|
console.timeEnd(perfLabel);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.timeEnd(perfLabel);
|
||||||
|
console.error(`${perfLabel} failed:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect device performance characteristics
|
||||||
|
* @returns {Object} Performance info
|
||||||
|
*/
|
||||||
|
export function getDevicePerformance() {
|
||||||
|
const cores = navigator.hardwareConcurrency || 4;
|
||||||
|
const memory = navigator.deviceMemory || 4; // GB
|
||||||
|
|
||||||
|
return {
|
||||||
|
cores,
|
||||||
|
memory,
|
||||||
|
isHighPerformance: cores >= 8 && memory >= 8,
|
||||||
|
isMediumPerformance: cores >= 4 && memory >= 4,
|
||||||
|
isLowPerformance: cores < 4 || memory < 4,
|
||||||
|
recommendedChunkSize: cores >= 8 ? 1000 : cores >= 4 ? 500 : 250
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debounce function for performance optimization
|
||||||
|
* @param {Function} fn - Function to debounce
|
||||||
|
* @param {number} delay - Delay in milliseconds
|
||||||
|
* @returns {Function} Debounced function
|
||||||
|
*/
|
||||||
|
export function debounce(fn, delay = 300) {
|
||||||
|
let timeoutId;
|
||||||
|
|
||||||
|
return function (...args) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = setTimeout(() => fn.apply(this, args), delay);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throttle function for performance optimization
|
||||||
|
* @param {Function} fn - Function to throttle
|
||||||
|
* @param {number} limit - Limit in milliseconds
|
||||||
|
* @returns {Function} Throttled function
|
||||||
|
*/
|
||||||
|
export function throttle(fn, limit = 100) {
|
||||||
|
let inThrottle;
|
||||||
|
|
||||||
|
return function (...args) {
|
||||||
|
if (!inThrottle) {
|
||||||
|
fn.apply(this, args);
|
||||||
|
inThrottle = true;
|
||||||
|
setTimeout(() => (inThrottle = false), limit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user