From 158e80cc8d98b9770a4bb3d801ce728407eead83 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Sun, 1 Jun 2025 22:02:08 -0400 Subject: [PATCH] Added context menu for QueueSongs and Playlists --- dmp-core/src/music_controller/controller.rs | 4 +- .../src/music_controller/controller_handle.rs | 32 ++++++++++++- .../src/music_controller/library_command.rs | 19 +++++++- .../src/music_controller/queue_command.rs | 16 ++++++- dmp-core/src/music_storage/library.rs | 20 +++++++++ dmp-core/src/music_storage/playlist.rs | 20 +++++++++ dmp-core/src/music_storage/queue.rs | 8 ++-- src-tauri/src/lib.rs | 9 ++-- src-tauri/src/wrappers.rs | 25 ++++++++++- src/App.tsx | 45 ++++++++++++++++--- 10 files changed, 179 insertions(+), 19 deletions(-) diff --git a/dmp-core/src/music_controller/controller.rs b/dmp-core/src/music_controller/controller.rs index a7d9648..2e19d6c 100644 --- a/dmp-core/src/music_controller/controller.rs +++ b/dmp-core/src/music_controller/controller.rs @@ -115,6 +115,7 @@ pub enum LibraryCommand { Save, Playlists, PlaylistAddSong { playlist: Uuid, song: Uuid }, + DeletePlaylist(Uuid), } #[derive(Debug, Clone)] @@ -132,7 +133,7 @@ pub enum LibraryResponse { #[derive(Debug, PartialEq, Clone)] pub enum QueueCommand { - Append(QueueItem_, bool), + Append(QueueItem, bool), Next, Prev, GetIndex(usize), @@ -140,6 +141,7 @@ pub enum QueueCommand { Get, Clear, Remove(usize), + PlayNext(QueueItem, bool), } #[derive(Debug, PartialEq, Clone)] diff --git a/dmp-core/src/music_controller/controller_handle.rs b/dmp-core/src/music_controller/controller_handle.rs index cb8fb0c..9aa1d36 100644 --- a/dmp-core/src/music_controller/controller_handle.rs +++ b/dmp-core/src/music_controller/controller_handle.rs @@ -1,12 +1,13 @@ use std::path::PathBuf; use async_channel::{Receiver, Sender}; +use discord_presence::models::Command; use uuid::Uuid; use crate::music_storage::{ library::Song, playlist::ExternalPlaylist, - queue::{QueueError, QueueItem}, + queue::{QueueError, QueueItem, QueueItemType}, }; use super::{ @@ -83,6 +84,14 @@ impl ControllerHandle { }; } + pub async fn playlist_delete(&self, playlist: Uuid) { + let (command, tx) = LibraryCommandInput::command(LibraryCommand::DeletePlaylist(playlist)); + self.lib_mail_rx.send(command).await.unwrap(); + let LibraryResponse::Ok = tx.recv().await.unwrap() else { + unreachable!() + }; + } + // The Queue Section pub async fn queue_append( &self, @@ -117,6 +126,27 @@ impl ControllerHandle { queue } + pub async fn queue_play_next( + &self, + uuid: Uuid, + location: PlayerLocation, + ) -> Result<(), QueueError> { + let (command, tx) = LibraryCommandInput::command(LibraryCommand::Song(uuid)); + self.lib_mail_rx.send(command).await.unwrap(); + let LibraryResponse::Song(song, _) = tx.recv().await.unwrap() else { + unimplemented!() + }; + let (command, tx) = QueueCommandInput::command(QueueCommand::PlayNext( + QueueItem::from_item_type(QueueItemType::from_single(QueueSong { song, location })), + false, + )); + self.queue_mail_rx.send(command).await.unwrap(); + let QueueResponse::Empty(_) = tx.recv().await.unwrap() else { + unimplemented!() + }; + Ok(()) + } + // The Player Section pub async fn play_now(&self, uuid: Uuid, location: PlayerLocation) -> Result { let (command, tx) = PlayerCommandInput::command(PlayerCommand::PlayNow(uuid, location)); diff --git a/dmp-core/src/music_controller/library_command.rs b/dmp-core/src/music_controller/library_command.rs index 01b3cfe..d99bed0 100644 --- a/dmp-core/src/music_controller/library_command.rs +++ b/dmp-core/src/music_controller/library_command.rs @@ -6,7 +6,7 @@ use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterato use crate::{ config::Config, music_storage::{ - library::MusicLibrary, + library::{self, MusicLibrary}, playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem}, }, }; @@ -108,6 +108,23 @@ impl Controller { LibraryCommand::PlaylistAddSong { playlist, song } => { let playlist = library.query_playlist_uuid_mut(&playlist).unwrap(); playlist.add_track(song); + library.save(config.read().path.clone()).unwrap(); + res_rx.send(LibraryResponse::Ok).await.unwrap(); + } + LibraryCommand::DeletePlaylist(uuid) => { + _ = library.playlists.delete_uuid(uuid); + let lib_uuid = library.uuid; + library + .save( + config + .read() + .libraries + .get_library(&lib_uuid) + .unwrap() + .path + .clone(), + ) + .unwrap(); res_rx.send(LibraryResponse::Ok).await.unwrap(); } _ => { diff --git a/dmp-core/src/music_controller/queue_command.rs b/dmp-core/src/music_controller/queue_command.rs index caafc48..2e9ce0b 100644 --- a/dmp-core/src/music_controller/queue_command.rs +++ b/dmp-core/src/music_controller/queue_command.rs @@ -1,4 +1,7 @@ -use crate::music_storage::queue::{Queue, QueueError, QueueItemType}; +use crate::music_storage::{ + library::Song, + queue::{Queue, QueueError, QueueItemType}, +}; use super::{ controller::{Controller, QueueCommand, QueueResponse}, @@ -69,6 +72,17 @@ impl Controller { .await .unwrap(); } + QueueCommand::PlayNext(item, by_human) => { + match item.item { + QueueItemType::Single(song) => { + queue.add_item_next(song); + } + QueueItemType::Multi(album) => { + unimplemented!() + } + }; + res_rx.send(QueueResponse::Empty(Ok(()))).await.unwrap(); + } } } } diff --git a/dmp-core/src/music_storage/library.rs b/dmp-core/src/music_storage/library.rs index e316cc5..8e2c36d 100644 --- a/dmp-core/src/music_storage/library.rs +++ b/dmp-core/src/music_storage/library.rs @@ -18,6 +18,7 @@ use file_format::{FileFormat, Kind}; use lofty::file::{AudioFile as _, TaggedFileExt as _}; use lofty::probe::Probe; use lofty::tag::{ItemKey, ItemValue, TagType}; +use rayon::iter::plumbing::Folder; use rcue::parser::parse_from_file; use std::fs; use std::path::{Path, PathBuf}; @@ -1239,6 +1240,25 @@ impl MusicLibrary { pub fn push_playlist(&mut self, playlist: PlaylistFolderItem) { self.playlists.items.push(playlist); } + + pub fn delete_playlist(&mut self, uuid: Uuid) -> Option { + let mut index = None; + for (i, item) in self.playlists.items.iter_mut().enumerate() { + match item { + PlaylistFolderItem::Folder(folder) => return folder.delete_uuid(uuid), + PlaylistFolderItem::List(list) => { + if list.uuid == uuid { + index = Some(i); + } + } + } + } + if let Some(i) = index { + Some(self.playlists.items.remove(i)) + } else { + None + } + } } #[cfg(test)] diff --git a/dmp-core/src/music_storage/playlist.rs b/dmp-core/src/music_storage/playlist.rs index 1d510ff..7880771 100644 --- a/dmp-core/src/music_storage/playlist.rs +++ b/dmp-core/src/music_storage/playlist.rs @@ -11,6 +11,7 @@ use std::time::Duration; // use chrono::Duration; use super::library::{AlbumArt, MusicLibrary, Song, Tag, URI}; +use chrono::format::Item; use itertools::Itertools; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -79,6 +80,25 @@ impl PlaylistFolder { } vec } + + pub fn delete_uuid(&mut self, uuid: Uuid) -> Option { + let mut index = None; + for (i, item) in &mut self.items.iter_mut().enumerate() { + match item { + PlaylistFolderItem::Folder(folder) => return folder.delete_uuid(uuid), + PlaylistFolderItem::List(playlist) => { + if playlist.uuid == uuid { + index = Some(i); + } + } + } + } + if let Some(i) = index { + Some(self.items.remove(i)) + } else { + None + } + } } #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/dmp-core/src/music_storage/queue.rs b/dmp-core/src/music_storage/queue.rs index 8e4b929..1556313 100644 --- a/dmp-core/src/music_storage/queue.rs +++ b/dmp-core/src/music_storage/queue.rs @@ -30,9 +30,9 @@ pub enum QueueItemType< } impl< - T: Debug + Clone + PartialEq, // T: The Singular Item Type - U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items - > QueueItemType + T: Debug + Clone + PartialEq, // T: The Singular Item Type + U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items +> QueueItemType { pub fn from_single(item: T) -> Self { QueueItemType::Single(item) @@ -219,7 +219,7 @@ impl } /// Add multiple Items after the currently playing Item - pub fn add_multi_next(&mut self, items: Vec>) { + pub fn add_multiple_next(&mut self, items: Vec>) { use QueueState::*; let empty = self.items.is_empty(); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ccf9e63..a86b988 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -19,13 +19,14 @@ use dmp_core::{ music_storage::library::{MusicLibrary, Song}, }; use parking_lot::RwLock; -use tauri::{http::Response, AppHandle, Emitter, Manager}; +use tauri::{AppHandle, Emitter, Manager, http::Response}; use uuid::Uuid; use wrappers::{_Song, stop}; use crate::wrappers::{ - add_song_to_playlist, get_library, get_playlist, get_playlists, get_queue, get_song, - import_playlist, next, pause, play, prev, remove_from_queue, seek, set_volume, + add_song_to_playlist, delete_playlist, get_library, get_playlist, get_playlists, get_queue, + get_song, import_playlist, next, pause, play, play_next_queue, prev, remove_from_queue, seek, + set_volume, }; use commands::{add_song_to_queue, display_album_art, last_fm_init_auth, play_now}; @@ -70,6 +71,8 @@ pub fn run() { close_window, start_controller, add_song_to_playlist, + delete_playlist, + play_next_queue, // test_menu, ]) .manage(tempfile::TempDir::new().unwrap()) diff --git a/src-tauri/src/wrappers.rs b/src-tauri/src/wrappers.rs index 7c11116..f3c5c5c 100644 --- a/src-tauri/src/wrappers.rs +++ b/src-tauri/src/wrappers.rs @@ -1,6 +1,6 @@ use std::{collections::BTreeMap, path::PathBuf, thread::spawn}; -use chrono::{serde::ts_milliseconds_option, DateTime, Utc}; +use chrono::{DateTime, Utc, serde::ts_milliseconds_option}; use crossbeam::channel::Sender; use dmp_core::{ music_controller::controller::{ControllerHandle, PlayerLocation}, @@ -286,3 +286,26 @@ pub async fn add_song_to_playlist( ) -> Result<(), String> { Ok(ctrl_handle.playlist_add_song(playlist, song).await) } + +#[tauri::command] +pub async fn delete_playlist( + ctrl_handle: State<'_, ControllerHandle>, + uuid: Uuid, +) -> Result<(), String> { + Ok(ctrl_handle.playlist_delete(uuid).await) +} + +#[tauri::command] +pub async fn play_next_queue( + app: AppHandle, + ctrl_handle: State<'_, ControllerHandle>, + uuid: Uuid, + location: PlayerLocation, +) -> Result<(), String> { + let res = ctrl_handle + .queue_play_next(uuid, location) + .await + .map_err(|e| e.to_string()); + app.emit("queue_updated", ()).unwrap(); + res +} diff --git a/src/App.tsx b/src/App.tsx index 1eb2166..7d0340c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -125,18 +125,32 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary, playli console.log(playlistsInfo, res); setPlaylists([ - ...res.map( (item) => { + ...res.map( (list) => { + + const deletePlaylist = () => { + invoke('delete_playlist', { uuid: list.uuid }).then(() => {}); + invoke('get_playlists').then(() => {}); + } + async function menuHandler(event: React.MouseEvent) { + event.preventDefault(); + const menu = await Menu.new({ + items: [ + { id: "delete_playlist" + list.uuid, text: "Delete Playlist", action: deletePlaylist } + ] + }); + menu.popup(); + } return ( + setViewName( list.name ) + + } } + onContextMenu={ menuHandler } + key={ 'playlist_' + list.uuid }>{ list.name } ) }) ]) @@ -164,6 +181,7 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary, playli invoke('get_playlist', { uuid: res.uuid }).then((list) => { console.log((list as any[]).length); + setLibrary([...(list as any[]).map((song) => { // console.log(song); return ( @@ -454,9 +472,22 @@ function QueueSong({ song, location, index }: QueueSongProps) { const playNow = () => { invoke('play_now', { uuid: song.uuid, location: location }).then(() => {}) } + const play_next = () => invoke('play_next_queue', { uuid: song.uuid, location }).then(() => {}); + + async function menuHandler(event: React.MouseEvent) { + event.preventDefault(); + + const menu = await Menu.new({ + items: [ + { id: "play_next_" + song.uuid + index, text: "Play Next in Queue", action: play_next }, + { id: "remove_queue" + song.uuid + index, text: "Remove from Queue", action: removeFromQueue } + ] + }) + menu.popup(); + } return ( -
+

{ song.tags.TrackTitle }