External editor via cmd/start + exe picker; Permissions hidden for Windows-local (chmod only flips read-only there); Explorer/Recycle Bin wording; Ctrl vs Cmd shortcut labels; separator-aware path joins + drive-root detection; drives list in the panel source picker; icon.ico + colored tray icon; icacls ACL on connections.json; README Windows (beta) section with upstream known issues and a smoke-test checklist. Runtime paths pending first boot on a real Windows machine. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 lines
2.1 KiB
TypeScript
68 lines
2.1 KiB
TypeScript
const SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
|
|
export function formatSize(bytes: number): string {
|
|
let value = bytes;
|
|
let unit = 0;
|
|
while (value >= 1024 && unit < SIZE_UNITS.length - 1) {
|
|
value /= 1024;
|
|
unit++;
|
|
}
|
|
return `${unit === 0 ? value : value.toFixed(1)} ${SIZE_UNITS[unit]}`;
|
|
}
|
|
|
|
const DATE_FORMAT = new Intl.DateTimeFormat(undefined, {
|
|
year: '2-digit',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
});
|
|
|
|
export function formatDate(epochMs: number): string {
|
|
if (!epochMs) return '';
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Separator-aware join for UI-composed paths: remote and POSIX-local dirs use
|
|
* "/", a Windows-local dir ("C:\Users\x") keeps "\" so paths stay consistent.
|
|
*/
|
|
export function joinPath(dir: string, name: string): string {
|
|
const sep = dir.includes('\\') && !dir.includes('/') ? '\\' : '/';
|
|
return dir.endsWith(sep) ? dir + name : `${dir}${sep}${name}`;
|
|
}
|
|
|
|
/** "/" (POSIX) or a drive root like "C:\" — nothing above to navigate to. */
|
|
export function isRootPath(p: string): boolean {
|
|
return p === '/' || /^[A-Za-z]:[\\/]?$/.test(p);
|
|
}
|
|
|
|
/** 0o755 -> "rwxr-xr-x" */
|
|
export function formatMode(mode: number): string {
|
|
const flags = 'rwxrwxrwx';
|
|
let out = '';
|
|
for (let i = 0; i < 9; i++) {
|
|
out += mode & (0o400 >> i) ? flags[i] : '-';
|
|
}
|
|
return out;
|
|
}
|