Skip to content

Commit ed66792

Browse files
authored
feat: native save dialog for audio exports on desktop (Windows + macOS) (#115)
1 parent 2fa3de9 commit ed66792

4 files changed

Lines changed: 34 additions & 2 deletions

File tree

‎desktop/src-tauri/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ serde_json = "1"
1818
sha2 = "0.10"
1919
tar = "0.4"
2020
tauri = { version = "2", features = [] }
21+
tauri-plugin-dialog = "2"
2122
tauri-plugin-store = "2"
2223
zip = { version = "2", default-features = false, features = ["deflate"] }
2324
zstd = "0.13"

‎desktop/src-tauri/capabilities/default.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
"identifier": "default",
44
"description": "Default StemDeck desktop window permissions",
55
"windows": ["main"],
6-
"permissions": ["core:default"]
6+
"permissions": ["core:default", "dialog:allow-save"]
77
}

‎desktop/src-tauri/src/main.rs‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ struct GpuSetup {
109109

110110
fn main() {
111111
tauri::Builder::default()
112+
.plugin(tauri_plugin_dialog::init())
112113
.plugin(tauri_plugin_store::Builder::default().build())
113114
.setup(|app| {
114115
let data_dir = match local_data_dir() {
@@ -152,6 +153,7 @@ fn main() {
152153
ensure_torch_device,
153154
start_backend,
154155
open_url,
156+
save_audio_file,
155157
store_get,
156158
store_set,
157159
mark_store_migration_done,
@@ -961,6 +963,35 @@ fn open_url(url: String) -> Result<(), String> {
961963
Ok(())
962964
}
963965

966+
#[tauri::command]
967+
async fn save_audio_file(app: tauri::AppHandle, url: String, filename: String) -> Result<(), String> {
968+
if !url.starts_with("http://") && !url.starts_with("https://") {
969+
return Err("only http/https URLs are permitted".to_string());
970+
}
971+
use tauri_plugin_dialog::DialogExt;
972+
let dest = app
973+
.dialog()
974+
.file()
975+
.set_file_name(&filename)
976+
.blocking_save_file();
977+
let Some(file_path) = dest else {
978+
return Ok(()); // user cancelled
979+
};
980+
let dest = file_path.into_path().map_err(|e| e.to_string())?;
981+
let client = reqwest::Client::new();
982+
let resp = client
983+
.get(&url)
984+
.send()
985+
.await
986+
.map_err(|e| format!("fetch failed: {e}"))?;
987+
if !resp.status().is_success() {
988+
return Err(format!("backend returned HTTP {}", resp.status()));
989+
}
990+
let bytes = resp.bytes().await.map_err(|e| format!("read failed: {e}"))?;
991+
std::fs::write(&dest, &bytes).map_err(|e| format!("write failed: {e}"))?;
992+
Ok(())
993+
}
994+
964995
fn stop_backend(state: &BackendState) {
965996
if let Ok(mut guard) = state.child.lock() {
966997
if let Some(child) = guard.as_mut() {

‎static/js/player.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1011,7 +1011,7 @@ export function updateFooterTrack({ title, thumbnail, key, bpm, stemCount } = {}
10111011
function _triggerDownload(url, filename) {
10121012
const fullUrl = url.startsWith("http") ? url : `${location.origin}${url}`;
10131013
if (window.__TAURI__?.core?.invoke) {
1014-
window.__TAURI__.core.invoke("open_url", { url: fullUrl });
1014+
window.__TAURI__.core.invoke("save_audio_file", { url: fullUrl, filename });
10151015
return;
10161016
}
10171017
const a = document.createElement("a");

0 commit comments

Comments
 (0)