added "Add to Playlist" button to context menu

This commit is contained in:
MrDulfin 2025-05-27 06:55:17 -04:00
parent 99bf0b6855
commit bc79718dd5
8 changed files with 118 additions and 38 deletions

View file

@ -109,14 +109,12 @@ pub enum LibraryCommand {
AllSongs,
GetLibrary,
ExternalPlaylist(Uuid),
PlaylistSong{
list_uuid: Uuid,
item_uuid: Uuid
},
PlaylistSong { list_uuid: Uuid, item_uuid: Uuid },
Playlist(Uuid),
ImportM3UPlayList(PathBuf),
Save,
Playlists,
PlaylistAddSong { playlist: Uuid, song: Uuid },
}
#[derive(Debug, Clone)]

View file

@ -74,6 +74,15 @@ impl ControllerHandle {
Ok((uuid, name))
}
pub async fn playlist_add_song(&self, playlist: Uuid, song: Uuid) {
let (command, tx) =
LibraryCommandInput::command(LibraryCommand::PlaylistAddSong { playlist, song });
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,

View file

@ -89,15 +89,27 @@ impl Controller {
.await
.unwrap();
}
LibraryCommand::PlaylistSong { list_uuid, item_uuid } => {
let playlist= library.playlists.query_uuid(&list_uuid).unwrap();
let Some((uuid, index)) = playlist.query_uuid(&item_uuid) else { todo!() };
let Some((song, _)) = library.query_uuid(uuid) else { todo!() };
LibraryCommand::PlaylistSong {
list_uuid,
item_uuid,
} => {
let playlist = library.playlists.query_uuid(&list_uuid).unwrap();
let Some((uuid, index)) = playlist.query_uuid(&item_uuid) else {
todo!()
};
let Some((song, _)) = library.query_uuid(uuid) else {
todo!()
};
res_rx
.send(LibraryResponse::PlaylistSong(song.clone(), index))
.await
.unwrap();
}
LibraryCommand::PlaylistAddSong { playlist, song } => {
let playlist = library.query_playlist_uuid_mut(&playlist).unwrap();
playlist.add_track(song);
res_rx.send(LibraryResponse::Ok).await.unwrap();
}
_ => {
todo!()
}

View file

@ -1232,6 +1232,10 @@ impl MusicLibrary {
self.playlists.query_uuid(uuid)
}
pub fn query_playlist_uuid_mut(&mut self, uuid: &Uuid) -> Option<&mut Playlist> {
self.playlists.query_uuid_mut(uuid)
}
pub fn push_playlist(&mut self, playlist: PlaylistFolderItem) {
self.playlists.items.push(playlist);
}

View file

@ -55,6 +55,20 @@ impl PlaylistFolder {
None
}
pub fn query_uuid_mut(&mut self, uuid: &Uuid) -> Option<&mut Playlist> {
for item in &mut self.items {
match item {
PlaylistFolderItem::Folder(folder) => return folder.query_uuid_mut(uuid),
PlaylistFolderItem::List(playlist) => {
if &playlist.uuid == uuid {
return Some(playlist);
}
}
}
}
None
}
pub fn lists_recursive(&self) -> Vec<&Playlist> {
let mut vec = vec![];
for item in &self.items {

View file

@ -24,8 +24,8 @@ use uuid::Uuid;
use wrappers::{_Song, stop};
use crate::wrappers::{
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, get_library, get_playlist, get_playlists, get_queue, get_song,
import_playlist, next, pause, play, prev, remove_from_queue, seek, set_volume,
};
use commands::{add_song_to_queue, display_album_art, last_fm_init_auth, play_now};
@ -69,6 +69,7 @@ pub fn run() {
save_config,
close_window,
start_controller,
add_song_to_playlist,
// test_menu,
])
.manage(tempfile::TempDir::new().unwrap())

View file

@ -1,4 +1,4 @@
use std::{collections::BTreeMap, path::PathBuf};
use std::{collections::BTreeMap, path::PathBuf, thread::spawn};
use chrono::{serde::ts_milliseconds_option, DateTime, Utc};
use crossbeam::channel::Sender;
@ -11,6 +11,7 @@ use dmp_core::{
};
use itertools::Itertools;
use serde::Serialize;
use tauri::{AppHandle, Emitter, State, Wry};
use uuid::Uuid;
@ -217,14 +218,18 @@ pub async fn get_playlists(
ctrl_handle: State<'_, ControllerHandle>,
) -> Result<(), String> {
let lists = ctrl_handle.playlist_get_all().await;
app.emit(
"playlists_gotten",
lists
.into_iter()
.map(|(uuid, name)| PlaylistPayload { uuid, name })
.collect_vec(),
)
.unwrap();
spawn(move || {
futures::executor::block_on(async {
app.emit(
"playlists_gotten",
lists
.into_iter()
.map(|(uuid, name)| PlaylistPayload { uuid, name })
.collect_vec(),
)
.unwrap();
})
});
Ok(())
}
@ -272,3 +277,12 @@ pub async fn get_song(
pub async fn seek(ctrl_handle: State<'_, ControllerHandle>, time: i64) -> Result<(), String> {
ctrl_handle.seek(time).await.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn add_song_to_playlist(
ctrl_handle: State<'_, ControllerHandle>,
song: Uuid,
playlist: Uuid,
) -> Result<(), String> {
Ok(ctrl_handle.playlist_add_song(playlist, song).await)
}

View file

@ -1,4 +1,4 @@
import React, { createRef, ReactEventHandler, useEffect, useRef, useState } from "react";
import React, { createRef, MutableRefObject, ReactEventHandler, useEffect, useRef, useState } from "react";
import { convertFileSrc, invoke } from "@tauri-apps/api/core";
import "./App.css";
import { Config, playbackInfo } from "./types";
@ -7,7 +7,7 @@ import { Config, playbackInfo } from "./types";
// import { fetch } from "@tauri-apps/plugin-http";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { getCurrentWindow, LogicalPosition } from "@tauri-apps/api/window";
import { Menu } from "@tauri-apps/api/menu";
import { Menu, Submenu, SubmenuOptions } from "@tauri-apps/api/menu";
import { listen } from "@tauri-apps/api/event";
const appWindow = getCurrentWebviewWindow();
@ -18,6 +18,7 @@ function App() {
const [playing, setPlaying] = useState(false);
const [playlists, setPlaylists] = useState<JSX.Element[]>([]);
const [viewName, setViewName] = useState("Library");
const playlistsInfo= useRef<PlaylistInfo[]>([]);
const [nowPlaying, setNowPlaying] = useState<JSX.Element>(
<NowPlaying
@ -83,8 +84,8 @@ function App() {
<main>
<div className="container">
<div className="leftSide">
<PlaylistHead playlists={ playlists } setPlaylists={ setPlaylists } setViewName={ setViewName } setLibrary={ library[1] } />
<MainView lib_ref={ library } viewName={ viewName } />
<PlaylistHead playlists={ playlists } setPlaylists={ setPlaylists } setViewName={ setViewName } setLibrary={ library[1] } playlistsInfo={ playlistsInfo }/>
<MainView lib_ref={ library } viewName={ viewName } playlistsInfo={ playlistsInfo } />
</div>
<div className="rightSide">
{ nowPlaying }
@ -100,22 +101,32 @@ function App() {
export default App;
interface PlaylistInfo {
uuid: string,
name: string,
}
interface PlaylistHeadProps {
playlists: JSX.Element[]
setPlaylists: React.Dispatch<React.SetStateAction<JSX.Element[]>>,
setViewName: React.Dispatch<React.SetStateAction<string>>,
setLibrary: React.Dispatch<React.SetStateAction<JSX.Element[]>>,
playlistsInfo: MutableRefObject<PlaylistInfo[]>,
}
function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: PlaylistHeadProps) {
function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary, playlistsInfo }: PlaylistHeadProps) {
useEffect(() => {
const unlisten = appWindow.listen<any[]>("playlists_gotten", (_res) => {
// console.log(event);
let res = _res.payload;
let res = _res.payload as PlaylistInfo[];
playlistsInfo.current = [...res];
console.log(playlistsInfo, res);
setPlaylists([
...res.map( (item) => {
return (
<button onClick={ () => {
invoke('get_playlist', { uuid: item.uuid }).then((list) => {
@ -130,6 +141,7 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: Play
plays={ song.plays }
duration={ song.duration }
tags={ song.tags }
playlists={ playlistsInfo }
/>
)
})])
@ -163,6 +175,7 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: Play
plays={ song.plays }
duration={ song.duration }
tags={ song.tags }
playlists={ playlistsInfo }
/>
)
})])
@ -190,6 +203,7 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: Play
plays={ song.plays }
duration={ song.duration }
tags={ song.tags }
playlists={ playlistsInfo }
/>
)
})])
@ -204,10 +218,11 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: Play
interface MainViewProps {
lib_ref: [JSX.Element[], React.Dispatch<React.SetStateAction<JSX.Element[]>>],
viewName: string
viewName: string,
playlistsInfo: MutableRefObject<PlaylistInfo[]>,
}
function MainView({ lib_ref, viewName }: MainViewProps) {
function MainView({ lib_ref, viewName, playlistsInfo }: MainViewProps) {
const [library, setLibrary] = lib_ref;
useEffect(() => {
@ -225,6 +240,7 @@ function MainView({ lib_ref, viewName }: MainViewProps) {
plays={ song.plays }
duration={ song.duration }
tags={ song.tags }
playlists={ playlistsInfo }
/>
)
})])
@ -255,7 +271,8 @@ interface SongProps {
last_played?: string,
date_added?: string,
date_modified?: string,
tags: any
tags: any,
playlists: MutableRefObject<PlaylistInfo[]>
}
function Song(props: SongProps) {
@ -264,15 +281,27 @@ function Song(props: SongProps) {
invoke('add_song_to_queue', { uuid: props.uuid, location: props.playerLocation }).then(() => {});
}
const songMenuPromise = Menu.new({
items: [
{ id: "add_song_to_queue" + props.uuid, text: "Add to Queue", action: add_to_queue_test}
]
})
async function clickHandler(event: React.MouseEvent) {
event.preventDefault();
const menu = await songMenuPromise;
console.log(props.playlists);
const _ = await invoke('get_playlists');
const menu = await Menu.new({
items: [
{ id: "add_song_to_queue" + props.uuid, text: "Add to Queue", action: add_to_queue_test },
await Submenu.new(
{
text: "Add to Playlist...",
items: [...props.playlists.current.map((list) => {
const addToPlaylist = () => {
invoke('add_song_to_playlist', { playlist: list.uuid, song: props.uuid }).then(() => {});
}
return { id: "add_song_to_playlists" + props.uuid + list.uuid, text: list.name, action: addToPlaylist }
})]
} as SubmenuOptions
)
]
})
;
const pos = new LogicalPosition(event.clientX, event.clientY);
menu.popup(pos);
}
@ -419,11 +448,10 @@ interface QueueSongProps {
function QueueSong({ song, location, index }: QueueSongProps) {
// console.log(song.tags);
let removeFromQueue = () => {
const removeFromQueue = () => {
invoke('remove_from_queue', { index: index }).then(() => {})
}
let playNow = () => {
const playNow = () => {
invoke('play_now', { uuid: song.uuid, location: location }).then(() => {})
}