Anki/qt/launcher-gui/src-tauri/src/commands.rs
2025-10-14 09:22:33 +08:00

211 lines
6.2 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use anki_proto::generic;
use anki_proto::launcher::get_langs_response;
use anki_proto::launcher::get_mirrors_response;
use anki_proto::launcher::ChooseVersionRequest;
use anki_proto::launcher::ChooseVersionResponse;
use anki_proto::launcher::GetLangsResponse;
use anki_proto::launcher::GetMirrorsResponse;
use anki_proto::launcher::GetVersionsResponse;
use anki_proto::launcher::I18nResourcesRequest;
use anki_proto::launcher::Mirror;
use anki_proto::launcher::Options;
use anki_proto::launcher::ZoomWebviewRequest;
use anyhow::Context;
use anyhow::Result;
use strum::IntoEnumIterator;
use tauri::AppHandle;
use tauri::Manager;
use tauri::Runtime;
use tauri::WebviewWindow;
use tauri_plugin_os::locale;
use crate::lang::get_tr;
use crate::lang::setup_i18n;
use crate::lang::LANGS;
use crate::lang::LANGS_DEFAULT_REGION;
use crate::lang::LANGS_WITH_REGIONS;
use crate::uv;
pub async fn i18n_resources<R: Runtime>(
app: AppHandle<R>,
_window: WebviewWindow<R>,
input: I18nResourcesRequest,
) -> Result<generic::Json> {
let tr = get_tr(&app)?;
serde_json::to_vec(&tr.resources_for_js(&input.modules))
.with_context(|| "failed to serialise i18n resources")
.map(Into::into)
}
pub async fn get_langs<R: Runtime>(
_app: AppHandle<R>,
_window: WebviewWindow<R>,
) -> Result<GetLangsResponse> {
let langs = LANGS
.into_iter()
.map(|(locale, name)| get_langs_response::Pair {
name: name.to_string(),
locale: locale.to_string(),
})
.collect();
let user_locale = locale()
.and_then(|l| {
if LANGS.contains_key(&l) {
Some(l)
} else {
LANGS_DEFAULT_REGION
.get(l.split('-').next().unwrap())
.or_else(|| LANGS_DEFAULT_REGION.get("en"))
.map(ToString::to_string)
}
})
.unwrap();
Ok(GetLangsResponse { user_locale, langs })
}
pub async fn set_lang<R: Runtime>(
app: AppHandle<R>,
_window: WebviewWindow<R>,
input: generic::String,
) -> Result<()> {
// python's lang_to_disk_lang
let input = input.val;
let input = if LANGS_WITH_REGIONS.contains(input.as_str()) {
input
} else {
input.split('-').next().unwrap().to_owned()
};
setup_i18n(&app, &[&*input]);
Ok(())
}
pub async fn get_mirrors<R: Runtime>(
app: AppHandle<R>,
_window: WebviewWindow<R>,
) -> Result<GetMirrorsResponse> {
let tr = get_tr(&app)?;
Ok(GetMirrorsResponse {
mirrors: Mirror::iter()
.map(|mirror| get_mirrors_response::Pair {
mirror: mirror.into(),
name: match mirror {
Mirror::Disabled => tr.launcher_mirror_no_mirror(),
Mirror::China => tr.launcher_mirror_china(),
}
.into(),
})
.collect(),
})
}
pub async fn get_options<R: Runtime>(
app: AppHandle<R>,
_window: WebviewWindow<R>,
) -> Result<Options> {
let state = app.state::<uv::State>();
let allow_betas = state.prerelease_marker.exists();
let download_caching = !state.no_cache_marker.exists();
let mirror = if state.mirror_path.exists() {
Mirror::China
} else {
Mirror::Disabled
}
.into();
Ok(Options {
allow_betas,
download_caching,
mirror,
})
}
pub async fn get_versions<R: Runtime>(
app: AppHandle<R>,
_window: WebviewWindow<R>,
) -> Result<GetVersionsResponse> {
let state = (*app.state::<uv::State>()).clone();
// TODO: why...
let mut state1 = state.clone();
let releases_fut = tauri::async_runtime::spawn_blocking(move || uv::get_releases(&state));
let check_fut = tauri::async_runtime::spawn_blocking(move || uv::check_versions(&mut state1));
let (releases, check) = futures::future::join(releases_fut, check_fut).await;
// TODO: handle errors properly
let uv::Releases { latest, all } = releases.unwrap().unwrap();
let (current, previous) = check.unwrap().unwrap();
Ok(GetVersionsResponse {
latest,
all,
current,
previous,
})
}
pub async fn choose_version<R: Runtime>(
app: AppHandle<R>,
_window: WebviewWindow<R>,
input: ChooseVersionRequest,
) -> Result<ChooseVersionResponse> {
let state = (*app.state::<uv::State>()).clone();
let version = input.version.clone();
tauri::async_runtime::spawn_blocking(move || -> Result<()> {
if let Some(options) = input.options {
uv::set_allow_betas(&state, options.allow_betas)?;
uv::set_cache_enabled(&state, options.download_caching)?;
uv::set_mirror(&state, options.mirror != Mirror::Disabled as i32)?;
}
if !input.keep_existing || state.pyproject_modified_by_user {
// install or resync
let res = uv::handle_version_install_or_update(
app.clone(),
&state,
&input.version,
input.keep_existing,
);
println!("handle_version_install_or_update: {res:?}");
res?;
}
uv::post_install(&state)?;
// TODO: show some sort of notification before closing
// if let Some(window) = app.get_webview_window("main") {
// let _ = window.destroy();
// }
// // app.exit can't be called from the main thread
// app.exit(0);
Ok(())
})
.await??;
Ok(ChooseVersionResponse { version })
}
/// NOTE: [zoomHotkeysEnabled](https://v2.tauri.app/reference/config/#zoomhotkeysenabled) exists
/// but the polyfill it uses on lin doesn't allow regular scrolling
pub async fn zoom_webview<R: Runtime>(
_app: AppHandle<R>,
window: WebviewWindow<R>,
input: ZoomWebviewRequest,
) -> Result<()> {
let factor = input.scale_factor.into();
// NOTE: not supported on windows
let _ = window.set_zoom(factor);
Ok(())
}
pub async fn window_ready<R: Runtime>(_app: AppHandle<R>, window: WebviewWindow<R>) -> Result<()> {
window
.show()
.with_context(|| format!("could not show window: {}", window.label()))
}