modules and packages
This commit is contained in:
parent
e86b68dc49
commit
7a0a8537b9
6 changed files with 714 additions and 21 deletions
673
patches/termusic-503.patch
Normal file
673
patches/termusic-503.patch
Normal file
|
@ -0,0 +1,673 @@
|
|||
From a319c68c62ae93c1604c84d488192ca0e8551cb5 Mon Sep 17 00:00:00 2001
|
||||
From: hasezoey <hasezoey@gmail.com>
|
||||
Date: Wed, 28 May 2025 16:21:25 +0200
|
||||
Subject: [PATCH 1/3] feat(tui::components::music_library): change music
|
||||
library tree loading to be async and non-blocking
|
||||
|
||||
Downside is that the placeholder still acts as a correct path and that multiple loads can be done and overwrite eachother.
|
||||
|
||||
fixes #481
|
||||
---
|
||||
CHANGELOG.md | 1 +
|
||||
lib/src/types.rs | 13 ++
|
||||
tui/src/ui/components/config_editor/view.rs | 2 +-
|
||||
tui/src/ui/components/music_library.rs | 226 +++++++++++---------
|
||||
tui/src/ui/components/playlist.rs | 2 +-
|
||||
tui/src/ui/components/tag_editor/update.rs | 2 +-
|
||||
tui/src/ui/model/mod.rs | 19 +-
|
||||
tui/src/ui/model/update.rs | 32 +--
|
||||
8 files changed, 172 insertions(+), 125 deletions(-)
|
||||
|
||||
diff --git a/CHANGELOG.md b/CHANGELOG.md
|
||||
index 2cd911a25..0641e5f29 100644
|
||||
--- a/CHANGELOG.md
|
||||
+++ b/CHANGELOG.md
|
||||
@@ -21,6 +21,7 @@
|
||||
- Fix(tui): on track change, dont select "current track" playlist item if the old "current track" was not selected.
|
||||
- Fix(tui): re-select the (approximately) same playlist item after a shuffle.
|
||||
- Fix(tui): escape key will now no longer also act as a quit key.
|
||||
+- Fix(tui): changed the music library tree loading to be async.
|
||||
- Fix(server): with `rusty-soundtouch` on `rusty` backend, dont take initial samples until necessary.
|
||||
- Fix(server): on rusty backend, always decode and use `f32` samples. (instead of `i16`)
|
||||
- Fix(server): on rusty backend, update `soundtouch` version to fix build issues on latest arch & gcc 15.
|
||||
diff --git a/lib/src/types.rs b/lib/src/types.rs
|
||||
index d26802f9d..f60080802 100644
|
||||
--- a/lib/src/types.rs
|
||||
+++ b/lib/src/types.rs
|
||||
@@ -1,3 +1,4 @@
|
||||
+use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::v2::tui::{keys::KeyBinding, theme::styles::ColorTermusic};
|
||||
@@ -387,6 +388,14 @@ pub enum KFMsg {
|
||||
PodcastRefreshAllFeedsBlurUp,
|
||||
}
|
||||
|
||||
+/// Basically a Tree Node, but without having to include `tui-realm-treeview` as another dependency for lib
|
||||
+#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
+pub struct RecVec<T, V> {
|
||||
+ pub id: T,
|
||||
+ pub value: V,
|
||||
+ pub children: Vec<RecVec<T, V>>,
|
||||
+}
|
||||
+
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum LIMsg {
|
||||
TreeStepInto(String),
|
||||
@@ -397,6 +406,10 @@ pub enum LIMsg {
|
||||
SwitchRoot,
|
||||
AddRoot,
|
||||
RemoveRoot,
|
||||
+
|
||||
+ /// A requested node is ready from loading.
|
||||
+ /// `(Tree, FocusNode)`
|
||||
+ TreeNodeReady(RecVec<PathBuf, String>, Option<String>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
diff --git a/tui/src/ui/components/config_editor/view.rs b/tui/src/ui/components/config_editor/view.rs
|
||||
index de259702a..1a96d2177 100644
|
||||
--- a/tui/src/ui/components/config_editor/view.rs
|
||||
+++ b/tui/src/ui/components/config_editor/view.rs
|
||||
@@ -1732,7 +1732,7 @@ impl Model {
|
||||
}
|
||||
|
||||
pub fn umount_config_editor(&mut self) {
|
||||
- self.library_reload_tree();
|
||||
+ self.library_scan_dir(&self.library.tree_path, None);
|
||||
self.playlist_reload();
|
||||
self.database_reload();
|
||||
self.progress_reload();
|
||||
diff --git a/tui/src/ui/components/music_library.rs b/tui/src/ui/components/music_library.rs
|
||||
index afd6cc6ad..6a4d65e5c 100644
|
||||
--- a/tui/src/ui/components/music_library.rs
|
||||
+++ b/tui/src/ui/components/music_library.rs
|
||||
@@ -1,14 +1,15 @@
|
||||
-use crate::ui::model::Model;
|
||||
+use crate::ui::model::{DownloadTracker, Model};
|
||||
use crate::ui::tui_cmd::TuiCmd;
|
||||
use crate::utils::get_pin_yin;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::fs::{remove_dir_all, remove_file, rename, DirEntry};
|
||||
use std::path::{Path, PathBuf};
|
||||
+use std::sync::mpsc::Sender;
|
||||
use termusiclib::config::v2::server::config_extra::ServerConfigVersionedDefaulted;
|
||||
use termusiclib::config::v2::server::ScanDepth;
|
||||
use termusiclib::config::SharedTuiSettings;
|
||||
use termusiclib::ids::Id;
|
||||
-use termusiclib::types::{GSMsg, LIMsg, Msg, PLMsg, TEMsg, YSMsg};
|
||||
+use termusiclib::types::{GSMsg, LIMsg, Msg, PLMsg, RecVec, TEMsg, YSMsg};
|
||||
use tui_realm_treeview::{Node, Tree, TreeView, TREE_CMD_CLOSE, TREE_CMD_OPEN, TREE_INITIAL_NODE};
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
@@ -234,14 +235,6 @@ impl Component<Msg, NoUserEvent> for MusicLibrary {
|
||||
}
|
||||
|
||||
impl Model {
|
||||
- pub fn library_scan_dir<P: Into<PathBuf>>(&mut self, p: P) {
|
||||
- self.library.tree_path = p.into();
|
||||
- self.library.tree = Tree::new(Self::library_dir_tree(
|
||||
- &self.library.tree_path,
|
||||
- self.config_server.read().get_library_scan_depth(),
|
||||
- ));
|
||||
- }
|
||||
-
|
||||
pub fn library_upper_dir(&self) -> Option<PathBuf> {
|
||||
self.library
|
||||
.tree_path
|
||||
@@ -249,12 +242,58 @@ impl Model {
|
||||
.map(std::path::Path::to_path_buf)
|
||||
}
|
||||
|
||||
- pub fn library_dir_tree(p: &Path, depth: ScanDepth) -> Node<String> {
|
||||
- let name: String = match p.file_name() {
|
||||
+ /// Execute [`Self::library_scan`] from a `&self` instance.
|
||||
+ #[inline]
|
||||
+ pub fn library_scan_dir<P: Into<PathBuf>>(&self, path: P, focus_node: Option<String>) {
|
||||
+ Self::library_scan(
|
||||
+ self.tx_to_main.clone(),
|
||||
+ self.download_tracker.clone(),
|
||||
+ path,
|
||||
+ self.config_server.read().get_library_scan_depth(),
|
||||
+ focus_node,
|
||||
+ );
|
||||
+ }
|
||||
+
|
||||
+ pub fn loading_tree() -> Tree<String> {
|
||||
+ Tree::new(Node::new("/dev/null".to_string(), "Loading...".to_string()))
|
||||
+ }
|
||||
+
|
||||
+ /// Execute a library scan on a different thread.
|
||||
+ ///
|
||||
+ /// Executes [`Self::library_dir_tree`] on a different thread and send a [`LIMsg::TreeNodeReady`] on finish
|
||||
+ pub fn library_scan<P: Into<PathBuf>>(
|
||||
+ tx: Sender<Msg>,
|
||||
+ download_tracker: DownloadTracker,
|
||||
+ path: P,
|
||||
+ depth: ScanDepth,
|
||||
+ focus_node: Option<String>,
|
||||
+ ) {
|
||||
+ let path = path.into();
|
||||
+ std::thread::Builder::new()
|
||||
+ .name("library tree scan".to_string())
|
||||
+ .spawn(move || {
|
||||
+ download_tracker.increase_one(path.to_string_lossy());
|
||||
+ let root_node = Self::library_dir_tree(&path, depth);
|
||||
+
|
||||
+ let _ = tx.send(Msg::Library(LIMsg::TreeNodeReady(root_node, focus_node)));
|
||||
+ download_tracker.decrease_one(&path.to_string_lossy());
|
||||
+ })
|
||||
+ .expect("Failed to spawn thread");
|
||||
+ }
|
||||
+
|
||||
+ /// Scan the given `path` for up to `depth`, and return a [`Node`] tree.
|
||||
+ ///
|
||||
+ /// Note: consider using [`Self::library_scan`] instead of this directly.
|
||||
+ fn library_dir_tree(path: &Path, depth: ScanDepth) -> RecVec<PathBuf, String> {
|
||||
+ let name: String = match path.file_name() {
|
||||
None => "/".to_string(),
|
||||
Some(n) => n.to_string_lossy().into_owned(),
|
||||
};
|
||||
- let mut node: Node<String> = Node::new(p.to_string_lossy().into_owned(), name);
|
||||
+ let mut node = RecVec {
|
||||
+ id: path.to_path_buf(),
|
||||
+ value: name,
|
||||
+ children: Vec::new(),
|
||||
+ };
|
||||
|
||||
let depth = match depth {
|
||||
ScanDepth::Limited(v) => v,
|
||||
@@ -262,8 +301,8 @@ impl Model {
|
||||
ScanDepth::Unlimited => u32::MAX,
|
||||
};
|
||||
|
||||
- if depth > 0 && p.is_dir() {
|
||||
- if let Ok(paths) = std::fs::read_dir(p) {
|
||||
+ if depth > 0 && path.is_dir() {
|
||||
+ if let Ok(paths) = std::fs::read_dir(path) {
|
||||
let mut paths: Vec<(String, PathBuf)> = paths
|
||||
.filter_map(std::result::Result::ok)
|
||||
.filter(|p| !p.file_name().to_string_lossy().starts_with('.'))
|
||||
@@ -273,12 +312,14 @@ impl Model {
|
||||
paths.sort_by(|a, b| alphanumeric_sort::compare_str(&a.0, &b.0));
|
||||
|
||||
for p in paths {
|
||||
- node.add_child(Self::library_dir_tree(&p.1, ScanDepth::Limited(depth - 1)));
|
||||
+ node.children
|
||||
+ .push(Self::library_dir_tree(&p.1, ScanDepth::Limited(depth - 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
node
|
||||
}
|
||||
+
|
||||
pub fn library_dir_children(p: &Path) -> Vec<String> {
|
||||
let mut children: Vec<String> = vec![];
|
||||
if p.is_dir() {
|
||||
@@ -300,88 +341,69 @@ impl Model {
|
||||
children
|
||||
}
|
||||
|
||||
- pub fn library_reload_with_node_focus(&mut self, node: Option<&str>) {
|
||||
+ /// Reload the library with the given `node` as a focus, also starts a new database sync worker for the current path.
|
||||
+ pub fn library_reload_with_node_focus(&mut self, node: Option<String>) {
|
||||
self.db.sync_database(self.library.tree_path.as_path());
|
||||
self.database_reload();
|
||||
- self.library_reload_tree();
|
||||
- if let Some(n) = node {
|
||||
- assert!(self
|
||||
- .app
|
||||
- .attr(
|
||||
- &Id::Library,
|
||||
- Attribute::Custom(TREE_INITIAL_NODE),
|
||||
- AttrValue::String(n.to_string()),
|
||||
- )
|
||||
- .is_ok());
|
||||
+ self.library_scan_dir(&self.library.tree_path, node);
|
||||
+ }
|
||||
+
|
||||
+ /// Convert a [`RecVec`] to a [`Node`].
|
||||
+ fn recvec_to_node(vec: RecVec<PathBuf, String>) -> Node<String> {
|
||||
+ let mut node = Node::new(vec.id.to_string_lossy().to_string(), vec.value);
|
||||
+
|
||||
+ for val in vec.children {
|
||||
+ node.add_child(Self::recvec_to_node(val));
|
||||
}
|
||||
+
|
||||
+ node
|
||||
}
|
||||
|
||||
- pub fn library_reload_tree(&mut self) {
|
||||
- self.library.tree = Tree::new(Self::library_dir_tree(
|
||||
- self.library.tree_path.as_ref(),
|
||||
- self.config_server.read().get_library_scan_depth(),
|
||||
- ));
|
||||
- let current_node = match self.app.state(&Id::Library).ok().unwrap() {
|
||||
+ /// Apply the given [`RecVec`] as a tree
|
||||
+ pub fn library_apply_as_tree(
|
||||
+ &mut self,
|
||||
+ msg: RecVec<PathBuf, String>,
|
||||
+ focus_node: Option<String>,
|
||||
+ ) {
|
||||
+ let root_path = msg.id.clone();
|
||||
+ let root_node = Self::recvec_to_node(msg);
|
||||
+
|
||||
+ let old_current_node = match self.app.state(&Id::Library).ok().unwrap() {
|
||||
State::One(StateValue::String(id)) => Some(id),
|
||||
_ => None,
|
||||
};
|
||||
- let mut focus = false;
|
||||
-
|
||||
- if let Ok(f) = self.app.query(&Id::Library, Attribute::Focus) {
|
||||
- if Some(AttrValue::Flag(true)) == f {
|
||||
- focus = true;
|
||||
- }
|
||||
- }
|
||||
|
||||
- if focus {
|
||||
- self.app.umount(&Id::Library).ok();
|
||||
- assert!(self
|
||||
- .app
|
||||
- .mount(
|
||||
- Id::Library,
|
||||
- Box::new(MusicLibrary::new(
|
||||
- &self.library.tree,
|
||||
- current_node,
|
||||
- self.config_tui.clone(),
|
||||
- ),),
|
||||
- Vec::new()
|
||||
- )
|
||||
- .is_ok());
|
||||
- self.app.active(&Id::Library).ok();
|
||||
- return;
|
||||
+ self.library.tree_path = root_path;
|
||||
+ self.library.tree = Tree::new(root_node);
|
||||
+
|
||||
+ // remount preserves focus
|
||||
+ let _ = self.app.remount(
|
||||
+ Id::Library,
|
||||
+ Box::new(MusicLibrary::new(
|
||||
+ &self.library.tree,
|
||||
+ old_current_node,
|
||||
+ self.config_tui.clone(),
|
||||
+ )),
|
||||
+ Vec::new(),
|
||||
+ );
|
||||
+
|
||||
+ // focus the specified node
|
||||
+ if let Some(id) = focus_node {
|
||||
+ let _ = self.app.attr(
|
||||
+ &Id::Library,
|
||||
+ Attribute::Custom(TREE_INITIAL_NODE),
|
||||
+ AttrValue::String(id),
|
||||
+ );
|
||||
}
|
||||
-
|
||||
- assert!(self
|
||||
- .app
|
||||
- .remount(
|
||||
- Id::Library,
|
||||
- Box::new(MusicLibrary::new(
|
||||
- &self.library.tree,
|
||||
- current_node,
|
||||
- self.config_tui.clone(),
|
||||
- ),),
|
||||
- Vec::new()
|
||||
- )
|
||||
- .is_ok());
|
||||
}
|
||||
|
||||
- // Kept for debugging focus issue
|
||||
- // if let Ok(f) = self.app.query(&Id::Library, Attribute::Focus) {
|
||||
- // if Some(AttrValue::Flag(true)) == f {
|
||||
- // error!("focus after remount: true");
|
||||
- // } else {
|
||||
- // error!("focus after remount: false");
|
||||
- // }
|
||||
- // }
|
||||
pub fn library_stepinto(&mut self, node_id: &str) {
|
||||
- self.library_scan_dir(PathBuf::from(node_id));
|
||||
- self.library_reload_tree();
|
||||
+ self.library_scan_dir(PathBuf::from(node_id), None);
|
||||
}
|
||||
|
||||
pub fn library_stepout(&mut self) {
|
||||
if let Some(p) = self.library_upper_dir() {
|
||||
- self.library_scan_dir(p);
|
||||
- self.library_reload_tree();
|
||||
+ self.library_scan_dir(p, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,6 +418,7 @@ impl Model {
|
||||
}
|
||||
}
|
||||
|
||||
+ /// Delete the currently selected node from the filesystem and reload the tree and remove the deleted paths from the playlist.
|
||||
pub fn library_delete_node(&mut self) -> Result<()> {
|
||||
if let Ok(State::One(StateValue::String(node_id))) = self.app.state(&Id::Library) {
|
||||
if let Some(mut route) = self.library.tree.root().route_by_node(&node_id) {
|
||||
@@ -407,28 +430,27 @@ impl Model {
|
||||
remove_dir_all(p)?;
|
||||
}
|
||||
|
||||
- // this is to keep the state of playlist
|
||||
- self.library_reload_tree();
|
||||
- let tree = self.library.tree.clone();
|
||||
- if let Some(new_node) = tree.root().node_by_route(&route) {
|
||||
- self.library_reload_with_node_focus(Some(new_node.id()));
|
||||
- } else {
|
||||
- //special case 1: old route not available but have siblings
|
||||
- if let Some(last) = route.last_mut() {
|
||||
- if last > &mut 0 {
|
||||
- *last -= 1;
|
||||
- }
|
||||
- }
|
||||
- if let Some(new_node) = tree.root().node_by_route(&route) {
|
||||
- self.library_reload_with_node_focus(Some(new_node.id()));
|
||||
- } else {
|
||||
- //special case 2: old route not available and no siblings
|
||||
- route.truncate(route.len() - 1);
|
||||
- if let Some(new_node) = tree.root().node_by_route(&route) {
|
||||
- self.library_reload_with_node_focus(Some(new_node.id()));
|
||||
+ let mut tree = self.library.tree.clone();
|
||||
+ tree.root_mut().remove_child(&node_id);
|
||||
+ let mut focus_node: Option<String> = None;
|
||||
+ // case 1: the route still exists due to having a sibling beyond the index which now takes the same index
|
||||
+ if let Some(node) = tree.root().node_by_route(&route) {
|
||||
+ focus_node = Some(node.id().to_string());
|
||||
+ } else if !route.is_empty() {
|
||||
+ let _ = route.pop();
|
||||
+ // case 2: the route does not exist anymore, but there is a parent in the route
|
||||
+ if let Some(parent) = tree.root().node_by_route(&route) {
|
||||
+ // case 2.1: the parent has children, select the last of them
|
||||
+ if let Some(last_child) = parent.children().last() {
|
||||
+ focus_node = Some(last_child.id().to_string());
|
||||
+ } else {
|
||||
+ // case 2.2: the parent exists, but has no children
|
||||
+ focus_node = Some(parent.id().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
+
|
||||
+ self.library_scan_dir(&self.library.tree_path, focus_node);
|
||||
}
|
||||
// this line remove the deleted songs from playlist
|
||||
self.playlist_update_library_delete();
|
||||
@@ -459,7 +481,7 @@ impl Model {
|
||||
p_parent.join(pold_filename)
|
||||
};
|
||||
rename(pold, new_node_id.as_path())?;
|
||||
- self.library_reload_with_node_focus(new_node_id.to_str());
|
||||
+ self.library_reload_with_node_focus(Some(new_node_id.to_string_lossy().to_string()));
|
||||
}
|
||||
self.library.yanked_node_id = None;
|
||||
self.playlist_update_library_delete();
|
||||
@@ -520,7 +542,7 @@ impl Model {
|
||||
}
|
||||
if let Some(dir) = vec.get(index) {
|
||||
let pathbuf = PathBuf::from(dir);
|
||||
- self.library_scan_dir(pathbuf);
|
||||
+ self.library_scan_dir(pathbuf, None);
|
||||
self.library_reload_with_node_focus(None);
|
||||
}
|
||||
}
|
||||
diff --git a/tui/src/ui/components/playlist.rs b/tui/src/ui/components/playlist.rs
|
||||
index 96254dd8b..a2a333b9e 100644
|
||||
--- a/tui/src/ui/components/playlist.rs
|
||||
+++ b/tui/src/ui/components/playlist.rs
|
||||
@@ -888,7 +888,7 @@ impl Model {
|
||||
// TODO: move this to server?
|
||||
self.playback.playlist.save_m3u(filename)?;
|
||||
|
||||
- self.library_reload_with_node_focus(Some(&filename.to_string_lossy()));
|
||||
+ self.library_reload_with_node_focus(Some(filename.to_string_lossy().to_string()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
diff --git a/tui/src/ui/components/tag_editor/update.rs b/tui/src/ui/components/tag_editor/update.rs
|
||||
index ca268cacf..5b2554501 100644
|
||||
--- a/tui/src/ui/components/tag_editor/update.rs
|
||||
+++ b/tui/src/ui/components/tag_editor/update.rs
|
||||
@@ -34,7 +34,7 @@ impl Model {
|
||||
TEMsg::TagEditorClose => {
|
||||
if let Some(s) = self.tageditor_song.clone() {
|
||||
// TODO: this should be re-done and take actual track ids themself, or at least verified to use the same functions to result in the same id
|
||||
- self.library_reload_with_node_focus(Some(&s.path_as_id_str()));
|
||||
+ self.library_reload_with_node_focus(Some(s.path_as_id_str().to_string()));
|
||||
}
|
||||
self.umount_tageditor();
|
||||
}
|
||||
diff --git a/tui/src/ui/model/mod.rs b/tui/src/ui/model/mod.rs
|
||||
index 8916513aa..c5515e81d 100644
|
||||
--- a/tui/src/ui/model/mod.rs
|
||||
+++ b/tui/src/ui/model/mod.rs
|
||||
@@ -35,7 +35,7 @@ use super::components::TETrack;
|
||||
use super::tui_cmd::TuiCmd;
|
||||
use crate::ui::Application;
|
||||
use crate::CombinedSettings;
|
||||
-use download_tracker::DownloadTracker;
|
||||
+pub use download_tracker::DownloadTracker;
|
||||
|
||||
mod download_tracker;
|
||||
mod playlist;
|
||||
@@ -362,10 +362,7 @@ impl Model {
|
||||
} = config;
|
||||
let path = Self::get_full_path_from_config(&config_server.read());
|
||||
// TODO: refactor music library tree to be Paths instead?
|
||||
- let tree = Tree::new(Self::library_dir_tree(
|
||||
- &path,
|
||||
- config_server.read().get_library_scan_depth(),
|
||||
- ));
|
||||
+ let tree = Self::loading_tree();
|
||||
|
||||
let viuer_supported = if config_tui.read().cover_features_enabled() {
|
||||
get_viuer_support()
|
||||
@@ -412,6 +409,16 @@ impl Model {
|
||||
let ce_theme = config_tui.read().settings.theme.clone();
|
||||
let xywh = xywh::Xywh::from(&config_tui.read().settings.coverart);
|
||||
|
||||
+ let download_tracker = DownloadTracker::default();
|
||||
+
|
||||
+ Self::library_scan(
|
||||
+ tx_to_main.clone(),
|
||||
+ download_tracker.clone(),
|
||||
+ &path,
|
||||
+ config_server.read().get_library_scan_depth(),
|
||||
+ None,
|
||||
+ );
|
||||
+
|
||||
Self {
|
||||
app,
|
||||
quit: false,
|
||||
@@ -460,7 +467,7 @@ impl Model {
|
||||
taskpool,
|
||||
tx_to_main,
|
||||
rx_to_main,
|
||||
- download_tracker: DownloadTracker::default(),
|
||||
+ download_tracker,
|
||||
current_track_lyric: None,
|
||||
playback: Playback::new(),
|
||||
cmd_to_server_tx,
|
||||
diff --git a/tui/src/ui/model/update.rs b/tui/src/ui/model/update.rs
|
||||
index 7e9111ec5..066f56f8f 100644
|
||||
--- a/tui/src/ui/model/update.rs
|
||||
+++ b/tui/src/ui/model/update.rs
|
||||
@@ -57,7 +57,7 @@ impl Update<Msg> for Model {
|
||||
None
|
||||
}
|
||||
Msg::Library(m) => {
|
||||
- self.update_library(&m);
|
||||
+ self.update_library(m);
|
||||
None
|
||||
}
|
||||
Msg::GeneralSearch(m) => {
|
||||
@@ -110,7 +110,7 @@ impl Update<Msg> for Model {
|
||||
|
||||
Msg::Podcast(m) => self.update_podcast(m),
|
||||
Msg::LyricMessage(m) => self.update_lyric_textarea(m),
|
||||
- Msg::Download(m) => self.update_download_msg(&m),
|
||||
+ Msg::Download(m) => self.update_download_msg(m),
|
||||
Msg::Xywh(m) => self.update_xywh_msg(m),
|
||||
|
||||
Msg::ForceRedraw => None,
|
||||
@@ -558,13 +558,13 @@ impl Model {
|
||||
None
|
||||
}
|
||||
|
||||
- fn update_library(&mut self, msg: &LIMsg) {
|
||||
+ fn update_library(&mut self, msg: LIMsg) {
|
||||
match msg {
|
||||
LIMsg::TreeBlur => {
|
||||
assert!(self.app.active(&Id::Playlist).is_ok());
|
||||
}
|
||||
LIMsg::TreeStepInto(path) => {
|
||||
- self.library_stepinto(path);
|
||||
+ self.library_stepinto(&path);
|
||||
}
|
||||
LIMsg::TreeStepOut => {
|
||||
self.library_stepout();
|
||||
@@ -588,6 +588,9 @@ impl Model {
|
||||
self.mount_error_popup(e.context("library remove root"));
|
||||
}
|
||||
}
|
||||
+ LIMsg::TreeNodeReady(vec, focus_node) => {
|
||||
+ self.library_apply_as_tree(vec, focus_node);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -916,20 +919,20 @@ impl Model {
|
||||
}
|
||||
|
||||
// change status bar text to indicate the downloading state
|
||||
- fn update_download_msg(&mut self, msg: &DLMsg) -> Option<Msg> {
|
||||
+ fn update_download_msg(&mut self, msg: DLMsg) -> Option<Msg> {
|
||||
self.redraw = true;
|
||||
match msg {
|
||||
DLMsg::DownloadRunning(url, title) => {
|
||||
- self.download_tracker.increase_one(&**url);
|
||||
+ self.download_tracker.increase_one(&*url);
|
||||
self.show_message_timeout_label_help(
|
||||
- self.download_tracker.message_download_start(title),
|
||||
+ self.download_tracker.message_download_start(&title),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
DLMsg::DownloadSuccess(url) => {
|
||||
- self.download_tracker.decrease_one(url);
|
||||
+ self.download_tracker.decrease_one(&url);
|
||||
self.show_message_timeout_label_help(
|
||||
self.download_tracker.message_download_complete(),
|
||||
None,
|
||||
@@ -945,13 +948,14 @@ impl Model {
|
||||
if self.download_tracker.visible() {
|
||||
return None;
|
||||
}
|
||||
- self.library_reload_with_node_focus(file.as_deref());
|
||||
+ self.library_reload_with_node_focus(file);
|
||||
}
|
||||
DLMsg::DownloadErrDownload(url, title, error_message) => {
|
||||
- self.download_tracker.decrease_one(url);
|
||||
+ self.download_tracker.decrease_one(&url);
|
||||
self.mount_error_popup(anyhow!("download failed: {error_message}"));
|
||||
self.show_message_timeout_label_help(
|
||||
- self.download_tracker.message_download_error_response(title),
|
||||
+ self.download_tracker
|
||||
+ .message_download_error_response(&title),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
@@ -961,17 +965,17 @@ impl Model {
|
||||
self.mount_error_popup(anyhow!("download ok but tag info is not complete."));
|
||||
self.show_message_timeout_label_help(
|
||||
self.download_tracker
|
||||
- .message_download_error_embed_data(title),
|
||||
+ .message_download_error_embed_data(&title),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
DLMsg::MessageShow((title, text)) => {
|
||||
- self.mount_message(title, text);
|
||||
+ self.mount_message(&title, &text);
|
||||
}
|
||||
DLMsg::MessageHide((title, text)) => {
|
||||
- self.umount_message(title, text);
|
||||
+ self.umount_message(&title, &text);
|
||||
}
|
||||
DLMsg::FetchPhotoSuccess(image_wrapper) => {
|
||||
self.show_image(&image_wrapper.data).ok();
|
||||
|
||||
From 8b1fd1216fb81bfa982958086832c10b52d6e494 Mon Sep 17 00:00:00 2001
|
||||
From: hasezoey <hasezoey@gmail.com>
|
||||
Date: Wed, 28 May 2025 16:25:41 +0200
|
||||
Subject: [PATCH 2/3] fix(tui::components::music_library::library_stepout):
|
||||
focus the last root node in the new tree
|
||||
|
||||
Instead of always focusing the root node of the new tree.
|
||||
---
|
||||
CHANGELOG.md | 1 +
|
||||
tui/src/ui/components/music_library.rs | 5 ++++-
|
||||
2 files changed, 5 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/CHANGELOG.md b/CHANGELOG.md
|
||||
index 0641e5f29..3e05516e9 100644
|
||||
--- a/CHANGELOG.md
|
||||
+++ b/CHANGELOG.md
|
||||
@@ -22,6 +22,7 @@
|
||||
- Fix(tui): re-select the (approximately) same playlist item after a shuffle.
|
||||
- Fix(tui): escape key will now no longer also act as a quit key.
|
||||
- Fix(tui): changed the music library tree loading to be async.
|
||||
+- Fix(tui): in the music library tree, when stepping out now the correct node if focused instead of the root node.
|
||||
- Fix(server): with `rusty-soundtouch` on `rusty` backend, dont take initial samples until necessary.
|
||||
- Fix(server): on rusty backend, always decode and use `f32` samples. (instead of `i16`)
|
||||
- Fix(server): on rusty backend, update `soundtouch` version to fix build issues on latest arch & gcc 15.
|
||||
diff --git a/tui/src/ui/components/music_library.rs b/tui/src/ui/components/music_library.rs
|
||||
index 6a4d65e5c..498a29ffb 100644
|
||||
--- a/tui/src/ui/components/music_library.rs
|
||||
+++ b/tui/src/ui/components/music_library.rs
|
||||
@@ -397,13 +397,16 @@ impl Model {
|
||||
}
|
||||
}
|
||||
|
||||
+ /// Handle stepping into a node on the tree
|
||||
pub fn library_stepinto(&mut self, node_id: &str) {
|
||||
self.library_scan_dir(PathBuf::from(node_id), None);
|
||||
}
|
||||
|
||||
+ /// Handle stepping out of the current root node on the tree
|
||||
pub fn library_stepout(&mut self) {
|
||||
if let Some(p) = self.library_upper_dir() {
|
||||
- self.library_scan_dir(p, None);
|
||||
+ let focus_node = Some(self.library.tree_path.to_string_lossy().to_string());
|
||||
+ self.library_scan_dir(p, focus_node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
From 47a13fc83e3c353a516d83e892ea87c7e26ed430 Mon Sep 17 00:00:00 2001
|
||||
From: hasezoey <hasezoey@gmail.com>
|
||||
Date: Wed, 28 May 2025 16:32:42 +0200
|
||||
Subject: [PATCH 3/3] style(tui::components::music_library): add a todo to try
|
||||
to load the directory if not loaded yet
|
||||
|
||||
---
|
||||
tui/src/ui/components/music_library.rs | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/tui/src/ui/components/music_library.rs b/tui/src/ui/components/music_library.rs
|
||||
index 498a29ffb..d98cd6c09 100644
|
||||
--- a/tui/src/ui/components/music_library.rs
|
||||
+++ b/tui/src/ui/components/music_library.rs
|
||||
@@ -90,6 +90,7 @@ impl MusicLibrary {
|
||||
let current_node = self.component.tree_state().selected().unwrap();
|
||||
let p: &Path = Path::new(current_node);
|
||||
if p.is_dir() {
|
||||
+ // TODO: try to load the directory if it is not loaded yet.
|
||||
(self.perform(Cmd::Custom(TREE_CMD_OPEN)), None)
|
||||
} else {
|
||||
(
|
Loading…
Add table
Add a link
Reference in a new issue