diff --git a/services/premiere-plugin-uxp/src/main.js b/services/premiere-plugin-uxp/src/main.js index df5ed89..f2da16c 100644 --- a/services/premiere-plugin-uxp/src/main.js +++ b/services/premiere-plugin-uxp/src/main.js @@ -1,12 +1,11 @@ -// Dragonflight UXP Panel — main.js v2.1.0 -// Bootstrap + event wiring for all UI panels. +// Dragonflight UXP Panel — main.js v2.1.1 (function () { if (window.__df_uxp_started) return; window.__df_uxp_started = true; - // ── Helpers ────────────────────────────────────────────────────── const $ = id => document.getElementById(id); + function syncConnectBtn() { $('connect-btn').disabled = !$('server-url').value.trim() || !$('api-token').value.trim(); } @@ -31,7 +30,7 @@ } } - // ── Connect pane wiring ────────────────────────────────────────── + // ── Connect pane ───────────────────────────────────────────────── function wireConnectPane() { $('server-url').value = API.state.serverUrl; $('api-token').value = API.state.apiToken; @@ -45,9 +44,8 @@ }); } - // ── Library pane wiring ────────────────────────────────────────── + // ── Library pane ───────────────────────────────────────────────── function wireLibraryPane() { - // Disconnect $('disconnect-btn').addEventListener('click', () => { API.disconnect(); Library.stopGrowingPoll(); @@ -58,11 +56,9 @@ UI.setStatus('#connect-status', 'Disconnected.', 'muted'); }); - // Tabs $('tab-library').addEventListener('click', () => Library.switchTab('library')); $('tab-growing').addEventListener('click', () => Library.switchTab('growing')); - // Search (debounced) let searchTimer; $('search-input').addEventListener('input', e => { clearTimeout(searchTimer); @@ -73,50 +69,53 @@ }, 280); }); - // Project filter $('project-filter').addEventListener('change', e => { Library.state.selectedProject = e.target.value; if (Library.state.currentTab === 'library') Library.refresh(Library.state.searchQuery); else Library._pollGrowing(); }); - // Refresh - $('refresh-btn').addEventListener('click', () => - Library.refresh($('search-input').value) - ); + $('refresh-btn').addEventListener('click', () => Library.refresh($('search-input').value)); - // Import proxy + // ── Import proxy ── $('import-proxy-btn').addEventListener('click', async () => { const a = Library.selectedAsset(); if (!a) return; _disableImportBtns(true); try { - await Import.proxy(a); - Library.recordImport(a._localPath, { assetId: a.id, displayName: a.display_name || a.filename }); - Library.recordImport('name:' + a._safeName, { assetId: a.id, displayName: a.display_name || a.filename }); + const { localPath, safeName } = await Import.proxy(a); + Library.recordImport(localPath, { assetId: a.id, displayName: a.display_name || a.filename }); + Library.recordImport('name:' + safeName, { assetId: a.id, displayName: a.display_name || a.filename }); } catch (e) { UI.hideProgress(); UI.toast('Proxy import failed: ' + e.message, 'error'); } finally { _disableImportBtns(false); Library._syncActions(); } }); - // Import hi-res + // ── Import hi-res ── $('import-hires-btn').addEventListener('click', async () => { const a = Library.selectedAsset(); if (!a) return; _disableImportBtns(true); - try { await Import.hires(a); } - catch (e) { UI.hideProgress(); UI.toast('Hi-res import failed: ' + e.message, 'error'); } + try { + const { localPath, safeName } = await Import.hires(a); + Library.recordImport(localPath, { assetId: a.id, displayName: a.display_name || a.filename }); + Library.recordImport('name:' + safeName, { assetId: a.id, displayName: a.display_name || a.filename }); + } catch (e) { UI.hideProgress(); UI.toast('Hi-res import failed: ' + e.message, 'error'); } finally { _disableImportBtns(false); Library._syncActions(); } }); - // Import all + // ── Import all ── $('import-all-btn').addEventListener('click', async () => { const assets = Library.state.assets; - if (!assets.length) { UI.toast('No assets to import', 'error'); return; } + if (!assets.length) { UI.toast('No assets', 'error'); return; } _disableImportBtns(true); let ok = 0, fail = 0; for (const a of assets) { - try { await Import.proxy(a); ok++; } - catch (_) { fail++; } + try { + const { localPath, safeName } = await Import.proxy(a); + Library.recordImport(localPath, { assetId: a.id, displayName: a.display_name || a.filename }); + Library.recordImport('name:' + safeName, { assetId: a.id, displayName: a.display_name || a.filename }); + ok++; + } catch (_) { fail++; } } _disableImportBtns(false); UI.hideProgress(); @@ -124,7 +123,7 @@ Library._syncActions(); }); - // Mount live + // ── Mount live ── $('mount-live-btn').addEventListener('click', async () => { const a = Library.selectedAsset(); if (!a) return; @@ -132,129 +131,119 @@ UI.showProgress('Resolving live path…', 10); try { const info = await API.getLivePath(a.id); - // UXP runs on Windows PPro → use win_path const hostPath = info.win_path || info.posix_path; UI.showProgress('Importing live file…', 50); await Import.importIntoProject(hostPath); Library.recordImport('live:' + a.id, { assetId: a.id, displayName: info.display_name, livePath: hostPath }); Library.startLiveStatusPoll(a.id); UI.hideProgress(); - UI.toast('Mounted live: ' + info.display_name, 'ok'); + UI.toast('Mounted live: ' + (info.display_name || a.id), 'ok'); } catch (e) { UI.hideProgress(); UI.toast('Mount live failed: ' + e.message, 'error'); - } finally { - Library._syncActions(); - } + } finally { Library._syncActions(); } }); - // Relink single asset + // ── Relink single asset ── $('relink-btn').addEventListener('click', async () => { const a = Library.selectedAsset(); if (!a) return; const entry = Library.getImport('live:' + a.id); - if (!entry) { UI.toast('No live mount recorded for this asset', 'error'); return; } + if (!entry) { UI.toast('No live mount recorded', 'error'); return; } $('relink-btn').disabled = true; UI.showProgress('Fetching hi-res…', 10); try { - const info = await API.getHiresInfo(a.id); + const info = await API.getHiresInfo(a.id); const safeName = UI.sanitizeFilename(info.filename || (a.display_name || a.id) + '.' + (info.ext || 'mxf')); - const dest = await Import._tempPath(safeName); + const dest = await Import._tempPath(safeName); UI.showProgress('Downloading ' + safeName + '…', 20); const r = await API.requestFollow(info.url, {}); if (!r.ok) throw new Error('Download HTTP ' + r.status); await Import._streamToFile(r, dest, ({ received, total }) => { const pct = total ? 20 + (received / total) * 60 : 20; - UI.showProgress('Downloading ' + UI.formatBytes(received) + '…', pct); + UI.showProgress(UI.formatBytes(received) + '…', pct); }); UI.showProgress('Relinking…', 85); const P = require('premierepro'); - const project = await P.Project.getActiveProject(); - if (!project) throw new Error('No active project'); - await Timeline._relinkInProject(project, entry.livePath, dest); + const proj = await P.Project.getActiveProject(); + if (!proj) throw new Error('No active project'); + await Timeline._relinkInProject(proj, entry.livePath, dest); Library.recordImport(dest, { assetId: a.id, displayName: a.display_name || a.filename }); UI.hideProgress(); - UI.toast('Relinked to hi-res: ' + safeName, 'ok'); + UI.toast('Relinked: ' + safeName, 'ok'); } catch (e) { UI.hideProgress(); UI.toast('Relink failed: ' + e.message, 'error'); - } finally { - Library._syncActions(); - } + } finally { Library._syncActions(); } }); - // Export timeline $('export-timeline-btn').addEventListener('click', openExportPanel); - - // Advanced $('export-conform-btn').addEventListener('click', openConformPanel); $('fetch-relink-btn').addEventListener('click', openRelinkPanel); } function _disableImportBtns(dis) { - $('import-proxy-btn').disabled = dis; - $('import-hires-btn').disabled = dis; + ['import-proxy-btn','import-hires-btn'].forEach(id => { $(id).disabled = dis; }); } // ── Export timeline panel ───────────────────────────────────────── - let _timelineCache = null; + let _seqCache = null; async function openExportPanel() { UI.showProgress('Reading Premiere sequence…', 20); - try { - _timelineCache = await Timeline.readActiveSequence(); - } catch (e) { - UI.hideProgress(); UI.toast('Timeline read failed: ' + e.message, 'error'); return; - } - if (!_timelineCache.clips.length) { - UI.hideProgress(); UI.toast('No clips in active sequence', 'error'); return; - } + try { _seqCache = await Timeline.readActiveSequence(); } + catch (e) { UI.hideProgress(); UI.toast('Timeline read failed: ' + e.message, 'error'); return; } + if (!_seqCache.clips.length) { UI.hideProgress(); UI.toast('No clips in active sequence', 'error'); return; } UI.hideProgress(); - const resolved = Library.resolveClipsToAssets(_timelineCache.clips); + const resolved = Library.resolveClipsToAssets(_seqCache.clips); const matched = resolved.filter(c => c.asset_id).length; - $('export-seq-name').value = _timelineCache.sequenceName || 'Sequence 1'; - $('export-clip-info').textContent = matched + ' of ' + _timelineCache.clips.length + ' clip(s) matched to MAM assets'; + $('export-seq-name').value = _seqCache.sequenceName || 'Sequence 1'; + $('export-clip-info').textContent = matched + ' of ' + _seqCache.clips.length + ' clip(s) matched to MAM assets'; UI.openSlide('export-overlay', 'export-panel'); } function wireExportPanel() { $('export-close-btn').addEventListener('click', () => UI.closeSlide('export-overlay', 'export-panel')); $('export-cancel-btn').addEventListener('click', () => UI.closeSlide('export-overlay', 'export-panel')); + $('export-overlay').addEventListener('click', () => UI.closeSlide('export-overlay', 'export-panel')); + $('export-confirm-btn').addEventListener('click', async () => { - const seqName = $('export-seq-name').value.trim() || 'Sequence 1'; + if (!_seqCache) return; + const seqName = ($('export-seq-name').value || '').trim() || 'Sequence 1'; const projectId = $('export-proj-select').value; if (!projectId) { UI.toast('Select a target project', 'error'); return; } UI.closeSlide('export-overlay', 'export-panel'); UI.showProgress('Pushing timeline…', 20); try { - const { matched, skipped } = await Timeline.pushToMAM(seqName, projectId, _timelineCache); + const { matched, skipped } = await Timeline.pushToMAM(seqName, projectId, _seqCache); UI.hideProgress(); UI.toast('Pushed ' + matched + ' clip(s)' + (skipped ? ' (' + skipped + ' skipped)' : ''), 'ok'); - } catch (e) { - UI.hideProgress(); - UI.toast('Export failed: ' + e.message, 'error'); - } + } catch (e) { UI.hideProgress(); UI.toast('Export failed: ' + e.message, 'error'); } }); - $('export-overlay').addEventListener('click', () => UI.closeSlide('export-overlay', 'export-panel')); } // ── Conform panel ───────────────────────────────────────────────── async function openConformPanel() { UI.showProgress('Reading Premiere sequence…', 20); - try { - _timelineCache = await Timeline.readActiveSequence(); - } catch (e) { - UI.hideProgress(); UI.toast('Timeline read failed: ' + e.message, 'error'); return; - } - if (!_timelineCache.clips.length) { - UI.hideProgress(); UI.toast('No clips in active sequence', 'error'); return; - } + try { _seqCache = await Timeline.readActiveSequence(); } + catch (e) { UI.hideProgress(); UI.toast('Timeline read failed: ' + e.message, 'error'); return; } + if (!_seqCache.clips.length) { UI.hideProgress(); UI.toast('No clips in active sequence', 'error'); return; } UI.hideProgress(); - const resolved = Library.resolveClipsToAssets(_timelineCache.clips); + const resolved = Library.resolveClipsToAssets(_seqCache.clips); const matched = resolved.filter(c => c.asset_id).length; - $('conform-clip-info').textContent = matched + ' of ' + _timelineCache.clips.length + ' clip(s) matched'; + $('conform-clip-info').textContent = matched + ' of ' + _seqCache.clips.length + ' clip(s) matched'; $('conform-start-btn').disabled = matched === 0; + // Sync conform project select from loaded projects + const conformProj = $('conform-proj-select'); + if (conformProj) { + conformProj.innerHTML = ''; + Library.state.projects.forEach(p => { + const o = document.createElement('option'); + o.value = p.id; o.textContent = p.name; + conformProj.appendChild(o); + }); + } UI.openSlide('conform-overlay', 'conform-panel'); } @@ -263,43 +252,41 @@ $('conform-cancel-btn').addEventListener('click', () => UI.closeSlide('conform-overlay', 'conform-panel')); $('conform-overlay').addEventListener('click', () => UI.closeSlide('conform-overlay', 'conform-panel')); - // Preset cards $('preset-cards').addEventListener('click', e => { const card = e.target.closest('.preset-card'); if (!card) return; document.querySelectorAll('.preset-card').forEach(c => c.classList.remove('selected')); card.classList.add('selected'); - const p = card.dataset.preset; const presets = { - broadcast: { codec:'prores_hq', quality:'high', resolution:'1080p', audio:'broadcast' }, - web: { codec:'h264', quality:'medium', resolution:'1080p', audio:'web' }, - archive: { codec:'prores_4444', quality:'high', resolution:'uhd', audio:'archive' }, + broadcast: { codec:'prores_hq', quality:'high', resolution:'1080p', audio:'broadcast' }, + web: { codec:'h264', quality:'medium', resolution:'1080p', audio:'web' }, + archive: { codec:'prores_4444', quality:'high', resolution:'uhd', audio:'archive' }, }; - if (presets[p]) { - $('conform-codec').value = presets[p].codec; - $('conform-quality').value = presets[p].quality; - $('conform-resolution').value = presets[p].resolution; - $('conform-audio').value = presets[p].audio; + const p = presets[card.dataset.preset]; + if (p) { + $('conform-codec').value = p.codec; + $('conform-quality').value = p.quality; + $('conform-resolution').value = p.resolution; + $('conform-audio').value = p.audio; } }); $('conform-start-btn').addEventListener('click', async () => { - // Need a target project — use first available or prompt user - const projectId = $('export-proj-select').value; - if (!projectId) { - UI.toast('Select a project via Export Timeline first', 'error'); return; - } + if (!_seqCache) return; + const conformProj = $('conform-proj-select'); + const projectId = conformProj ? conformProj.value : ''; + if (!projectId) { UI.toast('Select a target project', 'error'); return; } UI.closeSlide('conform-overlay', 'conform-panel'); - UI.showProgress('Starting conform job…', 20); + UI.showProgress('Starting conform job…', 15); try { - const jobId = await Timeline.startConform(projectId, _timelineCache.sequenceName, _timelineCache, { + const jobId = await Timeline.startConform(projectId, _seqCache.sequenceName, _seqCache, { codec: $('conform-codec').value, quality: $('conform-quality').value, resolution: $('conform-resolution').value, audio: $('conform-audio').value, }); Timeline.pollConform(jobId, - (pct, status) => UI.showProgress('Conform: ' + status + ' (' + pct + '%)…', 20 + pct * 0.78), + (pct, status) => UI.showProgress('Conform: ' + status + ' (' + pct + '%)…', 15 + pct * 0.8), job => { UI.hideProgress(); if (job.status === 'completed') { @@ -310,10 +297,7 @@ } } ); - } catch (e) { - UI.hideProgress(); - UI.toast('Conform failed: ' + e.message, 'error'); - } + } catch (e) { UI.hideProgress(); UI.toast('Conform failed: ' + e.message, 'error'); } }); } @@ -322,14 +306,11 @@ async function openRelinkPanel() { UI.showProgress('Reading Premiere sequence…', 20); - try { - _timelineCache = await Timeline.readActiveSequence(); - } catch (e) { - UI.hideProgress(); UI.toast('Timeline read failed: ' + e.message, 'error'); return; - } + try { _seqCache = await Timeline.readActiveSequence(); } + catch (e) { UI.hideProgress(); UI.toast('Timeline read failed: ' + e.message, 'error'); return; } UI.hideProgress(); - const resolved = Library.resolveClipsToAssets(_timelineCache.clips); + const resolved = Library.resolveClipsToAssets(_seqCache.clips); _relinkClips = resolved; const list = $('clip-list'); @@ -346,8 +327,7 @@ row.className = 'clip-item' + (matched ? ' matched' : ' unmatched'); const cb = document.createElement('input'); cb.type = 'checkbox'; cb.id = 'rclip-' + i; - cb.disabled = !matched; - cb.checked = matched; + cb.disabled = !matched; cb.checked = matched; cb.addEventListener('change', _syncRelinkStart); const lbl = document.createElement('label'); lbl.htmlFor = 'rclip-' + i; @@ -366,8 +346,8 @@ } function _syncRelinkStart() { - const anyChecked = !!document.querySelector('#clip-list input[type="checkbox"]:checked'); - $('relink-start-btn').disabled = !anyChecked; + const any = !!document.querySelector('#clip-list input[type="checkbox"]:checked'); + $('relink-start-btn').disabled = !any; } function wireRelinkPanel() { @@ -376,28 +356,21 @@ $('relink-overlay').addEventListener('click', () => UI.closeSlide('relink-overlay', 'relink-panel')); $('relink-start-btn').addEventListener('click', async () => { - const checkboxes = document.querySelectorAll('#clip-list input[type="checkbox"]:checked'); - const indices = []; - checkboxes.forEach(cb => indices.push(parseInt(cb.id.replace('rclip-', ''), 10))); - const selected = indices.map(i => _relinkClips[i]).filter(c => c && c.asset_id); + const checked = document.querySelectorAll('#clip-list input[type="checkbox"]:checked'); + const selected = []; + checked.forEach(cb => { + const i = parseInt(cb.id.replace('rclip-',''), 10); + if (_relinkClips[i] && _relinkClips[i].asset_id) selected.push(_relinkClips[i]); + }); if (!selected.length) return; - UI.closeSlide('relink-overlay', 'relink-panel'); UI.showProgress('Starting batch relink…', 5); try { - const results = await Timeline.batchRelink(selected); + const res = await Timeline.batchRelink(selected); UI.hideProgress(); - const msg = results.succeeded + ' clip(s) relinked' + - (results.failed ? ', ' + results.failed + ' failed' : ''); - UI.toast(msg, results.failed ? 'error' : 'ok'); - - // Show summary in panel - $('relink-summary').textContent = msg; - $('relink-summary').classList.remove('hidden'); - } catch (e) { - UI.hideProgress(); - UI.toast('Batch relink failed: ' + e.message, 'error'); - } + const msg = res.succeeded + ' clip(s) relinked' + (res.failed ? ', ' + res.failed + ' failed' : ''); + UI.toast(msg, res.failed ? 'error' : 'ok'); + } catch (e) { UI.hideProgress(); UI.toast('Batch relink failed: ' + e.message, 'error'); } }); } @@ -418,5 +391,4 @@ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init(); - })();