From 60d0b09c6321aa6fb24446c9d907ebbad0960b33 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Thu, 28 May 2026 00:59:21 -0400 Subject: [PATCH] =?UTF-8?q?UXP=20v2.1.0:=20ui.js=20=E2=80=94=20add=20forma?= =?UTF-8?q?tDuration,=20sanitizeFilename,=20slide=20panel=20helpers,=20esc?= =?UTF-8?q?apeXml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- services/premiere-plugin-uxp/src/ui.js | 79 ++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/services/premiere-plugin-uxp/src/ui.js b/services/premiere-plugin-uxp/src/ui.js index bd1ff03..b557a8d 100644 --- a/services/premiere-plugin-uxp/src/ui.js +++ b/services/premiere-plugin-uxp/src/ui.js @@ -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,'&').replace(//g,'>').replace(/"/g,'"'); + }; + + UI.escapeXml = function (s) { + return String(s || '').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); + }; + + // 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; })();