Files
qbz/crates/qbz-slint/src/locallibrary_prefs.rs
T
vicrodh 82129cff35 feat(slint): LocalLibrary ephemeral folders + Folders tree 1:1 port
Ephemeral folders: open / browse / play a folder without indexing it —
album-grouped pane, CUE seek fast-path (no full re-decode per virtual track),
folder-path persistence + rehydrate, "already playing" add-vs-replace choice
dialog (ephemeral-only), and now-playing / queue / menu gating so an ephemeral
track exposes no persistence actions.

Folders tree brought 1:1 with Tauri:
- Rail header: select-mode, collapse-all, open-ephemeral, tree search.
- Resizable rail (default ~20% narrower; drag handle, min 50% default /
  max 50% of the content area).
- Subfolder cover grid + grid/list toggle, recursive track count, name search;
  compact icon bulk bar (folder-check / play-next / queue / playlist / clear).
- Tree is the default mode; circular checkboxes homologated with TrackRow;
  compact rows (smaller type + icon, no ambiguous trailing count).
- Flat/Tree icon toggle (disc-album / folder-tree), floated right.

Per-source TrackRow context menu (logic, not a blanket trim): Qobuz/offline =
full; local = play/next/queue + add-to-playlist; plex/ephemeral = play/next/
queue only.

App-wide standard: Math.round pixel-snap on icon centering across the UI to
kill sub-pixel blur (CircleAction, header back/forward/collapse, and the rest).
NowPlayingBar context icon -> turntable (frees disc-album for the Folders
toggle). New assets: folder-check, folder-tree, turntable.

cc
2026-06-07 10:12:59 -06:00

91 lines
2.4 KiB
Rust

//! Persistence for the LocalLibrary toolbar choices (Tracks group mode) across
//! restarts. A small json under `<data-dir>/qbz/locallibrary_ui.json`, mirroring
//! `favorites_prefs.rs`. Search queries are transient, not persisted.
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use slint::ComponentHandle;
use crate::{AppWindow, LocalLibraryState};
#[derive(Serialize, Deserialize)]
struct Prefs {
#[serde(default = "d_off")]
tracks_group: String,
// Path of the last-opened ephemeral folder, re-scanned on startup. None when
// no ephemeral session is active.
#[serde(default)]
ephemeral_folder: Option<String>,
}
impl Default for Prefs {
fn default() -> Self {
Self {
tracks_group: d_off(),
ephemeral_folder: None,
}
}
}
fn d_off() -> String {
"off".to_string()
}
fn store_path() -> Option<PathBuf> {
Some(dirs::data_dir()?.join("qbz").join("locallibrary_ui.json"))
}
fn read() -> Prefs {
let Some(path) = store_path() else {
return Prefs::default();
};
match std::fs::read(&path) {
Ok(bytes) => serde_json::from_slice(&bytes).unwrap_or_default(),
Err(_) => Prefs::default(),
}
}
/// Apply persisted toolbar choices to LocalLibraryState. UI thread.
pub fn load(window: &AppWindow) {
let p = read();
window
.global::<LocalLibraryState>()
.set_tracks_group_mode(p.tracks_group.into());
}
fn write(p: &Prefs) {
let Some(path) = store_path() else {
return;
};
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
if let Ok(json) = serde_json::to_vec_pretty(p) {
let _ = std::fs::write(&path, json);
}
}
/// Persist the current toolbar choices read from LocalLibraryState. Preserves
/// the ephemeral-folder path (read-modify-write).
pub fn save(window: &AppWindow) {
let mut p = read();
p.tracks_group = window
.global::<LocalLibraryState>()
.get_tracks_group_mode()
.into();
write(&p);
}
/// The persisted ephemeral-folder path, if any (rehydrated on startup).
pub fn ephemeral_path() -> Option<String> {
read().ephemeral_folder.filter(|s| !s.is_empty())
}
/// Persist (or clear) the ephemeral-folder path (read-modify-write).
pub fn save_ephemeral_path(path: Option<&str>) {
let mut p = read();
p.ephemeral_folder = path.map(|s| s.to_string());
write(&p);
}