v1.2: overwrite confirm, transfer speed/ETA, settings + themes, fused macOS titlebar
- Overwrite confirmation: name-collision detection before enqueue; dialog shows source vs destination (size, mtime, 'newer' badge) with per-item Skip/Replace and batch Skip all / Replace all; type mismatches auto-skip with a toast; cancel aborts the whole operation - Transfer speed + ETA: UI computes smoothed bytes/sec from progress deltas (callback bus makes deltas meaningful); shown in the expanded queue rows and the status-bar summary; unit follows the speed-unit setting - Settings: bun-side SettingsStore (settings.json, 0600, atomic) + get/save RPC; SettingsService; gear popover on the tab row (theme segmented control, speed unit); persisted across restarts - Light theme: data-theme on <html> drives token overrides; absent = follow OS - macOS fused titlebar: hiddenInset + trafficLightOffset, tab row becomes the draggable titlebar (app-region drag/no-drag classes); other platforms keep a separate titlebar (gated by a platform value injected via window preload) - README: known-issues note for the upstream Bun/Electrobun FFI crash Verified: tsc + ui build clean; SettingsStore roundtrip + 0600 headless; app boots and the UI fetches settings over RPC at startup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,25 @@ export function formatDate(epochMs: number): string {
|
||||
return DATE_FORMAT.format(new Date(epochMs));
|
||||
}
|
||||
|
||||
/** bytes/sec -> "14.2 MB/s" or "113.8 Mbps" depending on the configured unit. */
|
||||
export function formatSpeed(bytesPerSec: number, unit: 'MBps' | 'Mbps'): string {
|
||||
if (unit === 'Mbps') {
|
||||
return `${((bytesPerSec * 8) / 1_000_000).toFixed(1)} Mbps`;
|
||||
}
|
||||
return `${formatSize(bytesPerSec)}/s`;
|
||||
}
|
||||
|
||||
/** Seconds -> "0:42" / "3:07" / "1:02:15". */
|
||||
export function formatEta(seconds: number): string {
|
||||
if (!Number.isFinite(seconds) || seconds < 0) return '';
|
||||
const s = Math.round(seconds);
|
||||
const h = Math.floor(s / 3600);
|
||||
const m = Math.floor((s % 3600) / 60);
|
||||
const sec = s % 60;
|
||||
const mmss = `${m}:${String(sec).padStart(2, '0')}`;
|
||||
return h ? `${h}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}` : mmss;
|
||||
}
|
||||
|
||||
/** POSIX-style join for UI-composed paths (both panels use "/" on macOS + remote). */
|
||||
export function joinPath(dir: string, name: string): string {
|
||||
return dir.endsWith('/') ? dir + name : `${dir}/${name}`;
|
||||
|
||||
Reference in New Issue
Block a user