feat(premiere-plugin-uxp): v2.0.0 — UXP port replacing CEP for import
CEP `csInterface.evalScript` callback is broken in Premiere Pro 26.0.x —
nothing called from the panel ever returns, so importFiles deadlocks. Adobe's
path forward is UXP. This is the minimum viable port that restores the
Import Proxy / Import Hi-Res workflow.
Scope (v2.0.0):
- Connect to a Dragonflight server (URL + Bearer token; persisted)
- Asset library (search, refresh, grid with thumbnails)
- Import Proxy via streamed download → Project.importFiles
- Import Hi-Res via presigned S3 URL → Project.importFiles
Layout:
manifest.json UXP v5, host=premierepro, minVersion=26.0.0
index.html Panel shell
styles.css Mirrors web UI dark tokens
src/ui.js DOM helpers, toast, progress, formatting
src/api.js HTTP client (Bearer; manual redirect-follow drops auth
when hopping to a different host per UXP security policy)
src/library.js Asset grid render + selection
src/import-flow.js Streaming download (fs.createWriteStream) +
premierepro.Project.importFiles into rootBin
src/main.js Bootstrap, event wiring
build/pack.mjs Packs into .ccx; installs via UnifiedPluginInstallerAgent
Coexists with services/premiere-plugin/ (CEP) — keeps the CEP panel for any
features that still work there while running v2.0.0 for import. Future v2.x
will add live preview, conform, timeline export, settings.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:19:28 -04:00
|
|
|
// Panel bootstrap. Wires DOM events to API / Library / Import handlers and
|
|
|
|
|
// restores the connection from localStorage on mount.
|
|
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
|
// Avoid running twice if UXP reloads the panel.
|
|
|
|
|
if (window.__df_uxp_started) return;
|
|
|
|
|
window.__df_uxp_started = true;
|
|
|
|
|
|
|
|
|
|
function syncConnectBtn() {
|
|
|
|
|
const u = UI.$('#server-url').value.trim();
|
|
|
|
|
const t = UI.$('#api-token').value.trim();
|
|
|
|
|
UI.$('#connect-btn').disabled = !u || !t;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function tryConnect(serverUrl, apiToken) {
|
|
|
|
|
UI.setStatus('#connect-status', 'Connecting…', 'muted');
|
|
|
|
|
try {
|
|
|
|
|
const me = await API.connect(serverUrl, apiToken);
|
2026-05-28 00:35:04 -04:00
|
|
|
// /auth/me returns the user fields directly (no `user:` wrapper).
|
|
|
|
|
const who = (me && (me.display_name || me.username)) || serverUrl;
|
|
|
|
|
UI.$('#connected-host').textContent = who + (who === serverUrl ? '' : ' @ ' + serverUrl);
|
feat(premiere-plugin-uxp): v2.0.0 — UXP port replacing CEP for import
CEP `csInterface.evalScript` callback is broken in Premiere Pro 26.0.x —
nothing called from the panel ever returns, so importFiles deadlocks. Adobe's
path forward is UXP. This is the minimum viable port that restores the
Import Proxy / Import Hi-Res workflow.
Scope (v2.0.0):
- Connect to a Dragonflight server (URL + Bearer token; persisted)
- Asset library (search, refresh, grid with thumbnails)
- Import Proxy via streamed download → Project.importFiles
- Import Hi-Res via presigned S3 URL → Project.importFiles
Layout:
manifest.json UXP v5, host=premierepro, minVersion=26.0.0
index.html Panel shell
styles.css Mirrors web UI dark tokens
src/ui.js DOM helpers, toast, progress, formatting
src/api.js HTTP client (Bearer; manual redirect-follow drops auth
when hopping to a different host per UXP security policy)
src/library.js Asset grid render + selection
src/import-flow.js Streaming download (fs.createWriteStream) +
premierepro.Project.importFiles into rootBin
src/main.js Bootstrap, event wiring
build/pack.mjs Packs into .ccx; installs via UnifiedPluginInstallerAgent
Coexists with services/premiere-plugin/ (CEP) — keeps the CEP panel for any
features that still work there while running v2.0.0 for import. Future v2.x
will add live preview, conform, timeline export, settings.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:19:28 -04:00
|
|
|
UI.setStatus('#connect-status', '', 'muted');
|
|
|
|
|
UI.showPane('library');
|
|
|
|
|
await Library.refresh('');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
API.state.connected = false;
|
|
|
|
|
UI.setStatus('#connect-status', 'Connect failed: ' + e.message, 'error');
|
|
|
|
|
UI.showPane('connect');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function wireConnectPane() {
|
|
|
|
|
UI.$('#server-url').value = API.state.serverUrl;
|
|
|
|
|
UI.$('#api-token').value = API.state.apiToken;
|
|
|
|
|
syncConnectBtn();
|
|
|
|
|
['input', 'change'].forEach(ev => {
|
|
|
|
|
UI.$('#server-url').addEventListener(ev, syncConnectBtn);
|
|
|
|
|
UI.$('#api-token').addEventListener(ev, syncConnectBtn);
|
|
|
|
|
});
|
|
|
|
|
UI.$('#connect-btn').addEventListener('click', async () => {
|
|
|
|
|
const u = UI.$('#server-url').value.trim();
|
|
|
|
|
const t = UI.$('#api-token').value.trim();
|
|
|
|
|
await tryConnect(u, t);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function wireLibraryPane() {
|
|
|
|
|
UI.$('#disconnect-btn').addEventListener('click', () => {
|
|
|
|
|
API.disconnect();
|
|
|
|
|
UI.$('#server-url').value = '';
|
|
|
|
|
UI.$('#api-token').value = '';
|
|
|
|
|
syncConnectBtn();
|
|
|
|
|
UI.showPane('connect');
|
|
|
|
|
UI.setStatus('#connect-status', 'Disconnected.', 'muted');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let searchTimer = null;
|
|
|
|
|
UI.$('#search-input').addEventListener('input', (e) => {
|
|
|
|
|
clearTimeout(searchTimer);
|
|
|
|
|
const q = e.target.value;
|
|
|
|
|
searchTimer = setTimeout(() => Library.refresh(q), 250);
|
|
|
|
|
});
|
|
|
|
|
UI.$('#refresh-btn').addEventListener('click', () => Library.refresh(UI.$('#search-input').value));
|
|
|
|
|
|
|
|
|
|
UI.$('#import-proxy-btn').addEventListener('click', async () => {
|
|
|
|
|
const a = Library.selectedAsset();
|
|
|
|
|
if (!a) return;
|
|
|
|
|
UI.$('#import-proxy-btn').disabled = true;
|
|
|
|
|
UI.$('#import-hires-btn').disabled = true;
|
|
|
|
|
try { await Import.proxy(a); }
|
|
|
|
|
catch (e) { UI.hideProgress(); UI.toast('Proxy import failed: ' + e.message, 'error'); }
|
|
|
|
|
finally { Library.syncActions(); }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
UI.$('#import-hires-btn').addEventListener('click', async () => {
|
|
|
|
|
const a = Library.selectedAsset();
|
|
|
|
|
if (!a) return;
|
|
|
|
|
UI.$('#import-proxy-btn').disabled = true;
|
|
|
|
|
UI.$('#import-hires-btn').disabled = true;
|
|
|
|
|
try { await Import.hires(a); }
|
|
|
|
|
catch (e) { UI.hideProgress(); UI.toast('Hi-res import failed: ' + e.message, 'error'); }
|
|
|
|
|
finally { Library.syncActions(); }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function init() {
|
|
|
|
|
wireConnectPane();
|
|
|
|
|
wireLibraryPane();
|
|
|
|
|
// If we have stored creds, try to reconnect silently. On failure fall
|
|
|
|
|
// back to the connect pane so the user can retype.
|
|
|
|
|
if (API.state.serverUrl && API.state.apiToken) {
|
|
|
|
|
tryConnect(API.state.serverUrl, API.state.apiToken);
|
|
|
|
|
} else {
|
|
|
|
|
UI.showPane('connect');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (document.readyState === 'loading') {
|
|
|
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
|
|
|
} else {
|
|
|
|
|
init();
|
|
|
|
|
}
|
|
|
|
|
})();
|