Better dev experience, better front page
All checks were successful
pipeline / build-and-push-images (push) Successful in 5m30s
All checks were successful
pipeline / build-and-push-images (push) Successful in 5m30s
This commit is contained in:
164
frontend/utils/helpers.ts
Normal file
164
frontend/utils/helpers.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Frontend utility functions for BuildPath application
|
||||
*/
|
||||
|
||||
/**
|
||||
* Debounce function to limit how often a function can be called
|
||||
* @param func Function to debounce
|
||||
* @param wait Time in milliseconds to wait before calling the function
|
||||
* @returns Debounced function
|
||||
*/
|
||||
export function debounce<T extends (...args: any[]) => any>(func: T, wait: number): (...args: Parameters<T>) => void {
|
||||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
return function(...args: Parameters<T>): void {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
timeout = setTimeout(() => func(...args), wait);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe JSON parsing with error handling
|
||||
* @param data Data to parse
|
||||
* @param defaultValue Default value to return if parsing fails
|
||||
* @returns Parsed JSON or default value
|
||||
*/
|
||||
export function safeJsonParse<T>(data: string, defaultValue: T): T {
|
||||
try {
|
||||
return JSON.parse(data) as T;
|
||||
} catch (error) {
|
||||
console.error('JSON parse error:', error);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format number as percentage with specified decimal places
|
||||
* @param value Number to format
|
||||
* @param decimals Number of decimal places
|
||||
* @returns Formatted percentage string
|
||||
*/
|
||||
export function formatPercentage(value: number, decimals: number = 0): string {
|
||||
return (value * 100).toFixed(decimals) + '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* Capitalize first letter of string
|
||||
* @param str String to capitalize
|
||||
* @returns Capitalized string
|
||||
*/
|
||||
export function capitalize(str: string): string {
|
||||
if (!str) return '';
|
||||
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert lane position to readable name
|
||||
* @param position Lane position string
|
||||
* @returns Readable lane name
|
||||
*/
|
||||
export function getLaneName(position: string): string {
|
||||
const laneMap: Record<string, string> = {
|
||||
'top': 'Top',
|
||||
'jungle': 'Jungle',
|
||||
'middle': 'Middle',
|
||||
'bottom': 'Bottom',
|
||||
'utility': 'Support'
|
||||
};
|
||||
return laneMap[position.toLowerCase()] || position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate champion image URL
|
||||
* @param championAlias Champion alias
|
||||
* @returns Full image URL
|
||||
*/
|
||||
export function getChampionImageUrl(championAlias: string): string {
|
||||
return `/img/champions/${championAlias.toLowerCase()}.png`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate item image URL
|
||||
* @param itemId Item ID
|
||||
* @returns Full item image URL
|
||||
*/
|
||||
export function getItemImageUrl(itemId: number): string {
|
||||
return `${CDRAGON_BASE}plugins/rcp-be-lol-game-data/global/default/v1/items/${itemId}.png`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate rune image URL
|
||||
* @param runeId Rune ID
|
||||
* @returns Full rune image URL
|
||||
*/
|
||||
export function getRuneImageUrl(runeId: number): string {
|
||||
return `${CDRAGON_BASE}plugins/rcp-be-lol-game-data/global/default/v1/perks/${runeId}.png`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format large numbers with abbreviations (K, M)
|
||||
* @param num Number to format
|
||||
* @returns Formatted string
|
||||
*/
|
||||
export function formatLargeNumber(num: number): string {
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + 'M';
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'K';
|
||||
}
|
||||
return num.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep clone an object
|
||||
* @param obj Object to clone
|
||||
* @returns Cloned object
|
||||
*/
|
||||
export function deepClone<T>(obj: T): T {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if value is empty (null, undefined, empty string, empty array, empty object)
|
||||
* @param value Value to check
|
||||
* @returns True if value is empty
|
||||
*/
|
||||
export function isEmpty(value: any): boolean {
|
||||
if (value === null || value === undefined) return true;
|
||||
if (typeof value === 'string' && value.trim() === '') return true;
|
||||
if (Array.isArray(value) && value.length === 0) return true;
|
||||
if (typeof value === 'object' && Object.keys(value).length === 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get winrate color based on value
|
||||
* @param winrate Winrate value (0-1)
|
||||
* @returns CSS color class
|
||||
*/
|
||||
export function getWinrateColor(winrate: number): string {
|
||||
if (winrate > 0.55) return 'text-green-500';
|
||||
if (winrate < 0.45) return 'text-red-500';
|
||||
return 'text-yellow-500';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format duration in milliseconds to readable string
|
||||
* @param ms Duration in milliseconds
|
||||
* @returns Formatted duration string
|
||||
*/
|
||||
export function formatDuration(ms: number): string {
|
||||
const seconds = Math.floor(ms / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}h ${minutes % 60}m`;
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}m ${seconds % 60}s`;
|
||||
} else {
|
||||
return `${seconds}s`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user