UXP v2.1.0: ui.js — add formatDuration, sanitizeFilename, slide panel helpers, escapeXml

This commit is contained in:
Zac Gaetano 2026-05-28 00:59:21 -04:00
parent 2608d7a465
commit 60d0b09c63

View file

@ -1,10 +1,10 @@
// Small DOM + state helpers used by the rest of the panel. UMD-style (attaches
// to window.UI) because UXP loads scripts as classic scripts, not modules.
// UI helpers — v2.1.0
(function () {
const UI = {};
UI.$ = function (sel) { return document.querySelector(sel); };
UI.$ = sel => document.querySelector(sel);
UI.$$ = sel => document.querySelectorAll(sel);
UI.setHidden = function (sel, hidden) {
const el = typeof sel === 'string' ? UI.$(sel) : sel;
@ -13,9 +13,8 @@
};
UI.showPane = function (name) {
// name in {connect, library}
UI.setHidden('#connect-pane', name !== 'connect');
UI.setHidden('#library-pane', name !== 'library');
UI.setHidden('#connect-pane', name !== 'connect');
UI.setHidden('#library-pane', name !== 'library');
};
UI.setStatus = function (sel, text, kind) {
@ -23,10 +22,10 @@
if (!el) return;
el.textContent = text || '';
el.classList.remove('error', 'muted');
if (kind === 'error') el.classList.add('error');
if (kind === 'muted') el.classList.add('muted');
if (kind) el.classList.add(kind);
};
// Toast — auto-hides after 6s
UI.toast = function (msg, kind) {
const el = UI.$('#toast');
if (!el) return;
@ -39,12 +38,26 @@
UI.showProgress = function (label, pct) {
UI.setHidden('#progress-row', false);
UI.$('#progress-label').textContent = label;
UI.$('#progress-fill').style.width = Math.max(0, Math.min(100, pct || 0)) + '%';
const lEl = UI.$('#progress-label');
if (lEl) lEl.textContent = label;
const fEl = UI.$('#progress-fill');
if (fEl) fEl.style.width = Math.max(0, Math.min(100, pct || 0)) + '%';
};
UI.hideProgress = function () { UI.setHidden('#progress-row', true); };
// Slide panels
UI.openSlide = function (overlayId, panelId) {
UI.setHidden('#' + overlayId, false);
UI.setHidden('#' + panelId, false);
};
UI.closeSlide = function (overlayId, panelId) {
UI.setHidden('#' + overlayId, true);
UI.setHidden('#' + panelId, true);
};
// Format helpers
UI.formatBytes = function (n) {
if (!Number.isFinite(n)) return '';
const u = ['B', 'KB', 'MB', 'GB', 'TB'];
@ -53,9 +66,55 @@
return n.toFixed(n < 10 && i > 0 ? 1 : 0) + ' ' + u[i];
};
UI.formatDuration = function (sec) {
if (!Number.isFinite(sec)) return '—';
const h = Math.floor(sec / 3600);
const m = Math.floor((sec % 3600) / 60);
const s = Math.floor(sec % 60);
return h > 0
? h + ':' + String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0')
: m + ':' + String(s).padStart(2, '0');
};
UI.sanitizeFilename = function (s) {
return String(s || 'asset').replace(/[\\/:*?"<>|]/g, '_').slice(0, 120);
};
UI.escapeHtml = function (s) {
return String(s || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
};
UI.escapeXml = function (s) {
return String(s || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&apos;');
};
// FCP timecode / frame rate helpers (used by timeline module)
UI.timecodeFromFrames = function (frames, fps) {
const r = Math.round(fps);
const f = frames % r;
const totalSec = Math.floor(frames / r);
const s = totalSec % 60;
const m = Math.floor(totalSec / 60) % 60;
const h = Math.floor(totalSec / 3600);
return h + ':' + String(m).padStart(2,'0') + ':' + String(s).padStart(2,'0') + ':' + String(f).padStart(2,'0');
};
UI.formatFrameRate = function (fps) {
// FCP XML uses rational notation: 1001/30000 for 29.97, 1/25 for 25, etc.
const known = {
23.976: '1001/24000', 24: '1/24', 25: '1/25',
29.97: '1001/30000', 30: '1/30', 50: '1/50',
59.94: '1001/60000', 60: '1/60',
};
// Find closest key
let best = '1001/30000';
let bestDist = Infinity;
for (const k in known) {
const d = Math.abs(Number(k) - fps);
if (d < bestDist) { bestDist = d; best = known[k]; }
}
return best;
};
window.UI = UI;
})();