Add support for sorting playlist tracks (#328)
* Add support for sorting playlists * Update string * formatting
This commit is contained in:
12
README.md
12
README.md
@@ -131,6 +131,18 @@ are supported:
|
|||||||
* `clear`: Clear playlist
|
* `clear`: Clear playlist
|
||||||
* `share [current | selected]`: Copies a sharable URL of either the selected item or the currernt song to the system clipboard
|
* `share [current | selected]`: Copies a sharable URL of either the selected item or the currernt song to the system clipboard
|
||||||
* `newplaylist <name>`: Create new playlist with name `<name>`
|
* `newplaylist <name>`: Create new playlist with name `<name>`
|
||||||
|
* `sort <sort_key> <sort_direction>`: Sort a playlist by `<sort_key>` in direction `<sort_direction>`
|
||||||
|
|
||||||
|
Supported `<sort_key>` are:
|
||||||
|
* title
|
||||||
|
* album
|
||||||
|
* artist
|
||||||
|
* duration
|
||||||
|
* added
|
||||||
|
|
||||||
|
Supported `<sort_direction>` are:
|
||||||
|
* a | asc | ascending
|
||||||
|
* d | desc | descending
|
||||||
|
|
||||||
The screens can be opened with `queue`, `search`, `playlists` and `log`, whereas
|
The screens can be opened with `queue`, `search`, `playlists` and `log`, whereas
|
||||||
`search` can be supplied with a search term that will be entered after opening
|
`search` can be supplied with a search term that will be entered after opening
|
||||||
|
|||||||
@@ -42,6 +42,23 @@ impl Default for MoveAmount {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Display, Clone, Serialize, Deserialize, Debug)]
|
||||||
|
#[strum(serialize_all = "lowercase")]
|
||||||
|
pub enum SortKey {
|
||||||
|
Title,
|
||||||
|
Duration,
|
||||||
|
Artist,
|
||||||
|
Album,
|
||||||
|
Added,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Display, Clone, Serialize, Deserialize, Debug)]
|
||||||
|
#[strum(serialize_all = "lowercase")]
|
||||||
|
pub enum SortDirection {
|
||||||
|
Ascending,
|
||||||
|
Descending,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Display, Clone, Serialize, Deserialize, Debug)]
|
#[derive(Display, Clone, Serialize, Deserialize, Debug)]
|
||||||
#[strum(serialize_all = "lowercase")]
|
#[strum(serialize_all = "lowercase")]
|
||||||
pub enum JumpMode {
|
pub enum JumpMode {
|
||||||
@@ -116,6 +133,7 @@ pub enum Command {
|
|||||||
Noop,
|
Noop,
|
||||||
Insert(Option<String>),
|
Insert(Option<String>),
|
||||||
NewPlaylist(String),
|
NewPlaylist(String),
|
||||||
|
Sort(SortKey, SortDirection),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Command {
|
impl fmt::Display for Command {
|
||||||
@@ -173,6 +191,7 @@ impl fmt::Display for Command {
|
|||||||
Command::ReloadConfig => "reload".to_string(),
|
Command::ReloadConfig => "reload".to_string(),
|
||||||
Command::Insert(_) => "insert".to_string(),
|
Command::Insert(_) => "insert".to_string(),
|
||||||
Command::NewPlaylist(name) => format!("new playlist {}", name),
|
Command::NewPlaylist(name) => format!("new playlist {}", name),
|
||||||
|
Command::Sort(key, direction) => format!("sort {} {}", key, direction),
|
||||||
};
|
};
|
||||||
write!(f, "{}", repr)
|
write!(f, "{}", repr)
|
||||||
}
|
}
|
||||||
@@ -369,6 +388,39 @@ pub fn parse(input: &str) -> Option<Command> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"sort" => {
|
||||||
|
if !args.is_empty() {
|
||||||
|
let sort_key = args.get(0).and_then(|key| match *key {
|
||||||
|
"title" => Some(SortKey::Title),
|
||||||
|
"duration" => Some(SortKey::Duration),
|
||||||
|
"album" => Some(SortKey::Album),
|
||||||
|
"added" => Some(SortKey::Added),
|
||||||
|
"artist" => Some(SortKey::Artist),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
if sort_key.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sort_direction = args
|
||||||
|
.get(1)
|
||||||
|
.and_then(|direction| match *direction {
|
||||||
|
"a" => Some(SortDirection::Ascending),
|
||||||
|
"asc" => Some(SortDirection::Ascending),
|
||||||
|
"ascending" => Some(SortDirection::Ascending),
|
||||||
|
"d" => Some(SortDirection::Descending),
|
||||||
|
"desc" => Some(SortDirection::Descending),
|
||||||
|
"descending" => Some(SortDirection::Descending),
|
||||||
|
_ => Some(SortDirection::Ascending),
|
||||||
|
})
|
||||||
|
.unwrap_or(SortDirection::Ascending);
|
||||||
|
|
||||||
|
Some(Command::Sort(sort_key.unwrap(), sort_direction))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
"noop" => Some(Command::Noop),
|
"noop" => Some(Command::Noop),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ impl Playlist {
|
|||||||
while let Some(ref tracks) = tracks_result.clone() {
|
while let Some(ref tracks) = tracks_result.clone() {
|
||||||
for listtrack in &tracks.items {
|
for listtrack in &tracks.items {
|
||||||
if let Some(track) = &listtrack.track {
|
if let Some(track) = &listtrack.track {
|
||||||
collected_tracks.push(track.into());
|
let mut t: Track = track.into();
|
||||||
|
t.added_at = Some(listtrack.added_at);
|
||||||
|
collected_tracks.push(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug!("got {} tracks", tracks.items.len());
|
debug!("got {} tracks", tracks.items.len());
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ use unicode_width::UnicodeWidthStr;
|
|||||||
|
|
||||||
use crate::album::Album;
|
use crate::album::Album;
|
||||||
use crate::artist::Artist;
|
use crate::artist::Artist;
|
||||||
use crate::command::{Command, GotoMode, JumpMode, MoveAmount, MoveMode, TargetMode};
|
use crate::command::{
|
||||||
|
Command, GotoMode, JumpMode, MoveAmount, MoveMode, SortDirection, SortKey, TargetMode,
|
||||||
|
};
|
||||||
use crate::commands::CommandResult;
|
use crate::commands::CommandResult;
|
||||||
use crate::episode::Episode;
|
use crate::episode::Episode;
|
||||||
use crate::library::Library;
|
use crate::library::Library;
|
||||||
@@ -190,6 +192,58 @@ impl<I: ListItem> ListView<I> {
|
|||||||
let mut c = self.content.write().unwrap();
|
let mut c = self.content.write().unwrap();
|
||||||
c.remove(index);
|
c.remove(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sort(&self, key: &SortKey, direction: &SortDirection) {
|
||||||
|
fn compare_artists(a: Vec<String>, b: Vec<String>) -> Ordering {
|
||||||
|
let sanitize_artists_name = |x: Vec<String>| -> Vec<String> {
|
||||||
|
x.iter()
|
||||||
|
.map(|x| {
|
||||||
|
x.to_lowercase()
|
||||||
|
.split(' ')
|
||||||
|
.skip_while(|x| x == &"the")
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
let a = sanitize_artists_name(a);
|
||||||
|
let b = sanitize_artists_name(b);
|
||||||
|
|
||||||
|
a.cmp(&b)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut c = self.content.write().unwrap();
|
||||||
|
|
||||||
|
c.sort_by(|a, b| match (a.track(), b.track()) {
|
||||||
|
(Some(a), Some(b)) => match (key, direction) {
|
||||||
|
(SortKey::Title, SortDirection::Ascending) => {
|
||||||
|
a.title.to_lowercase().cmp(&b.title.to_lowercase())
|
||||||
|
}
|
||||||
|
(SortKey::Title, SortDirection::Descending) => {
|
||||||
|
b.title.to_lowercase().cmp(&a.title.to_lowercase())
|
||||||
|
}
|
||||||
|
(SortKey::Duration, SortDirection::Ascending) => a.duration.cmp(&b.duration),
|
||||||
|
(SortKey::Duration, SortDirection::Descending) => b.duration.cmp(&a.duration),
|
||||||
|
(SortKey::Album, SortDirection::Ascending) => a
|
||||||
|
.album
|
||||||
|
.map(|x| x.to_lowercase())
|
||||||
|
.cmp(&b.album.map(|x| x.to_lowercase())),
|
||||||
|
(SortKey::Album, SortDirection::Descending) => b
|
||||||
|
.album
|
||||||
|
.map(|x| x.to_lowercase())
|
||||||
|
.cmp(&a.album.map(|x| x.to_lowercase())),
|
||||||
|
(SortKey::Added, SortDirection::Ascending) => a.added_at.cmp(&b.added_at),
|
||||||
|
(SortKey::Added, SortDirection::Descending) => b.added_at.cmp(&a.added_at),
|
||||||
|
(SortKey::Artist, SortDirection::Ascending) => {
|
||||||
|
compare_artists(a.artists, b.artists)
|
||||||
|
}
|
||||||
|
(SortKey::Artist, SortDirection::Descending) => {
|
||||||
|
compare_artists(b.artists, a.artists)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => std::cmp::Ordering::Equal,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: ListItem> View for ListView<I> {
|
impl<I: ListItem> View for ListView<I> {
|
||||||
|
|||||||
@@ -72,6 +72,11 @@ impl ViewExt for PlaylistView {
|
|||||||
return Ok(CommandResult::Consumed(None));
|
return Ok(CommandResult::Consumed(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Command::Sort(key, direction) = cmd {
|
||||||
|
self.list.sort(key, direction);
|
||||||
|
return Ok(CommandResult::Consumed(None))
|
||||||
|
}
|
||||||
|
|
||||||
self.list.on_command(s, cmd)
|
self.list.on_command(s, cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user