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
+2 -9
View File
@@ -2,6 +2,7 @@ import { AlertCircle, CheckCircle, ChevronDown, ChevronUp, Info, Loader2, X, XCi
import { createContext, useCallback, useContext, useEffect, useRef, useState, type ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { api } from '../api/client';
import { formatFileSize } from '../utils/file';
type ToastType = 'success' | 'error' | 'warning' | 'info' | 'loading';
@@ -76,14 +77,6 @@ export function ToastProvider({ children }: { children: ReactNode }) {
const dispatchToastId = 'background-dispatch';
const lastDispatchSummaryRef = useRef<string | null>(null);
const formatBytes = useCallback((bytes: number) => {
if (!Number.isFinite(bytes) || bytes < 0) return '0 B';
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}, []);
// Clean up all timeouts on unmount
useEffect(() => {
const timeouts = timeoutRefs.current;
@@ -502,7 +495,7 @@ export function ToastProvider({ children }: { children: ReactNode }) {
)}
{job.status === 'processing' && typeof job.uploadBytes === 'number' && typeof job.uploadTotalBytes === 'number' && job.uploadTotalBytes > 0 && (
<div className="text-[11px] text-bambu-gray truncate">
{formatBytes(job.uploadBytes)} / {formatBytes(job.uploadTotalBytes)}
{formatFileSize(job.uploadBytes)} / {formatFileSize(job.uploadTotalBytes)}
{typeof job.uploadProgressPct === 'number' ? ` (${job.uploadProgressPct.toFixed(1)}%)` : ''}
</div>
)}