fix(editor): asset auto-import now lands cleanly into the media bin
Three problems were blocking the round-trip. Each fixed. * MediaBridge.importFromURL went through the file-import service but not the Zustand store, so the media bin stayed empty. mam-bridge now calls window.__projectStore.getState().importMedia(file) which is what the actual UI uses. project-store.ts exposes useProjectStore on window for that hook. * rustfs serves the proxy with content-type application/octet-stream; the editor rejects with DECODE_ERROR on that mime. Bridge now forces video/mp4 (or audio/wav, video/webm, etc.) based on the asset filename. * The Recover Your Work modal and the Welcome tour blocked editor initialization. Bridge now auto-clicks Start Fresh and Skip Tour (alongside the format chooser), and waits 1.5s of project-id stability before calling importMedia so it does not get clobbered by the project-replacement cycle. One-shot guard prevents duplicate imports.
This commit is contained in:
parent
b68f0c6aba
commit
e390f0efab
3 changed files with 50 additions and 11 deletions
|
|
@ -485,6 +485,7 @@ let mediaBridgeInstance: MediaBridge | null = null;
|
|||
export function getMediaBridge(): MediaBridge {
|
||||
if (!mediaBridgeInstance) {
|
||||
mediaBridgeInstance = new MediaBridge();
|
||||
(window as any).__mediaBridge = mediaBridgeInstance;
|
||||
}
|
||||
return mediaBridgeInstance;
|
||||
}
|
||||
|
|
@ -493,6 +494,7 @@ export function getMediaBridge(): MediaBridge {
|
|||
* Initialize the shared MediaBridge
|
||||
*/
|
||||
export async function initializeMediaBridge(): Promise<MediaBridge> {
|
||||
// Z-AMPP: expose for mam-bridge.ts
|
||||
const bridge = getMediaBridge();
|
||||
await bridge.initialize();
|
||||
return bridge;
|
||||
|
|
|
|||
|
|
@ -25,15 +25,18 @@ function getMediaBridge(): any {
|
|||
|
||||
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();
|
||||
const safeName = (name || (assetId + '.mp4')).replace(/[^\w.\-]+/g, '_');
|
||||
const store: any = (window as any).__projectStore;
|
||||
if (store && typeof store.getState === 'function' && typeof store.getState().importMedia === 'function') {
|
||||
const res = await fetch(url, { credentials: 'omit' });
|
||||
if (!res.ok) throw new Error('fetch failed: ' + res.status);
|
||||
const blob = await res.blob();
|
||||
const inferred = (safeName.toLowerCase().endsWith('.webm') ? 'video/webm' : safeName.toLowerCase().endsWith('.mov') ? 'video/quicktime' : safeName.toLowerCase().endsWith('.mp3') ? 'audio/mpeg' : safeName.toLowerCase().endsWith('.wav') ? 'audio/wav' : 'video/mp4'); const ct = (blob.type && blob.type !== 'application/octet-stream') ? blob.type : inferred; const file = new File([blob], safeName, { type: ct });
|
||||
return store.getState().importMedia(file);
|
||||
}
|
||||
if (!bridge || typeof bridge.importFromURL !== 'function') {
|
||||
throw new Error('MediaBridge.importFromURL not available');
|
||||
}
|
||||
return bridge.importFromURL(url, name || (assetId + '.mp4'));
|
||||
const bridge = getMediaBridge();
|
||||
if (bridge && typeof bridge.importFromURL === 'function') return bridge.importFromURL(url, safeName);
|
||||
throw new Error('No import target');
|
||||
}
|
||||
|
||||
export async function pickFromMAM(): Promise<void> {
|
||||
|
|
@ -69,10 +72,42 @@ export function initMAMBridge(): void {
|
|||
(window as any).__zamppPickFromMAM = pickFromMAM;
|
||||
const assetId = qs.get('asset');
|
||||
if (!assetId) return;
|
||||
// Auto-skip the startup chooser so MediaBridge initializes immediately.
|
||||
const clickMatching = (matches: string[]): boolean => {
|
||||
const btns = document.querySelectorAll('button,[role="button"],a');
|
||||
for (const el of Array.from(btns)) {
|
||||
const t = (el.textContent || '').trim().toLowerCase();
|
||||
if (matches.some(m => t === m || t.startsWith(m))) { (el as HTMLElement).click(); return true; }
|
||||
}
|
||||
return false;
|
||||
};
|
||||
let dismissTries = 0;
|
||||
const dismissInterval = setInterval(() => {
|
||||
const acted = clickMatching(['horizontal', 'open editor', 'start fresh', 'skip tour']);
|
||||
if (dismissTries++ > 80) clearInterval(dismissInterval);
|
||||
if (acted) dismissTries = Math.max(0, dismissTries - 4);
|
||||
}, 250);
|
||||
let lastProjectId: string | null = null;
|
||||
let stableSince = 0;
|
||||
let imported = false;
|
||||
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);
|
||||
if (imported) return;
|
||||
const w = window as any;
|
||||
const st = w.__projectStore && w.__projectStore.getState && w.__projectStore.getState();
|
||||
const p = st && st.project;
|
||||
const ready = !!(p && p.mediaLibrary);
|
||||
if (!ready) {
|
||||
if (n < 80) { setTimeout(() => tryImport(n + 1), 750); return; }
|
||||
console.error('[mam-bridge] project never loaded'); return;
|
||||
}
|
||||
if (p.id !== lastProjectId) { lastProjectId = p.id; stableSince = Date.now(); setTimeout(() => tryImport(n + 1), 750); return; }
|
||||
if (Date.now() - stableSince < 1500) { setTimeout(() => tryImport(n + 1), 500); return; }
|
||||
try {
|
||||
await importAsset(assetId);
|
||||
imported = true;
|
||||
console.log('[mam-bridge] imported', assetId, 'into project', p.id);
|
||||
} catch (e) {
|
||||
if (n < 80) setTimeout(() => tryImport(n + 1), 1000);
|
||||
else console.error('[mam-bridge] import retries exhausted', e);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5901,3 +5901,5 @@ export const useProjectStore = create<ProjectState>()(
|
|||
};
|
||||
}),
|
||||
);
|
||||
|
||||
;(window as any).__projectStore = useProjectStore;
|
||||
|
|
|
|||
Loading…
Reference in a new issue