Spaces:
Running
Running
| import { type ClassValue, clsx } from "clsx"; | |
| import { twMerge } from "tailwind-merge"; | |
| export function cn(...inputs: ClassValue[]) { | |
| return twMerge(clsx(inputs)); | |
| } | |
| export function formatFileSize(bytes: number): string { | |
| if (bytes === 0) return "0 Bytes"; | |
| const k = 1024; | |
| const sizes = ["Bytes", "KB", "MB", "GB"]; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; | |
| } | |
| export function formatTimestamp(timestamp: string): string { | |
| return new Date(timestamp).toLocaleString(); | |
| } | |
| export function formatRelativeTime(dateString: string): string { | |
| const date = new Date(dateString); | |
| const now = new Date(); | |
| const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); | |
| if (diffInSeconds < 60) { | |
| return "just now"; | |
| } | |
| const diffInMinutes = Math.floor(diffInSeconds / 60); | |
| if (diffInMinutes < 60) { | |
| return `${diffInMinutes} minute${diffInMinutes > 1 ? "s" : ""} ago`; | |
| } | |
| const diffInHours = Math.floor(diffInMinutes / 60); | |
| if (diffInHours < 24) { | |
| return `${diffInHours} hour${diffInHours > 1 ? "s" : ""} ago`; | |
| } | |
| const diffInDays = Math.floor(diffInHours / 24); | |
| if (diffInDays < 7) { | |
| return `${diffInDays} day${diffInDays > 1 ? "s" : ""} ago`; | |
| } | |
| // For older dates, show the actual date | |
| return date.toLocaleDateString(); | |
| } | |
| export function generateId(): string { | |
| return Math.random().toString(36).substr(2, 9); | |
| } | |
| export function debounce<T extends (...args: any[]) => any>( | |
| func: T, | |
| delay: number | |
| ): (...args: Parameters<T>) => void { | |
| let timeoutId: NodeJS.Timeout; | |
| return (...args: Parameters<T>) => { | |
| clearTimeout(timeoutId); | |
| timeoutId = setTimeout(() => func(...args), delay); | |
| }; | |
| } | |
| /** | |
| * Limit JSON object depth for preview performance | |
| * @param obj - Object to limit depth | |
| * @param maxDepth - Maximum depth to traverse (default: 1) | |
| * @returns Depth-limited object | |
| */ | |
| export function limitJsonDepth(obj: any, maxDepth: number = 1): any { | |
| if (maxDepth <= 0) { | |
| if (typeof obj === 'object' && obj !== null) { | |
| if (Array.isArray(obj)) { | |
| return `[Array(${obj.length})]`; | |
| } | |
| return `{Object with ${Object.keys(obj).length} keys}`; | |
| } | |
| return obj; | |
| } | |
| if (typeof obj !== 'object' || obj === null) { | |
| return obj; | |
| } | |
| if (Array.isArray(obj)) { | |
| return obj.slice(0, 3).map(item => limitJsonDepth(item, maxDepth - 1)) | |
| .concat(obj.length > 3 ? [`... ${obj.length - 3} more items`] : []); | |
| } | |
| const limited: any = {}; | |
| const keys = Object.keys(obj); | |
| const maxKeys = 5; | |
| for (let i = 0; i < Math.min(keys.length, maxKeys); i++) { | |
| const key = keys[i]; | |
| if (key !== undefined) { | |
| limited[key] = limitJsonDepth(obj[key], maxDepth - 1); | |
| } | |
| } | |
| if (keys.length > maxKeys) { | |
| limited[`_truncated_${keys.length - maxKeys}_more_keys`] = '...'; | |
| } | |
| return limited; | |
| } | |