mirror of
https://github.com/Dangoware/dango-music-player.git
synced 2025-06-22 22:52:59 -05:00
added "Add to Playlist" button to context menu
This commit is contained in:
parent
99bf0b6855
commit
bc79718dd5
8 changed files with 118 additions and 38 deletions
|
@ -109,14 +109,12 @@ pub enum LibraryCommand {
|
||||||
AllSongs,
|
AllSongs,
|
||||||
GetLibrary,
|
GetLibrary,
|
||||||
ExternalPlaylist(Uuid),
|
ExternalPlaylist(Uuid),
|
||||||
PlaylistSong{
|
PlaylistSong { list_uuid: Uuid, item_uuid: Uuid },
|
||||||
list_uuid: Uuid,
|
|
||||||
item_uuid: Uuid
|
|
||||||
},
|
|
||||||
Playlist(Uuid),
|
Playlist(Uuid),
|
||||||
ImportM3UPlayList(PathBuf),
|
ImportM3UPlayList(PathBuf),
|
||||||
Save,
|
Save,
|
||||||
Playlists,
|
Playlists,
|
||||||
|
PlaylistAddSong { playlist: Uuid, song: Uuid },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -74,6 +74,15 @@ impl ControllerHandle {
|
||||||
Ok((uuid, name))
|
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
|
// The Queue Section
|
||||||
pub async fn queue_append(
|
pub async fn queue_append(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -89,15 +89,27 @@ impl Controller {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
LibraryCommand::PlaylistSong { list_uuid, item_uuid } => {
|
LibraryCommand::PlaylistSong {
|
||||||
|
list_uuid,
|
||||||
|
item_uuid,
|
||||||
|
} => {
|
||||||
let playlist = library.playlists.query_uuid(&list_uuid).unwrap();
|
let playlist = library.playlists.query_uuid(&list_uuid).unwrap();
|
||||||
let Some((uuid, index)) = playlist.query_uuid(&item_uuid) else { todo!() };
|
let Some((uuid, index)) = playlist.query_uuid(&item_uuid) else {
|
||||||
let Some((song, _)) = library.query_uuid(uuid) else { todo!() };
|
todo!()
|
||||||
|
};
|
||||||
|
let Some((song, _)) = library.query_uuid(uuid) else {
|
||||||
|
todo!()
|
||||||
|
};
|
||||||
res_rx
|
res_rx
|
||||||
.send(LibraryResponse::PlaylistSong(song.clone(), index))
|
.send(LibraryResponse::PlaylistSong(song.clone(), index))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.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!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1232,6 +1232,10 @@ impl MusicLibrary {
|
||||||
self.playlists.query_uuid(uuid)
|
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) {
|
pub fn push_playlist(&mut self, playlist: PlaylistFolderItem) {
|
||||||
self.playlists.items.push(playlist);
|
self.playlists.items.push(playlist);
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,20 @@ impl PlaylistFolder {
|
||||||
None
|
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> {
|
pub fn lists_recursive(&self) -> Vec<&Playlist> {
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
for item in &self.items {
|
for item in &self.items {
|
||||||
|
|
|
@ -24,8 +24,8 @@ use uuid::Uuid;
|
||||||
use wrappers::{_Song, stop};
|
use wrappers::{_Song, stop};
|
||||||
|
|
||||||
use crate::wrappers::{
|
use crate::wrappers::{
|
||||||
get_library, get_playlist, get_playlists, get_queue, get_song, import_playlist, next, pause,
|
add_song_to_playlist, get_library, get_playlist, get_playlists, get_queue, get_song,
|
||||||
play, prev, remove_from_queue, seek, set_volume,
|
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};
|
use commands::{add_song_to_queue, display_album_art, last_fm_init_auth, play_now};
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@ pub fn run() {
|
||||||
save_config,
|
save_config,
|
||||||
close_window,
|
close_window,
|
||||||
start_controller,
|
start_controller,
|
||||||
|
add_song_to_playlist,
|
||||||
// test_menu,
|
// test_menu,
|
||||||
])
|
])
|
||||||
.manage(tempfile::TempDir::new().unwrap())
|
.manage(tempfile::TempDir::new().unwrap())
|
||||||
|
|
|
@ -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 chrono::{serde::ts_milliseconds_option, DateTime, Utc};
|
||||||
use crossbeam::channel::Sender;
|
use crossbeam::channel::Sender;
|
||||||
|
@ -11,6 +11,7 @@ use dmp_core::{
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use tauri::{AppHandle, Emitter, State, Wry};
|
use tauri::{AppHandle, Emitter, State, Wry};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -217,6 +218,8 @@ pub async fn get_playlists(
|
||||||
ctrl_handle: State<'_, ControllerHandle>,
|
ctrl_handle: State<'_, ControllerHandle>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let lists = ctrl_handle.playlist_get_all().await;
|
let lists = ctrl_handle.playlist_get_all().await;
|
||||||
|
spawn(move || {
|
||||||
|
futures::executor::block_on(async {
|
||||||
app.emit(
|
app.emit(
|
||||||
"playlists_gotten",
|
"playlists_gotten",
|
||||||
lists
|
lists
|
||||||
|
@ -225,6 +228,8 @@ pub async fn get_playlists(
|
||||||
.collect_vec(),
|
.collect_vec(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
})
|
||||||
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,3 +277,12 @@ pub async fn get_song(
|
||||||
pub async fn seek(ctrl_handle: State<'_, ControllerHandle>, time: i64) -> Result<(), String> {
|
pub async fn seek(ctrl_handle: State<'_, ControllerHandle>, time: i64) -> Result<(), String> {
|
||||||
ctrl_handle.seek(time).await.map_err(|e| e.to_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)
|
||||||
|
}
|
||||||
|
|
66
src/App.tsx
66
src/App.tsx
|
@ -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 { convertFileSrc, invoke } from "@tauri-apps/api/core";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { Config, playbackInfo } from "./types";
|
import { Config, playbackInfo } from "./types";
|
||||||
|
@ -7,7 +7,7 @@ import { Config, playbackInfo } from "./types";
|
||||||
// import { fetch } from "@tauri-apps/plugin-http";
|
// import { fetch } from "@tauri-apps/plugin-http";
|
||||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||||
import { getCurrentWindow, LogicalPosition } from "@tauri-apps/api/window";
|
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";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
|
||||||
const appWindow = getCurrentWebviewWindow();
|
const appWindow = getCurrentWebviewWindow();
|
||||||
|
@ -18,6 +18,7 @@ function App() {
|
||||||
const [playing, setPlaying] = useState(false);
|
const [playing, setPlaying] = useState(false);
|
||||||
const [playlists, setPlaylists] = useState<JSX.Element[]>([]);
|
const [playlists, setPlaylists] = useState<JSX.Element[]>([]);
|
||||||
const [viewName, setViewName] = useState("Library");
|
const [viewName, setViewName] = useState("Library");
|
||||||
|
const playlistsInfo= useRef<PlaylistInfo[]>([]);
|
||||||
|
|
||||||
const [nowPlaying, setNowPlaying] = useState<JSX.Element>(
|
const [nowPlaying, setNowPlaying] = useState<JSX.Element>(
|
||||||
<NowPlaying
|
<NowPlaying
|
||||||
|
@ -83,8 +84,8 @@ function App() {
|
||||||
<main>
|
<main>
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="leftSide">
|
<div className="leftSide">
|
||||||
<PlaylistHead playlists={ playlists } setPlaylists={ setPlaylists } setViewName={ setViewName } setLibrary={ library[1] } />
|
<PlaylistHead playlists={ playlists } setPlaylists={ setPlaylists } setViewName={ setViewName } setLibrary={ library[1] } playlistsInfo={ playlistsInfo }/>
|
||||||
<MainView lib_ref={ library } viewName={ viewName } />
|
<MainView lib_ref={ library } viewName={ viewName } playlistsInfo={ playlistsInfo } />
|
||||||
</div>
|
</div>
|
||||||
<div className="rightSide">
|
<div className="rightSide">
|
||||||
{ nowPlaying }
|
{ nowPlaying }
|
||||||
|
@ -100,22 +101,32 @@ function App() {
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
||||||
|
interface PlaylistInfo {
|
||||||
|
uuid: string,
|
||||||
|
name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
interface PlaylistHeadProps {
|
interface PlaylistHeadProps {
|
||||||
playlists: JSX.Element[]
|
playlists: JSX.Element[]
|
||||||
setPlaylists: React.Dispatch<React.SetStateAction<JSX.Element[]>>,
|
setPlaylists: React.Dispatch<React.SetStateAction<JSX.Element[]>>,
|
||||||
setViewName: React.Dispatch<React.SetStateAction<string>>,
|
setViewName: React.Dispatch<React.SetStateAction<string>>,
|
||||||
setLibrary: React.Dispatch<React.SetStateAction<JSX.Element[]>>,
|
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(() => {
|
useEffect(() => {
|
||||||
const unlisten = appWindow.listen<any[]>("playlists_gotten", (_res) => {
|
const unlisten = appWindow.listen<any[]>("playlists_gotten", (_res) => {
|
||||||
// console.log(event);
|
// console.log(event);
|
||||||
let res = _res.payload;
|
let res = _res.payload as PlaylistInfo[];
|
||||||
|
playlistsInfo.current = [...res];
|
||||||
|
console.log(playlistsInfo, res);
|
||||||
|
|
||||||
setPlaylists([
|
setPlaylists([
|
||||||
...res.map( (item) => {
|
...res.map( (item) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button onClick={ () => {
|
<button onClick={ () => {
|
||||||
invoke('get_playlist', { uuid: item.uuid }).then((list) => {
|
invoke('get_playlist', { uuid: item.uuid }).then((list) => {
|
||||||
|
@ -130,6 +141,7 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: Play
|
||||||
plays={ song.plays }
|
plays={ song.plays }
|
||||||
duration={ song.duration }
|
duration={ song.duration }
|
||||||
tags={ song.tags }
|
tags={ song.tags }
|
||||||
|
playlists={ playlistsInfo }
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})])
|
})])
|
||||||
|
@ -163,6 +175,7 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: Play
|
||||||
plays={ song.plays }
|
plays={ song.plays }
|
||||||
duration={ song.duration }
|
duration={ song.duration }
|
||||||
tags={ song.tags }
|
tags={ song.tags }
|
||||||
|
playlists={ playlistsInfo }
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})])
|
})])
|
||||||
|
@ -190,6 +203,7 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: Play
|
||||||
plays={ song.plays }
|
plays={ song.plays }
|
||||||
duration={ song.duration }
|
duration={ song.duration }
|
||||||
tags={ song.tags }
|
tags={ song.tags }
|
||||||
|
playlists={ playlistsInfo }
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})])
|
})])
|
||||||
|
@ -204,10 +218,11 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: Play
|
||||||
|
|
||||||
interface MainViewProps {
|
interface MainViewProps {
|
||||||
lib_ref: [JSX.Element[], React.Dispatch<React.SetStateAction<JSX.Element[]>>],
|
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;
|
const [library, setLibrary] = lib_ref;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -225,6 +240,7 @@ function MainView({ lib_ref, viewName }: MainViewProps) {
|
||||||
plays={ song.plays }
|
plays={ song.plays }
|
||||||
duration={ song.duration }
|
duration={ song.duration }
|
||||||
tags={ song.tags }
|
tags={ song.tags }
|
||||||
|
playlists={ playlistsInfo }
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})])
|
})])
|
||||||
|
@ -255,7 +271,8 @@ interface SongProps {
|
||||||
last_played?: string,
|
last_played?: string,
|
||||||
date_added?: string,
|
date_added?: string,
|
||||||
date_modified?: string,
|
date_modified?: string,
|
||||||
tags: any
|
tags: any,
|
||||||
|
playlists: MutableRefObject<PlaylistInfo[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
function Song(props: SongProps) {
|
function Song(props: SongProps) {
|
||||||
|
@ -264,15 +281,27 @@ function Song(props: SongProps) {
|
||||||
invoke('add_song_to_queue', { uuid: props.uuid, location: props.playerLocation }).then(() => {});
|
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) {
|
async function clickHandler(event: React.MouseEvent) {
|
||||||
event.preventDefault();
|
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);
|
const pos = new LogicalPosition(event.clientX, event.clientY);
|
||||||
menu.popup(pos);
|
menu.popup(pos);
|
||||||
}
|
}
|
||||||
|
@ -419,11 +448,10 @@ interface QueueSongProps {
|
||||||
function QueueSong({ song, location, index }: QueueSongProps) {
|
function QueueSong({ song, location, index }: QueueSongProps) {
|
||||||
// console.log(song.tags);
|
// console.log(song.tags);
|
||||||
|
|
||||||
let removeFromQueue = () => {
|
const removeFromQueue = () => {
|
||||||
invoke('remove_from_queue', { index: index }).then(() => {})
|
invoke('remove_from_queue', { index: index }).then(() => {})
|
||||||
}
|
}
|
||||||
|
const playNow = () => {
|
||||||
let playNow = () => {
|
|
||||||
invoke('play_now', { uuid: song.uuid, location: location }).then(() => {})
|
invoke('play_now', { uuid: song.uuid, location: location }).then(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue