mirror of
https://github.com/Dangoware/dango-music-player.git
synced 2025-04-19 01:52:53 -05:00
Added basic visual config editor
This commit is contained in:
parent
04da9c88f3
commit
6f90aa09b9
11 changed files with 151 additions and 54 deletions
|
@ -364,7 +364,6 @@ fn last_fm_scrobble(scrobbler: Scrobbler, now_playing_tx: Receiver<Song>, scrobb
|
|||
// TODO: Add support for scrobble storage for later
|
||||
|
||||
let mut song: Option<Song> = None;
|
||||
let mut last_song: Option<Song> = None;
|
||||
LAST_FM_ACTIVE.store(true, Ordering::Relaxed);
|
||||
println!("last.fm connected");
|
||||
|
||||
|
|
|
@ -175,6 +175,7 @@ pub struct ControllerHandle {
|
|||
pub(super) player_mail_rx: async_channel::Sender<PlayerCommandInput>,
|
||||
pub(super) queue_mail_rx: async_channel::Sender<QueueCommandInput>,
|
||||
pub(super) connections_rx: crossbeam_channel::Sender<ConnectionsNotification>,
|
||||
pub config: Arc<RwLock<Config>>,
|
||||
}
|
||||
|
||||
impl ControllerHandle {
|
||||
|
@ -199,6 +200,7 @@ impl ControllerHandle {
|
|||
player_mail_rx: player_mail_rx.clone(),
|
||||
queue_mail_rx: queue_mail_rx.clone(),
|
||||
connections_rx: connections_mail_rx.clone(),
|
||||
config: config.clone(),
|
||||
},
|
||||
ControllerInput {
|
||||
player_mail: (player_mail_rx, player_mail_tx),
|
||||
|
|
|
@ -182,7 +182,7 @@ impl ControllerHandle {
|
|||
self.connections_rx.send(super::connections::ConnectionsNotification::TryEnableConnection(super::connections::TryConnectionType::Discord(client_id))).unwrap();
|
||||
}
|
||||
|
||||
pub fn listenbrainz_scrobble(&self, token: String) {
|
||||
pub fn listenbrainz_scrobble_auth(&self, token: String) {
|
||||
self.connections_rx.send(super::connections::ConnectionsNotification::TryEnableConnection(super::connections::TryConnectionType::ListenBrainz(token))).unwrap();
|
||||
}
|
||||
|
||||
|
|
|
@ -10,11 +10,8 @@
|
|||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jprochazk/cbor": "github:jprochazk/cbor",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-shell": "^2",
|
||||
"cbor": "github:jprochazk/cbor",
|
||||
"cbor-x": "^1.6.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
|
|
38
src-tauri/src/config.rs
Normal file
38
src-tauri/src/config.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use dmp_core::{config::Config, music_controller::controller::ControllerHandle};
|
||||
use tauri::{State, WebviewWindowBuilder, Window, Wry};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn open_config_window(app: tauri::AppHandle<Wry>) -> Result<(), String> {
|
||||
WebviewWindowBuilder::new(&app, "editdmpconfig", tauri::WebviewUrl::App(PathBuf::from("src/config/index.html")))
|
||||
.title("Edit Dango Music Player")
|
||||
.build()
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_config(ctrl_handle: State<'_, ControllerHandle>) -> Result<Config, String> {
|
||||
Ok(ctrl_handle.config.read().clone())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_config(ctrl_handle: State<'_, ControllerHandle>, config: Config) -> Result<(), String> {
|
||||
let config_original = ctrl_handle.config.read().clone();
|
||||
|
||||
if config.connections.listenbrainz_token.as_ref().is_some_and(|t| Some(t) != config_original.connections.listenbrainz_token.as_ref()) {
|
||||
let token = config.connections.listenbrainz_token.clone().unwrap();
|
||||
ctrl_handle.listenbrainz_scrobble_auth(dbg!(token));
|
||||
}
|
||||
|
||||
*ctrl_handle.config.write() = config;
|
||||
ctrl_handle.config.read().write_file().unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn close_window(window: Window<Wry>) -> Result<(), String> {
|
||||
window.close().unwrap();
|
||||
Ok(())
|
||||
}
|
|
@ -8,16 +8,13 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
|
||||
use config::{close_window, get_config, open_config_window, save_config};
|
||||
use crossbeam::channel::{bounded, unbounded, Receiver, Sender};
|
||||
use dmp_core::{
|
||||
config::{Config, ConfigLibrary},
|
||||
music_controller::{
|
||||
connections::ConnectionsInput,
|
||||
controller::{Controller, ControllerHandle, PlaybackInfo},
|
||||
},
|
||||
music_controller::{connections::LastFMAuth, controller::{Controller, ControllerHandle, PlaybackInfo}},
|
||||
music_storage::library::{MusicLibrary, Song},
|
||||
};
|
||||
use futures::channel::oneshot;
|
||||
use parking_lot::RwLock;
|
||||
use tauri::{http::Response, Emitter, Manager, State, Wry};
|
||||
use uuid::Uuid;
|
||||
|
@ -27,23 +24,28 @@ 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,
|
||||
};
|
||||
use commands::{add_song_to_queue, display_album_art, play_now};
|
||||
use commands::{add_song_to_queue, display_album_art, last_fm_init_auth, play_now};
|
||||
|
||||
pub mod commands;
|
||||
pub mod wrappers;
|
||||
pub mod config;
|
||||
|
||||
const DEFAULT_IMAGE: &[u8] = include_bytes!("../icons/icon.png");
|
||||
|
||||
const DISCORD_CLIENT_ID: u64 = 1198868728243290152;
|
||||
const LAST_FM_API_KEY: &str = env!("LAST_FM_API_KEY", "None");
|
||||
const LAST_FM_API_SECRET: &str = env!("LAST_FM_API_SECRET", "None");
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let (rx, tx) = unbounded::<Config>();
|
||||
let (config_rx, config_tx) = unbounded::<Config>();
|
||||
let (lib_rx, lib_tx) = unbounded::<Option<PathBuf>>();
|
||||
let (handle_rx, handle_tx) = unbounded::<ControllerHandle>();
|
||||
let (playback_info_rx, playback_info_tx) = bounded(1);
|
||||
let (next_rx, next_tx) = bounded(1);
|
||||
|
||||
let _controller_thread = spawn(move || {
|
||||
let mut config = { tx.recv().unwrap() };
|
||||
let mut config = { config_tx.recv().unwrap() };
|
||||
let scan_path = { lib_tx.recv().unwrap() };
|
||||
let _temp_config = ConfigLibrary::default();
|
||||
let _lib = config.libraries.get_default().unwrap_or(&_temp_config);
|
||||
|
@ -97,25 +99,35 @@ pub fn run() {
|
|||
|
||||
library.save(save_path).unwrap();
|
||||
|
||||
let last_fm_session = config.connections.last_fm_session.clone();
|
||||
let listenbrainz_token = config.connections.listenbrainz_token.clone();
|
||||
|
||||
let (handle, input, playback_info, next_song_notification) = ControllerHandle::new(
|
||||
library,
|
||||
std::sync::Arc::new(RwLock::new(config)),
|
||||
Some(ConnectionsInput {
|
||||
discord_rpc_client_id: std::option_env!("DISCORD_CLIENT_ID")
|
||||
.map(|id| id.parse::<u64>().unwrap()),
|
||||
}),
|
||||
std::sync::Arc::new(RwLock::new(config))
|
||||
);
|
||||
|
||||
handle.discord_rpc(DISCORD_CLIENT_ID);
|
||||
if let Some(token) = listenbrainz_token {
|
||||
handle.listenbrainz_scrobble_auth(token);
|
||||
} else {
|
||||
println!("No ListenBrainz token found");
|
||||
}
|
||||
if let Some(session) = last_fm_session {
|
||||
handle.last_fm_scrobble_auth(LAST_FM_API_KEY.to_string(), LAST_FM_API_SECRET.to_string(), LastFMAuth::Session(Some(session)));
|
||||
}
|
||||
|
||||
handle_rx.send(handle).unwrap();
|
||||
playback_info_rx.send(playback_info).unwrap();
|
||||
next_rx.send(next_song_notification).unwrap();
|
||||
|
||||
let _controller = futures::executor::block_on(Controller::start(input)).unwrap();
|
||||
});
|
||||
|
||||
let app = tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
get_config,
|
||||
init_get_config,
|
||||
create_new_library,
|
||||
get_library,
|
||||
play,
|
||||
|
@ -135,8 +147,13 @@ pub fn run() {
|
|||
remove_from_queue,
|
||||
display_album_art,
|
||||
seek,
|
||||
last_fm_init_auth,
|
||||
open_config_window,
|
||||
get_config,
|
||||
save_config,
|
||||
close_window,
|
||||
])
|
||||
.manage(ConfigRx(rx))
|
||||
.manage(ConfigRx(config_rx))
|
||||
.manage(LibRx(lib_rx))
|
||||
.manage(HandleTx(handle_tx))
|
||||
.manage(tempfile::TempDir::new().unwrap())
|
||||
|
@ -238,7 +255,7 @@ struct LibRx(Sender<Option<PathBuf>>);
|
|||
struct HandleTx(Receiver<ControllerHandle>);
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_config(state: State<'_, ConfigRx>) -> Result<Config, String> {
|
||||
async fn init_get_config(state: State<'_, ConfigRx>) -> Result<Config, String> {
|
||||
if let Some(dir) = directories::ProjectDirs::from("", "Dangoware", "dmp") {
|
||||
let path = dir.config_dir();
|
||||
fs::create_dir_all(path)
|
||||
|
@ -268,6 +285,7 @@ async fn get_config(state: State<'_, ConfigRx>) -> Result<Config, String> {
|
|||
|
||||
state.inner().0.send(config.clone()).unwrap();
|
||||
|
||||
println!("got config");
|
||||
Ok(config)
|
||||
} else {
|
||||
panic!("No config dir for DMP")
|
||||
|
|
10
src/App.tsx
10
src/App.tsx
|
@ -200,6 +200,7 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: Play
|
|||
} }>Library</button>
|
||||
{ playlists }
|
||||
<button onClick={ handle_import }>Import .m3u Playlist</button>
|
||||
<button onClick={() => { invoke('open_config_window').then(() => {}) }} style={{marginLeft: "auto", float: "right"}}>Edit DMP</button>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
@ -312,12 +313,6 @@ function PlayBar({ playing, setPlaying }: PlayBarProps) {
|
|||
invoke('seek', { time: Math.round(val * 1000) }).then()
|
||||
};
|
||||
|
||||
const lastFmLogin = () => {
|
||||
invoke('last_fm_init_auth').then(() => {
|
||||
setLastFmLoggedIn(true);
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<section id="playBar" className="playBar unselectable">
|
||||
<div className="seekBar" ref={ seekBarRef } onClick={ seek } onDrag={ seek }>
|
||||
|
@ -334,7 +329,6 @@ function PlayBar({ playing, setPlaying }: PlayBarProps) {
|
|||
<button onClick={ () => invoke('next').then(() => {}) }>⏭</button>
|
||||
</div>
|
||||
<div className="bottomRight">
|
||||
<button id="lastFmLogin" onClick={ lastFmLogin } style={{visibility: lastFmLoggedIn ? 'hidden' : 'visible' }} >last.fm</button>
|
||||
<button>🔀</button>
|
||||
<button>🔁</button>
|
||||
<input type="range" name="volume" id="volumeSlider" onChange={ (volume) => {
|
||||
|
@ -414,7 +408,7 @@ function QueueSong({ song, location, index }: QueueSongProps) {
|
|||
}
|
||||
|
||||
function getConfig(): any {
|
||||
invoke('get_config').then( (_config) => {
|
||||
invoke('init_get_config').then( (_config) => {
|
||||
let config = _config as Config;
|
||||
if (config.libraries.libraries.length == 0) {
|
||||
invoke('create_new_library').then(() => {})
|
||||
|
|
71
src/config/code.tsx
Normal file
71
src/config/code.tsx
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { Config, ConfigConnections } from "../types";
|
||||
import { TauriEvent } from "@tauri-apps/api/event";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<>
|
||||
<App />
|
||||
</>,
|
||||
);
|
||||
|
||||
function App() {
|
||||
let [config, setConfig] = useState<Config>();
|
||||
useEffect(() => {
|
||||
invoke('get_config').then((_config) => {
|
||||
let config = _config as Config;
|
||||
console.log(config);
|
||||
|
||||
setConfig(config);
|
||||
});
|
||||
}, [])
|
||||
|
||||
const last_fm_login = () => {
|
||||
invoke('last_fm_init_auth').then(() => {})
|
||||
}
|
||||
const save_config = () => {
|
||||
invoke('save_config', { config: config }).then(() => {
|
||||
// invoke('close_window').then(() => {})
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Config</h1>
|
||||
<label>last.fm:</label>
|
||||
{ config?.connections.last_fm_session ? (" already signed in") : (<button onClick={last_fm_login}>sign into last.fm</button>) }
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<ListenBrainz config={ config } setConfig={ setConfig } />
|
||||
<br />
|
||||
<br />
|
||||
<button onClick={ save_config }>save</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
interface ListenBrainzProps {
|
||||
config: Config | undefined,
|
||||
setConfig: React.Dispatch<React.SetStateAction<Config | undefined>>,
|
||||
}
|
||||
function ListenBrainz({ config, setConfig }: ListenBrainzProps) {
|
||||
const [token, setToken] = useState("");
|
||||
|
||||
useEffect( () => {
|
||||
console.log("Token: " + token);
|
||||
|
||||
config? setConfig((prev) => ({...prev!, connections: {...config.connections, listenbrainz_token: token}})) : {}
|
||||
}, [token])
|
||||
|
||||
const updateToken = (e: ChangeEvent<HTMLInputElement>)=> {
|
||||
setToken(e.currentTarget.value);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<label>{ "Listenbrainz Token" }</label>
|
||||
<input type="text" value={ config?.connections.listenbrainz_token } onChange={updateToken} />
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Library Thing</title>
|
||||
<title>Edit Config</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
|
@ -1,24 +0,0 @@
|
|||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useRef } from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<App />,
|
||||
);
|
||||
|
||||
|
||||
function App() {
|
||||
let x = useRef('')
|
||||
return (
|
||||
<>
|
||||
<h2>Insert your music folder path here</h2>
|
||||
<form>
|
||||
<input type="text" name="libinput" id="libinput" onChange={ (event) => x.current = event.target.value as string } />
|
||||
<input type="submit" value="sumbit" onClick={(event) => {
|
||||
event.preventDefault();
|
||||
invoke('create_library', { path: x.current }).then(() => {})
|
||||
}} />
|
||||
</form>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -20,6 +20,8 @@ export interface Config {
|
|||
}
|
||||
|
||||
export interface ConfigConnections {
|
||||
discord_rpc_client_id?: number,
|
||||
last_fm_session?: string,
|
||||
listenbrainz_token?: string
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue