feat(frontend): use Wails bindings for file I/O

Replace hidden file inputs and anchor downloads with native open/save
dialogs via a thin platform layer. WAV export sends base64 audio data to
the Go backend for writing to the user-chosen path.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 22:23:35 -07:00
parent 3a5d9d849f
commit 45075b3328
8 changed files with 120 additions and 42 deletions
+38
View File
@@ -0,0 +1,38 @@
import * as FileService from '../../../bindings/sfxkeeb/fileservice';
export type VideoOpenResult = {
url: string;
name: string;
};
export async function openVideo(): Promise<VideoOpenResult | null> {
const result = await FileService.OpenVideo();
if (!result.url) return null;
return { url: result.url, name: result.name };
}
export async function openProject(): Promise<string | null> {
const text = await FileService.OpenProject();
return text || null;
}
export async function saveProject(content: string, defaultName = 'project.json'): Promise<boolean> {
await FileService.SaveProject(content, defaultName);
return true;
}
async function blobToBase64(blob: Blob): Promise<string> {
const buffer = await blob.arrayBuffer();
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]!);
}
return btoa(binary);
}
export async function saveAudio(blob: Blob, defaultName = 'keyboard_track.wav'): Promise<boolean> {
const dataBase64 = await blobToBase64(blob);
await FileService.SaveAudio(dataBase64, defaultName);
return true;
}
+5 -14
View File
@@ -1,5 +1,6 @@
import { renderKeyboardTrack } from './audio/export';
import type { SampleCache } from './audio/sampleCache';
import { saveAudio, saveProject } from './platform/files';
import type { LegacySwitchType, Marker, Project, SwitchType } from './types';
import { app, setActiveSwitch, setMarkers, setSelectedIds } from './store.svelte';
@@ -32,15 +33,10 @@ export function buildProject(): Project {
};
}
export function downloadProject(filename = 'project.json') {
export async function downloadProject(filename = 'project.json') {
const project = buildProject();
const blob = new Blob([JSON.stringify(project, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = filename;
anchor.click();
URL.revokeObjectURL(url);
const content = JSON.stringify(project, null, 2);
await saveProject(content, filename);
}
export function loadProject(data: Project & { version?: number; switch?: string }) {
@@ -65,10 +61,5 @@ export async function exportAudio(duration: number, cache?: SampleCache) {
cache,
});
const url = URL.createObjectURL(blob);
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = 'keyboard_track.wav';
anchor.click();
URL.revokeObjectURL(url);
await saveAudio(blob, 'keyboard_track.wav');
}