Naut-Plugin-WebUI/public/js/format.js
ookami125 bc1be49a37 Initial commit: NAUT torrent web UI with stubbed server
Advanced torrent client web UI aimed at power users, with a
zero-dependency Node stub server (built-in http + SSE) serving live
mock data.

- Dense sortable/multi-select torrent grid with live updates
- Detail panel: General/Trackers/Peers/Content/Pieces (resizable)
- Sidebar filters: status, categories, tags, trackers
- Create/delete categories and tags (sidebar + right-click)
- Add via magnet or client-side-parsed .torrent upload
- RSS, integrated search, and read-only engine views
- Hand-drawn SVG icon set, dark theme, keyboard shortcuts

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 21:15:31 -04:00

87 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Display formatters — units, durations, dates, states.
export function bytes(n, dp = 1) {
if (n == null || isNaN(n)) return '';
if (n === 0) return '0 B';
const u = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
const i = Math.min(u.length - 1, Math.floor(Math.log(n) / Math.log(1024)));
return `${(n / 1024 ** i).toFixed(i === 0 ? 0 : dp)} ${u[i]}`;
}
export function rate(n) {
if (!n) return '';
return `${bytes(n)}/s`;
}
export function eta(sec) {
if (sec == null || sec >= 8640000 || sec < 0) return '∞';
if (sec < 1) return '0s';
const d = Math.floor(sec / 86400);
const h = Math.floor((sec % 86400) / 3600);
const m = Math.floor((sec % 3600) / 60);
const s = Math.floor(sec % 60);
if (d) return `${d}d ${h}h`;
if (h) return `${h}h ${m}m`;
if (m) return `${m}m ${s}s`;
return `${s}s`;
}
export function duration(sec) {
if (sec == null || sec < 0) return '';
const d = Math.floor(sec / 86400);
const h = Math.floor((sec % 86400) / 3600);
const m = Math.floor((sec % 3600) / 60);
if (d) return `${d}d ${h}h ${m}m`;
if (h) return `${h}h ${m}m`;
return `${m}m`;
}
export function pct(p) { return `${(p * 100).toFixed(1)}%`; }
export function ratio(r) {
if (r == null) return '';
if (r >= 9999) return '∞';
return r.toFixed(2);
}
export function date(ms) {
if (!ms || ms < 0) return '';
const d = new Date(ms);
return d.toLocaleString(undefined, { year: '2-digit', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' });
}
export function ago(ms) {
if (!ms || ms < 0) return '';
const s = (Date.now() - ms) / 1000;
if (s < 60) return 'just now';
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
return `${Math.floor(s / 86400)}d ago`;
}
const STATE_LABEL = {
downloading: 'Downloading', forcedDL: '[F] Downloading', metaDL: 'Fetching metadata',
stalledDL: 'Stalled (DL)', uploading: 'Seeding', forcedUP: '[F] Seeding',
stalledUP: 'Seeding (idle)', pausedDL: 'Paused', pausedUP: 'Completed',
checkingDL: 'Checking', checkingUP: 'Checking', moving: 'Moving',
queuedDL: 'Queued', queuedUP: 'Queued', error: 'Error', missingFiles: 'Missing files',
};
export function stateLabel(s) { return STATE_LABEL[s] || s; }
const STATE_COLOR = {
downloading: 'var(--dl)', forcedDL: 'var(--dl)', metaDL: 'var(--dl)',
uploading: 'var(--up)', forcedUP: 'var(--up)',
stalledDL: 'var(--txt-dim)', stalledUP: 'var(--txt-dim)',
pausedDL: 'var(--pause)', pausedUP: 'var(--pause)',
checkingDL: 'var(--check)', checkingUP: 'var(--check)', moving: 'var(--check)',
queuedDL: 'var(--queue)', queuedUP: 'var(--queue)',
error: 'var(--err)', missingFiles: 'var(--err)',
};
export function stateColor(s) { return STATE_COLOR[s] || 'var(--txt-dim)'; }
const PRIO_LABEL = { 0: 'Do not download', 1: 'Normal', 6: 'High', 7: 'Maximum' };
export function filePrio(p) { return PRIO_LABEL[p] ?? 'Normal'; }
export function esc(s) {
return String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
}