Consolidate duplicate utility functions into shared modules

Extract repeated helper functions from components into shared utils
(colors.ts, date.ts, file.ts) to reduce duplication:
- isLightColor, parseFilamentColor, hexToColorName → utils/colors.ts
- formatMediaTime, formatDurationFromHours → utils/date.ts
- formatFileSize guard hardened for NaN/negative → utils/file.ts
- formatBytes (ToastContext), formatTime (Timelapse*) deduplicated

Functions with subtly different behavior are intentionally kept local
(formatFilament, formatStorageSize, formatBytes, formatUptime,
formatDateTime, formatDate, formatTimeAgo) to preserve exact UI output.
This commit is contained in:
AneoPsy
2026-03-03 04:20:21 -10:00
parent 15521deb0a
commit abe40f1ed0
15 changed files with 507 additions and 577 deletions
+30
View File
@@ -83,6 +83,9 @@ export function hexToColorName(hex: string | null | undefined): string {
if (l > 0.6) return 'Light Gray';
return 'Gray';
}
// Brown is orange/yellow hue with lower lightness
if (h >= 15 && h < 45 && l < 0.45) return 'Brown';
if (h >= 45 && h < 70 && l < 0.40) return 'Brown';
if (h < 15 || h >= 345) return 'Red';
if (h < 45) return 'Orange';
if (h < 70) return 'Yellow';
@@ -125,3 +128,30 @@ export function resolveSpoolColorName(colorName: string | null, rgba: string | n
// Return null (displayed as "-") — better than showing a code
return null;
}
/**
* Parse an RGBA hex string (e.g., "FF0000FF") to a CSS rgba() color.
* Returns null for empty, all-zero, or fully transparent colors.
*/
export function parseFilamentColor(rgba: string): string | null {
if (!rgba || rgba === '00000000' || rgba.length < 6) return null;
const r = rgba.slice(0, 2);
const g = rgba.slice(2, 4);
const b = rgba.slice(4, 6);
const a = rgba.length >= 8 ? parseInt(rgba.slice(6, 8), 16) / 255 : 1;
if (a === 0) return null;
return `rgba(${parseInt(r, 16)}, ${parseInt(g, 16)}, ${parseInt(b, 16)}, ${a})`;
}
/**
* Check if a hex color is light (for choosing text contrast).
* Uses luminance formula: 0.299*R + 0.587*G + 0.114*B.
*/
export function isLightColor(hex: string | null): boolean {
if (!hex || hex.length < 6) return false;
const cleanHex = hex.replace('#', '');
const r = parseInt(cleanHex.slice(0, 2), 16);
const g = parseInt(cleanHex.slice(2, 4), 16);
const b = parseInt(cleanHex.slice(4, 6), 16);
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.6;
}
+25
View File
@@ -418,3 +418,28 @@ export function formatRelativeTime(
// Older than 7 days
return formatDateTime(dateStr, timeFormat);
}
/**
* Format seconds as MM:SS for media/video player display.
*
* @param seconds - Total seconds
* @returns Formatted string (e.g., "2:05", "0:30")
*/
export function formatMediaTime(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
/**
* Format a duration given in hours to a human-readable string.
*
* @param hours - Duration in hours (e.g., 2.5)
* @returns Formatted string (e.g., "2h 30m", "45m", "3h")
*/
export function formatDurationFromHours(hours: number): string {
if (hours < 1) return `${Math.round(hours * 60)}m`;
const h = Math.floor(hours);
const m = Math.round((hours - h) * 60);
return m > 0 ? `${h}h ${m}m` : `${h}h`;
}
+1 -1
View File
@@ -5,7 +5,7 @@
* @returns A formatted string with the appropriate unit (B, KB, MB, GB, or TB).
*/
export function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 B';
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
const k = 1024;