import { registerMediaSaver } from './saveMedia'; import { isElectron, isTauri } from './tauri'; type ElectronSaveResult = { success?: boolean; canceled?: boolean; path?: string; error?: string; }; type ElectronMediaApi = { saveFile?: (payload: { filename: string; mimeType?: string; data: Uint8Array; }) => Promise; }; async function blobToUint8Array(blob: Blob): Promise { const buffer = await blob.arrayBuffer(); return new Uint8Array(buffer); } function guessFilters(filename: string, mimeType?: string): Array<{ name: string; extensions: string[] }> { const ext = filename.includes('.') ? filename.split('.').pop()!.toLowerCase() : ''; const mime = (mimeType || '').toLowerCase(); if (mime.startsWith('image/') || ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg'].includes(ext)) { return [ { name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg'] }, { name: 'All Files', extensions: ['*'] }, ]; } if (mime.startsWith('video/') || ['mp4', 'webm', 'mkv', 'mov'].includes(ext)) { return [ { name: 'Videos', extensions: ['mp4', 'webm', 'mkv', 'mov'] }, { name: 'All Files', extensions: ['*'] }, ]; } if (mime.startsWith('audio/') || ['mp3', 'ogg', 'wav', 'm4a', 'flac'].includes(ext)) { return [ { name: 'Audio', extensions: ['mp3', 'ogg', 'wav', 'm4a', 'flac'] }, { name: 'All Files', extensions: ['*'] }, ]; } if (mime === 'application/pdf' || ext === 'pdf') { return [ { name: 'PDF', extensions: ['pdf'] }, { name: 'All Files', extensions: ['*'] }, ]; } if (ext) { return [ { name: ext.toUpperCase(), extensions: [ext] }, { name: 'All Files', extensions: ['*'] }, ]; } return [{ name: 'All Files', extensions: ['*'] }]; } async function saveWithElectron(blob: Blob, filename: string): Promise { const media = (window.electron as { media?: ElectronMediaApi } | undefined)?.media; if (!media?.saveFile) { throw new Error('Electron media.saveFile is unavailable'); } const data = await blobToUint8Array(blob); const result = await media.saveFile({ filename, mimeType: blob.type || 'application/octet-stream', data, }); if (result?.canceled) return; if (result?.success === false) { throw new Error(result.error || 'Failed to save file'); } } async function saveWithTauri(blob: Blob, filename: string): Promise { const { save } = await import('@tauri-apps/plugin-dialog'); const { writeFile } = await import('@tauri-apps/plugin-fs'); const path = await save({ defaultPath: filename, filters: guessFilters(filename, blob.type), }); if (!path) return; const data = await blobToUint8Array(blob); await writeFile(path, data); } /** * Wire Electron / Tauri native save dialogs into core saveMedia helpers. * Call once at desktop app startup. No-ops in plain browser. */ export function registerDesktopMediaSaver(): void { if (isElectron()) { registerMediaSaver(async (blob, filename) => { await saveWithElectron(blob, filename); }); return; } if (isTauri()) { registerMediaSaver(async (blob, filename) => { await saveWithTauri(blob, filename); }); } }