modules and packages
This commit is contained in:
parent
e86b68dc49
commit
7a0a8537b9
6 changed files with 714 additions and 21 deletions
30
flake.lock
generated
30
flake.lock
generated
|
@ -88,11 +88,11 @@
|
||||||
"nixpkgs-stable": "nixpkgs-stable"
|
"nixpkgs-stable": "nixpkgs-stable"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1748309015,
|
"lastModified": 1748449332,
|
||||||
"narHash": "sha256-NVgo/saT8uehYYwwhzWrTTFlpH0icR2E3tHHKsUouJ4=",
|
"narHash": "sha256-3Qr0pThpHgbIhscOia2ETxo2ge7el458Z9pv5+ONTKc=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "emacs-overlay",
|
"repo": "emacs-overlay",
|
||||||
"rev": "0fba546d9aa235fc726fe9c8c3bb703e918c14c4",
|
"rev": "44f10e03d04b678d13ee9c36a9bd632dbce84103",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -376,11 +376,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1748391243,
|
"lastModified": 1748455938,
|
||||||
"narHash": "sha256-7sCuihzsTRZemtbTXaFUoGJUfuQErhKEcL9v7HKIo1k=",
|
"narHash": "sha256-mQ/iNzPra2WtDQ+x2r5IadcWNr0m3uHvLMzJkXKAG/8=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "f5b12be834874f7661db4ced969a621ab2d57971",
|
"rev": "02077149e2921014511dac2729ae6dadb4ec50e2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -785,11 +785,11 @@
|
||||||
"xwayland-satellite-unstable": "xwayland-satellite-unstable"
|
"xwayland-satellite-unstable": "xwayland-satellite-unstable"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1748441725,
|
"lastModified": 1748450232,
|
||||||
"narHash": "sha256-Coxe8x6tuE7URW73/qjwKeggrxMYTcdlmMx0u/TUfUg=",
|
"narHash": "sha256-6w7klfyLkEaQ9PlE6z3vYPRJWsguL9JsUdU1Bkv6fbs=",
|
||||||
"owner": "sodiboo",
|
"owner": "sodiboo",
|
||||||
"repo": "niri-flake",
|
"repo": "niri-flake",
|
||||||
"rev": "2933c5d663b47e2934cf09fda3b5aebaa9bc2124",
|
"rev": "8319d7cc74becbce927ab6c1e5b0027d22855c9f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -1004,11 +1004,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs-stable_2": {
|
"nixpkgs-stable_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1748162331,
|
"lastModified": 1748302896,
|
||||||
"narHash": "sha256-rqc2RKYTxP3tbjA+PB3VMRQNnjesrT0pEofXQTrMsS8=",
|
"narHash": "sha256-ixMT0a8mM091vSswlTORZj93WQAJsRNmEvqLL+qwTFM=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "7c43f080a7f28b2774f3b3f43234ca11661bf334",
|
"rev": "7848cd8c982f7740edf76ddb3b43d234cb80fc4d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -1659,11 +1659,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1748436066,
|
"lastModified": 1748467104,
|
||||||
"narHash": "sha256-smVwlauRs/j/mlxYfEqncNg1tvxAlkC/taw1+pJHtWw=",
|
"narHash": "sha256-EgYaWbzbtYFLZH1/Eni1ZdLgTwYhe8g9m10UPdBMCmY=",
|
||||||
"owner": "0xc000022070",
|
"owner": "0xc000022070",
|
||||||
"repo": "zen-browser-flake",
|
"repo": "zen-browser-flake",
|
||||||
"rev": "f39ee486e6a14fc30c4e7d00bf389046640c2f63",
|
"rev": "68d2b647f845f6679db96468336aba5dfae901fa",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -37,6 +37,7 @@ in
|
||||||
texlive.combined.scheme-full
|
texlive.combined.scheme-full
|
||||||
openrgb-with-all-plugins
|
openrgb-with-all-plugins
|
||||||
bazecor
|
bazecor
|
||||||
|
faircamp
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,22 @@
|
||||||
inputs.nh.overlays.default
|
inputs.nh.overlays.default
|
||||||
# inputs.emacs-lsp-booster.overlays.default
|
# inputs.emacs-lsp-booster.overlays.default
|
||||||
|
|
||||||
|
# (final: prev: {
|
||||||
|
# termusic-patched = pkgs.termusic.overrideAttrs (old: {
|
||||||
|
# src = pkgs.fetchFromGithub {
|
||||||
|
# owner = "";
|
||||||
|
# repo = "";
|
||||||
|
# rev = "";
|
||||||
|
# hash = "";
|
||||||
|
# };
|
||||||
|
# patches = old.patches ++ [ ../../patches/termusic-503.patch ];
|
||||||
|
# });
|
||||||
|
# })
|
||||||
|
|
||||||
(final: prev: {
|
(final: prev: {
|
||||||
wineWowPackages.stagingFull = pkgs-stable.wineWowPackages.stagingFull.overrideAttrs
|
wineWowPackages.stagingFull = pkgs-stable.wineWowPackages.stagingFull.overrideAttrs
|
||||||
(old: {
|
(old: {
|
||||||
patches = old.patches ++ [ ../../patches/wine-6006.patch ];
|
patches = [ ../../patches/wine-6006.patch ];
|
||||||
waylandSupport = true;
|
waylandSupport = true;
|
||||||
fontconfigSupport = true;
|
fontconfigSupport = true;
|
||||||
vulkanSupport = true;
|
vulkanSupport = true;
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
mpc
|
mpc
|
||||||
nix-config.packages.x86_64-linux.rmpc-latest
|
nix-config.packages.x86_64-linux.rmpc-latest
|
||||||
|
# termusic-patched
|
||||||
|
termusic
|
||||||
];
|
];
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
|
|
|
@ -241,6 +241,14 @@ window#waybar {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#cpu {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#memory {
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
#taskbar button {
|
#taskbar button {
|
||||||
margin: 0 3px;
|
margin: 0 3px;
|
||||||
padding: 3px 6px;
|
padding: 3px 6px;
|
||||||
|
@ -270,17 +278,14 @@ window#waybar {
|
||||||
|
|
||||||
#battery.charging {
|
#battery.charging {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background-color: #26A65B;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#battery.warning:not(.charging) {
|
#battery.warning:not(.charging) {
|
||||||
background-color: #ffbe61;
|
color: #ffbe61;
|
||||||
color: black;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#battery.critical:not(.charging) {
|
#battery.critical:not(.charging) {
|
||||||
background-color: #f53c3c;
|
color: #f53c3c;
|
||||||
color: #ffffff;
|
|
||||||
animation-name: blink;
|
animation-name: blink;
|
||||||
animation-duration: 0.5s;
|
animation-duration: 0.5s;
|
||||||
animation-timing-function: linear;
|
animation-timing-function: linear;
|
||||||
|
|
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