85 lines
4.6 KiB
TypeScript
85 lines
4.6 KiB
TypeScript
|
|
// Z-AMPP MAM bridge. On boot, reads ?asset=<uuid> from URL and imports it.
|
||
|
|
// Also exports pickFromMAM() for an in-app picker.
|
||
|
|
|
||
|
|
type MAMAsset = { id: string; display_name?: string; filename?: string; };
|
||
|
|
|
||
|
|
async function getStreamURL(assetId: string): Promise<string> {
|
||
|
|
const r = await fetch('/api/v1/assets/' + assetId + '/stream', { credentials: 'omit' });
|
||
|
|
if (!r.ok) throw new Error('stream lookup failed: ' + r.status);
|
||
|
|
const j = await r.json();
|
||
|
|
if (!j || !j.url) throw new Error('stream lookup: no url');
|
||
|
|
return j.url;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function listAssets(limit = 200): Promise<MAMAsset[]> {
|
||
|
|
const r = await fetch('/api/v1/assets?limit=' + limit, { credentials: 'omit' });
|
||
|
|
if (!r.ok) throw new Error('list assets failed: ' + r.status);
|
||
|
|
const j = await r.json();
|
||
|
|
return (j && j.assets) || [];
|
||
|
|
}
|
||
|
|
|
||
|
|
function getMediaBridge(): any {
|
||
|
|
const w = window as any;
|
||
|
|
return w.__mediaBridge || w.mediaBridge || w.MediaBridge || (w.editor && w.editor.mediaBridge) || null;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function importAsset(assetId: string, name?: string): Promise<void> {
|
||
|
|
const url = await getStreamURL(assetId);
|
||
|
|
let bridge = getMediaBridge();
|
||
|
|
if (!bridge || typeof bridge.importFromURL !== 'function') {
|
||
|
|
await new Promise(r => setTimeout(r, 500));
|
||
|
|
bridge = getMediaBridge();
|
||
|
|
}
|
||
|
|
if (!bridge || typeof bridge.importFromURL !== 'function') {
|
||
|
|
throw new Error('MediaBridge.importFromURL not available');
|
||
|
|
}
|
||
|
|
return bridge.importFromURL(url, name || (assetId + '.mp4'));
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function pickFromMAM(): Promise<void> {
|
||
|
|
const assets = await listAssets(200);
|
||
|
|
const overlay = document.createElement('div');
|
||
|
|
Object.assign(overlay.style, { position:'fixed',inset:'0',background:'rgba(0,0,0,0.72)',zIndex:'999999',display:'flex',alignItems:'center',justifyContent:'center' } as CSSStyleDeclaration);
|
||
|
|
const modal = document.createElement('div');
|
||
|
|
Object.assign(modal.style, { width:'min(960px,92vw)',maxHeight:'84vh',background:'#161618',color:'#e8e8ea',borderRadius:'12px',padding:'16px',display:'flex',flexDirection:'column',gap:'12px',overflow:'hidden' } as CSSStyleDeclaration);
|
||
|
|
modal.innerHTML = '<div style="display:flex;justify-content:space-between;align-items:center"><strong>Import from Z-AMPP MAM</strong><button data-close style="background:#2a2a2e;color:#fff;border:0;border-radius:6px;padding:6px 10px;cursor:pointer">Close</button></div>';
|
||
|
|
const grid = document.createElement('div');
|
||
|
|
Object.assign(grid.style, { display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(180px,1fr))',gap:'10px',overflowY:'auto',padding:'4px' } as CSSStyleDeclaration);
|
||
|
|
for (const a of assets) {
|
||
|
|
const card = document.createElement('button');
|
||
|
|
Object.assign(card.style, { background:'#1f1f23',border:'1px solid #2c2c32',borderRadius:'8px',padding:'6px',color:'#e8e8ea',cursor:'pointer',display:'flex',flexDirection:'column',gap:'6px',textAlign:'left' } as CSSStyleDeclaration);
|
||
|
|
const label = a.display_name || a.filename || a.id;
|
||
|
|
card.innerHTML = '<img loading="lazy" src="/api/v1/assets/' + a.id + '/thumbnail" style="width:100%;aspect-ratio:16/9;object-fit:cover;background:#000;border-radius:4px" onerror="this.style.opacity=0.2"/><span style="font-size:12px;line-height:1.2;word-break:break-word">' + label + '</span>';
|
||
|
|
card.addEventListener('click', async () => { card.disabled = true; card.style.opacity='0.5'; try { await importAsset(a.id, label); } catch(e){ alert('Import failed: '+(e as Error).message); } finally { card.disabled=false; card.style.opacity='1'; } });
|
||
|
|
grid.appendChild(card);
|
||
|
|
}
|
||
|
|
modal.appendChild(grid);
|
||
|
|
overlay.appendChild(modal);
|
||
|
|
document.body.appendChild(overlay);
|
||
|
|
const close = () => overlay.remove();
|
||
|
|
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
|
||
|
|
modal.querySelector('[data-close]')?.addEventListener('click', close);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function initMAMBridge(): void {
|
||
|
|
try {
|
||
|
|
const qs = new URLSearchParams(window.location.search);
|
||
|
|
const projectId = qs.get('project');
|
||
|
|
if (projectId) { try { window.localStorage.setItem('mamProjectId', projectId); } catch {} }
|
||
|
|
(window as any).__zamppPickFromMAM = pickFromMAM;
|
||
|
|
const assetId = qs.get('asset');
|
||
|
|
if (!assetId) return;
|
||
|
|
const tryImport = async (n = 0): Promise<void> => {
|
||
|
|
try { await importAsset(assetId); console.log('[mam-bridge] imported', assetId); }
|
||
|
|
catch (e) {
|
||
|
|
if (n < 10) setTimeout(() => tryImport(n + 1), 500);
|
||
|
|
else console.error('[mam-bridge] import retries exhausted', e);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
if (document.readyState === 'complete') tryImport();
|
||
|
|
else window.addEventListener('load', () => tryImport());
|
||
|
|
} catch (e) { console.error('[mam-bridge] init failed', e); }
|
||
|
|
}
|
||
|
|
|
||
|
|
initMAMBridge();
|