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
91 lines
2.4 KiB
Rust
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);
|
|
}
|