@@ -489,6 +489,31 @@ impl Library {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn playlist_append_tracks(&self, playlist_id: &str, new_tracks: &[Track]) {
|
||||||
|
let track_ids: Vec<String> = new_tracks
|
||||||
|
.to_vec()
|
||||||
|
.iter()
|
||||||
|
.filter(|t| t.id.is_some())
|
||||||
|
.map(|t| t.id.clone().unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut has_modified = false;
|
||||||
|
|
||||||
|
if self.spotify.append_tracks(playlist_id, &track_ids, None) {
|
||||||
|
let mut playlists = self.playlists.write().expect("can't writelock playlists");
|
||||||
|
if let Some(playlist) = playlists.iter_mut().find(|p| p.id == playlist_id) {
|
||||||
|
if let Some(tracks) = &mut playlist.tracks {
|
||||||
|
tracks.append(&mut new_tracks.to_vec());
|
||||||
|
has_modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_modified {
|
||||||
|
self.save_cache(config::cache_path(CACHE_PLAYLISTS), self.playlists.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_saved_track(&self, track: &Track) -> bool {
|
pub fn is_saved_track(&self, track: &Track) -> bool {
|
||||||
if !*self.is_done.read().unwrap() {
|
if !*self.is_done.read().unwrap() {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -415,6 +415,18 @@ impl Spotify {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn append_tracks(
|
||||||
|
&self,
|
||||||
|
playlist_id: &str,
|
||||||
|
tracks: &[String],
|
||||||
|
position: Option<i32>,
|
||||||
|
) -> bool {
|
||||||
|
self.api_with_retry(|api| {
|
||||||
|
api.user_playlist_add_tracks(&self.user, playlist_id, &tracks, position)
|
||||||
|
})
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn overwrite_playlist(&self, id: &str, tracks: &[Track]) {
|
pub fn overwrite_playlist(&self, id: &str, tracks: &[Track]) {
|
||||||
// extract only track IDs
|
// extract only track IDs
|
||||||
let mut tracks: Vec<String> = tracks
|
let mut tracks: Vec<String> = tracks
|
||||||
@@ -443,11 +455,7 @@ impl Spotify {
|
|||||||
};
|
};
|
||||||
|
|
||||||
debug!("adding another {} tracks to playlist", tracks.len());
|
debug!("adding another {} tracks to playlist", tracks.len());
|
||||||
let result = self.api_with_retry(|api| {
|
if self.append_tracks(id, tracks, None) {
|
||||||
api.user_playlist_add_tracks(&self.user, id, &tracks, None)
|
|
||||||
});
|
|
||||||
|
|
||||||
if result.is_some() {
|
|
||||||
debug!("{} tracks successfully added", tracks.len());
|
debug!("{} tracks successfully added", tracks.len());
|
||||||
} else {
|
} else {
|
||||||
error!("error saving tracks to playlists {}", id);
|
error!("error saving tracks to playlists {}", id);
|
||||||
|
|||||||
@@ -222,4 +222,8 @@ impl ListItem for Track {
|
|||||||
self.artists[0].clone(),
|
self.artists[0].clone(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn track(&self) -> Option<Track> {
|
||||||
|
Some(self.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use command::Command;
|
|||||||
use commands::CommandResult;
|
use commands::CommandResult;
|
||||||
use library::Library;
|
use library::Library;
|
||||||
use queue::Queue;
|
use queue::Queue;
|
||||||
|
use track::Track;
|
||||||
|
|
||||||
pub trait ListItem: Sync + Send + 'static {
|
pub trait ListItem: Sync + Send + 'static {
|
||||||
fn is_playing(&self, queue: Arc<Queue>) -> bool;
|
fn is_playing(&self, queue: Arc<Queue>) -> bool;
|
||||||
@@ -31,6 +32,10 @@ pub trait ListItem: Sync + Send + 'static {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn track(&self) -> Option<Track> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn as_listitem(&self) -> Box<dyn ListItem>;
|
fn as_listitem(&self) -> Box<dyn ListItem>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use cursive::view::ViewWrapper;
|
use cursive::view::ViewWrapper;
|
||||||
use cursive::views::{Dialog, SelectView};
|
use cursive::views::{Dialog, ScrollView, SelectView};
|
||||||
use cursive::Cursive;
|
use cursive::Cursive;
|
||||||
|
|
||||||
#[cfg(feature = "share_clipboard")]
|
#[cfg(feature = "share_clipboard")]
|
||||||
use clipboard::{ClipboardContext, ClipboardProvider};
|
use clipboard::{ClipboardContext, ClipboardProvider};
|
||||||
use library::Library;
|
use library::Library;
|
||||||
use queue::Queue;
|
use queue::Queue;
|
||||||
|
use track::Track;
|
||||||
use traits::ListItem;
|
use traits::ListItem;
|
||||||
use ui::layout::Layout;
|
use ui::layout::Layout;
|
||||||
use ui::modal::Modal;
|
use ui::modal::Modal;
|
||||||
@@ -19,9 +20,30 @@ pub struct ContextMenu {
|
|||||||
enum ContextMenuAction {
|
enum ContextMenuAction {
|
||||||
ShowItem(Box<dyn ListItem>),
|
ShowItem(Box<dyn ListItem>),
|
||||||
ShareUrl(String),
|
ShareUrl(String),
|
||||||
|
AddToPlaylist(Box<Track>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextMenu {
|
impl ContextMenu {
|
||||||
|
pub fn add_track_dialog(library: Arc<Library>, track: Track) -> Modal<Dialog> {
|
||||||
|
let mut list_select: SelectView<String> = SelectView::new().autojump();
|
||||||
|
|
||||||
|
for list in library.items().iter() {
|
||||||
|
list_select.add_item(list.name.clone(), list.id.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
list_select.set_on_submit(move |s, selected| {
|
||||||
|
library.playlist_append_tracks(selected, &[track.clone()]);
|
||||||
|
s.pop_layer();
|
||||||
|
});
|
||||||
|
|
||||||
|
let dialog = Dialog::new()
|
||||||
|
.title("Add track to playlist")
|
||||||
|
.dismiss_button("Cancel")
|
||||||
|
.padding((1, 1, 1, 0))
|
||||||
|
.content(ScrollView::new(list_select));
|
||||||
|
Modal::new(dialog)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(item: &dyn ListItem, queue: Arc<Queue>, library: Arc<Library>) -> Self {
|
pub fn new(item: &dyn ListItem, queue: Arc<Queue>, library: Arc<Library>) -> Self {
|
||||||
let mut content: SelectView<ContextMenuAction> = SelectView::new().autojump();
|
let mut content: SelectView<ContextMenuAction> = SelectView::new().autojump();
|
||||||
if let Some(a) = item.artist() {
|
if let Some(a) = item.artist() {
|
||||||
@@ -34,27 +56,33 @@ impl ContextMenu {
|
|||||||
#[cfg(feature = "share_clipboard")]
|
#[cfg(feature = "share_clipboard")]
|
||||||
content.add_item("Share", ContextMenuAction::ShareUrl(url));
|
content.add_item("Share", ContextMenuAction::ShareUrl(url));
|
||||||
}
|
}
|
||||||
|
if let Some(t) = item.track() {
|
||||||
|
content.add_item("Add to playlist", ContextMenuAction::AddToPlaylist(Box::new(t)))
|
||||||
|
}
|
||||||
|
|
||||||
// open detail view of artist/album
|
// open detail view of artist/album
|
||||||
content.set_on_submit(move |s: &mut Cursive, action: &ContextMenuAction| {
|
content.set_on_submit(move |s: &mut Cursive, action: &ContextMenuAction| {
|
||||||
s.pop_layer();
|
s.pop_layer();
|
||||||
let queue = queue.clone();
|
let queue = queue.clone();
|
||||||
let library = library.clone();
|
let library = library.clone();
|
||||||
s.call_on_id("main", move |v: &mut Layout| match action {
|
|
||||||
|
match action {
|
||||||
ContextMenuAction::ShowItem(item) => {
|
ContextMenuAction::ShowItem(item) => {
|
||||||
if let Some(view) = item.open(queue, library) {
|
if let Some(view) = item.open(queue, library) {
|
||||||
v.push_view(view)
|
s.call_on_id("main", move |v: &mut Layout| v.push_view(view));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ContextMenuAction::ShareUrl(url) => {
|
ContextMenuAction::ShareUrl(url) => {
|
||||||
#[cfg(feature = "share_clipboard")]
|
#[cfg(feature = "share_clipboard")]
|
||||||
{
|
ClipboardProvider::new()
|
||||||
ClipboardProvider::new()
|
.and_then(|mut ctx: ClipboardContext| ctx.set_contents(url.to_string()))
|
||||||
.and_then(|mut ctx: ClipboardContext| ctx.set_contents(url.to_string()))
|
.ok();
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
ContextMenuAction::AddToPlaylist(track) => {
|
||||||
|
let dialog = Self::add_track_dialog(library, *track.clone());
|
||||||
|
s.add_layer(dialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let dialog = Dialog::new()
|
let dialog = Dialog::new()
|
||||||
|
|||||||
Reference in New Issue
Block a user