Desktop: tray mini card on left click (store status at a glance next to the tray icon; Refresh runs the main window's Refresh all)

This commit is contained in:
CRTkafa 2026-10-23 11:15:35 +08:00
parent c03938f9dc
commit 60a72e6947
23 changed files with 1052 additions and 11 deletions

View file

@ -44,6 +44,11 @@ fn guard(w: &WebviewWindow) -> Res<()> {
policy::require_main(w.label(), &url, cfg!(debug_assertions)).map_err(Fail::host)
}
fn guard_card(w: &WebviewWindow) -> Res<()> {
let url = w.url().map_err(|e| Fail::host(e.to_string()))?;
policy::require_card(w.label(), &url, cfg!(debug_assertions)).map_err(Fail::host)
}
fn saved(s: &AppState) -> Res<std::sync::MutexGuard<'_, Saved>> {
s.saved.lock().map_err(|_| Fail::host("state poisoned"))
}
@ -1304,6 +1309,69 @@ pub fn tray_update(window: WebviewWindow, app: AppHandle, summary: crate::tray::
crate::tray::apply(&app, &summary).map_err(|e| Fail::host(e.to_string()))
}
/// The main window's snapshot for the tray card (what it already read: releases, reviews, health, last read, busy).
/// Kept in the host so the card shows it the moment it opens; forwarded to the card as `card-snapshot`.
#[tauri::command]
pub fn card_snapshot(window: WebviewWindow, app: AppHandle, snapshot: Value) -> Res<()> {
guard(&window)?;
if !crate::card::set_snapshot(snapshot.clone()) {
return Err(Fail::host("card snapshot refused"));
}
let _ = app.emit_to(crate::card::LABEL, "card-snapshot", snapshot);
Ok(())
}
/// The card: the last snapshot of the main window (null before the first one).
#[tauri::command]
pub fn card_get(window: WebviewWindow) -> Res<Option<Value>> {
guard_card(&window)?;
Ok(crate::card::snapshot())
}
/// The card's buttons. `refresh` and `mute` go to the main window as the tray menu's `tray-action` (its own Refresh
/// all and mute), `route` shows the main window on a route (`tray-route`), `external` opens a store console.
#[tauri::command]
pub fn card_action(window: WebviewWindow, app: AppHandle, action: String, arg: Option<String>) -> Res<()> {
guard_card(&window)?;
if !crate::card::valid_action(&action) {
return Err(Fail::host("unknown card action"));
}
let main = || {
if let Some(w) = app.get_webview_window(policy::MAIN) {
let _ = w.unminimize();
let _ = w.show();
let _ = w.set_focus();
}
};
match action.as_str() {
"hide" => crate::card::hide(&app),
"ready" => { let _ = app.emit_to(policy::MAIN, "card-request", ()); }
"open" => { crate::card::hide(&app); main(); }
"refresh" => { let _ = app.emit_to(policy::MAIN, "tray-action", "read"); }
"mute" => { let _ = app.emit_to(policy::MAIN, "tray-action", "mute-toggle"); }
"quit" => app.exit(0),
"route" => {
let r = arg.unwrap_or_default();
if !crate::tray::valid_route(&r) {
return Err(Fail::host("bad route"));
}
crate::card::hide(&app);
main();
let _ = app.emit_to(policy::MAIN, "tray-route", r);
}
"external" => {
let u = url::Url::parse(arg.as_deref().unwrap_or_default()).map_err(|_| Fail::host("not a URL"))?;
if !policy::external_allowed(&u) {
return Err(Fail::host("only the store consoles open from here"));
}
crate::card::hide(&app);
app.opener().open_url(u.as_str(), None::<&str>).map_err(|e| Fail::host(e.to_string()))?;
}
_ => {}
}
Ok(())
}
/// Forgets everything after a native confirmation: store keys (keychain), studios, accounts, app folders (the folders
/// themselves stay), overrides, settings and saved reads. Downloaded tools stay. True when it was reset.
#[tauri::command]