/** * Shared utilities for S3 browser components */ export interface IMoveValidation { valid: boolean; error?: string; } /** * Format a byte size into a human-readable string */ export function formatSize(bytes?: number): string { if (bytes === undefined || bytes === null) return '-'; if (bytes === 0) return '0 B'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(unitIndex > 0 ? 1 : 0)} ${units[unitIndex]}`; } /** * Format a count into a compact human-readable string */ export function formatCount(count?: number): string { if (count === undefined || count === null) return ''; if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`; if (count >= 1000) return `${(count / 1000).toFixed(1)}K`; return count.toString(); } /** * Extract the file name from a path */ export function getFileName(path: string): string { const parts = path.replace(/\/$/, '').split('/'); return parts[parts.length - 1] || path; } /** * Validates if a move operation is allowed */ export function validateMove(sourceKey: string, destPrefix: string): IMoveValidation { if (sourceKey.endsWith('/')) { if (destPrefix.startsWith(sourceKey)) { return { valid: false, error: 'Cannot move a folder into itself' }; } } const sourceParent = getParentPrefix(sourceKey); if (sourceParent === destPrefix) { return { valid: false, error: 'Item is already in this location' }; } return { valid: true }; } /** * Gets the parent prefix (directory) of a given key */ export function getParentPrefix(key: string): string { const trimmed = key.endsWith('/') ? key.slice(0, -1) : key; const lastSlash = trimmed.lastIndexOf('/'); return lastSlash >= 0 ? trimmed.substring(0, lastSlash + 1) : ''; } /** * Get content type from file extension */ export function getContentType(ext: string): string { const contentTypes: Record = { json: 'application/json', txt: 'text/plain', html: 'text/html', css: 'text/css', js: 'application/javascript', ts: 'text/typescript', md: 'text/markdown', xml: 'application/xml', yaml: 'text/yaml', yml: 'text/yaml', csv: 'text/csv', }; return contentTypes[ext] || 'application/octet-stream'; } /** * Get default content for a new file based on extension */ export function getDefaultContent(ext: string): string { const defaults: Record = { json: '{\n \n}', html: '\n\n\n \n\n\n \n\n', md: '# Title\n\n', txt: '', }; return defaults[ext] || ''; } /** * Parse a prefix into cumulative path segments */ export function getPathSegments(prefix: string): string[] { if (!prefix) return []; const parts = prefix.split('/').filter(p => p); const segments: string[] = []; let cumulative = ''; for (const part of parts) { cumulative += part + '/'; segments.push(cumulative); } return segments; }