dragonflight/services/premiere-plugin-uxp/src/library.js

110 lines
3.6 KiB
JavaScript
Raw Normal View History

// Asset library rendering. Calls API.listAssets, renders a grid into
// #asset-grid, tracks the selected asset for the action buttons.
(function () {
const Library = {};
Library.state = { assets: [], selectedId: null };
Library.render = function () {
const grid = UI.$('#asset-grid');
grid.innerHTML = '';
if (!Library.state.assets.length) {
const e = document.createElement('div');
e.className = 'empty muted';
e.textContent = 'No assets';
grid.appendChild(e);
return;
}
for (const a of Library.state.assets) {
grid.appendChild(makeCard(a));
}
Library.syncActions();
};
function makeCard(asset) {
const card = document.createElement('div');
card.className = 'asset-card';
if (asset.id === Library.state.selectedId) card.classList.add('selected');
card.dataset.assetId = asset.id;
const thumbKey = asset.thumbnail_s3_key || asset.thumbnail || null;
const thumbUrl = thumbKey
? `${API.state.serverUrl}/api/v1/assets/${asset.id}/thumbnail?redirect=1`
: null;
if (thumbUrl) {
const img = document.createElement('img');
img.className = 'asset-thumb';
img.alt = asset.display_name || asset.filename || asset.id;
// UXP fetch supports cross-origin images; the redirect=1 query on
// /thumbnail tells the server to 302 to the presigned S3 URL.
img.src = thumbUrl;
img.onerror = () => { img.replaceWith(placeholder()); };
card.appendChild(img);
} else {
card.appendChild(placeholder());
}
const name = document.createElement('div');
name.className = 'asset-name';
name.textContent = asset.display_name || asset.filename || asset.id;
card.appendChild(name);
card.addEventListener('click', () => Library.select(asset.id));
return card;
}
function placeholder() {
const p = document.createElement('div');
p.className = 'asset-thumb-placeholder';
p.textContent = 'no preview';
return p;
}
Library.select = function (id) {
Library.state.selectedId = id;
Library.render();
};
Library.selectedAsset = function () {
return Library.state.assets.find(a => a.id === Library.state.selectedId) || null;
};
Library.syncActions = function () {
const sel = Library.selectedAsset();
const info = UI.$('#selected-info');
if (sel) {
info.textContent = (sel.display_name || sel.filename || sel.id)
+ (sel.file_size ? ' · ' + UI.formatBytes(Number(sel.file_size)) : '');
info.classList.remove('muted');
} else {
info.textContent = 'No asset selected';
info.classList.add('muted');
}
UI.$('#import-proxy-btn').disabled = !sel;
UI.$('#import-hires-btn').disabled = !sel || !sel.original_s3_key;
};
Library.refresh = async function (query) {
const grid = UI.$('#asset-grid');
grid.innerHTML = '<div class="empty muted">Loading…</div>';
try {
const data = await API.listAssets(query);
Library.state.assets = (data && (data.assets || data.rows)) || [];
Library.render();
// Surface count via toast so we can tell empty-grid (zero) from
// CSS-broken-grid (cards exist but invisible) at a glance.
if (UI.toast) UI.toast('Loaded ' + Library.state.assets.length + ' assets (total ' + (data.total || '?') + ')', 'ok');
} catch (e) {
grid.innerHTML = '';
const err = document.createElement('div');
err.className = 'empty muted';
err.textContent = 'Error loading assets: ' + e.message;
grid.appendChild(err);
if (UI.toast) UI.toast('Asset load failed: ' + e.message, 'error');
}
};
window.Library = Library;
})();