diff --git a/Cargo.toml b/Cargo.toml index 8a90fb6..e54d231 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,4 +17,4 @@ strip = true lto = true opt-level = "z" codegen-units = 1 -panic = "abort" \ No newline at end of file +panic = "abort" diff --git a/dmp-core/Cargo.toml b/dmp-core/Cargo.toml index ea485db..97d4c05 100644 --- a/dmp-core/Cargo.toml +++ b/dmp-core/Cargo.toml @@ -44,3 +44,7 @@ rustfm-scrobble = "1.1.1" reqwest = { version = "0.12.12", features = ["json"] } tokio = { version = "1.43.0", features = ["macros"] } opener = "0.7.2" +ts-rs = { version = "11.0.1", optional = true, features = ["uuid-impl", "chrono-impl", "serde_json"] } + +[features] +ts = ["dep:ts-rs"] diff --git a/dmp-core/src/config/mod.rs b/dmp-core/src/config/mod.rs index 86b6f65..0b76e38 100644 --- a/dmp-core/src/config/mod.rs +++ b/dmp-core/src/config/mod.rs @@ -7,8 +7,10 @@ use std::{ use serde::{Deserialize, Serialize}; use serde_json::to_string_pretty; use thiserror::Error; +use ts_rs::TS; use uuid::Uuid; +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConfigLibrary { pub name: String, @@ -51,6 +53,7 @@ impl ConfigLibrary { } } +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct ConfigLibraries { pub default_library: Uuid, @@ -92,6 +95,7 @@ impl ConfigLibraries { } } +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct ConfigConnections { pub discord_rpc_client_id: Option, @@ -99,6 +103,7 @@ pub struct ConfigConnections { pub last_fm_session: Option, } +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Debug, Default, Serialize, Deserialize, Clone)] #[serde(default)] pub struct Config { diff --git a/dmp-core/src/music_controller/controller.rs b/dmp-core/src/music_controller/controller.rs index a8cf640..916c4ca 100644 --- a/dmp-core/src/music_controller/controller.rs +++ b/dmp-core/src/music_controller/controller.rs @@ -28,6 +28,8 @@ use super::connections::{ConnectionsNotification, ControllerConnections}; use super::controller_handle::{LibraryCommandInput, PlayerCommandInput, QueueCommandInput}; use super::queue::{QueueAlbum, QueueSong}; +use ts_rs::TS; + pub struct Controller(); type QueueItem_ = QueueItem; @@ -43,6 +45,7 @@ pub enum ControllerError { } // TODO: move this to a different location to be used elsewhere +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)] #[non_exhaustive] pub enum PlayerLocation { @@ -370,8 +373,11 @@ impl Controller { } } +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Debug, Default, Serialize, Clone)] pub struct PlaybackInfo { + #[ts(as = "(f64, f64)")] pub position: Option, + #[ts(as = "(f64, f64)")] pub duration: Option, } diff --git a/dmp-core/src/music_controller/controller_handle.rs b/dmp-core/src/music_controller/controller_handle.rs index 378c981..02c7188 100644 --- a/dmp-core/src/music_controller/controller_handle.rs +++ b/dmp-core/src/music_controller/controller_handle.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; use async_channel::{Receiver, Sender}; -use discord_presence::models::Command; use uuid::Uuid; use crate::music_storage::{ diff --git a/dmp-core/src/music_controller/library_command.rs b/dmp-core/src/music_controller/library_command.rs index c04e643..4935efb 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::{self, MusicLibrary}, + library::MusicLibrary, playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem}, }, }; diff --git a/dmp-core/src/music_controller/queue_command.rs b/dmp-core/src/music_controller/queue_command.rs index 2615c7f..ff5ce1d 100644 --- a/dmp-core/src/music_controller/queue_command.rs +++ b/dmp-core/src/music_controller/queue_command.rs @@ -1,7 +1,4 @@ -use crate::music_storage::{ - library::Song, - queue::{Queue, QueueError, QueueItemType}, -}; +use crate::music_storage::queue::{Queue, QueueError, QueueItemType}; use super::{ controller::{Controller, QueueCommand, QueueResponse}, diff --git a/dmp-core/src/music_storage/db_reader/itunes/reader.rs b/dmp-core/src/music_storage/db_reader/itunes/reader.rs index 3c9b8e3..910901e 100644 --- a/dmp-core/src/music_storage/db_reader/itunes/reader.rs +++ b/dmp-core/src/music_storage/db_reader/itunes/reader.rs @@ -213,8 +213,8 @@ fn to_tag(string: String) -> Tag { "album artist" => Tag::AlbumArtist, "genre" => Tag::Genre, "comment" => Tag::Comment, - "track number" => Tag::Track, - "disc number" => Tag::Disk, + "track number" => Tag::TrackNumber, + "disc number" => Tag::DiskNumber, _ => Tag::Key(string), } } diff --git a/dmp-core/src/music_storage/library.rs b/dmp-core/src/music_storage/library.rs index 985ced2..7ab1a5e 100644 --- a/dmp-core/src/music_storage/library.rs +++ b/dmp-core/src/music_storage/library.rs @@ -1,6 +1,7 @@ use super::playlist::{Playlist, PlaylistFolder}; // Crate things use super::utils::{find_images, normalize, read_file, write_file}; + use crate::music_storage::playlist::PlaylistFolderItem; use std::cmp::Ordering; @@ -19,6 +20,7 @@ use lofty::file::{AudioFile as _, TaggedFileExt as _}; use lofty::probe::Probe; use lofty::tag::{ItemKey, ItemValue, TagType}; use rcue::parser::parse_from_file; +use serde::ser::SerializeMap; use std::fs; use std::path::{Path, PathBuf}; use uuid::Uuid; @@ -35,6 +37,10 @@ use serde::{Deserialize, Serialize}; use rayon::prelude::*; use std::sync::{Arc, Mutex}; +// TS +use ts_rs::TS; + +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub enum AlbumArt { Embedded(usize), @@ -52,6 +58,7 @@ impl AlbumArt { /// A tag for a song #[non_exhaustive] +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] pub enum Tag { Title, @@ -60,23 +67,26 @@ pub enum Tag { AlbumArtist, Genre, Comment, - Track, - Disk, - Key(String), + TrackNumber, + DiskNumber, + #[cfg_attr(feature = "ts", ts(type = "string"))] Field(String), + #[serde(untagged)] + #[cfg_attr(feature = "ts", ts(type = "string"))] + Key(String), } impl Display for Tag { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let path_str: String = match self { - Self::Title => "TrackTitle".into(), - Self::Album => "AlbumTitle".into(), - Self::Artist => "TrackArtist".into(), + Self::Title => "Title".into(), + Self::Album => "Album".into(), + Self::Artist => "Artist".into(), Self::AlbumArtist => "AlbumArtist".into(), Self::Genre => "Genre".into(), Self::Comment => "Comment".into(), - Self::Track => "TrackNumber".into(), - Self::Disk => "DiscNumber".into(), + Self::TrackNumber => "TrackNumber".into(), + Self::DiskNumber => "DiscNumber".into(), Self::Key(key) => key.into(), Self::Field(f) => f.into(), }; @@ -121,6 +131,7 @@ impl Display for Field { } } +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] #[non_exhaustive] pub enum InternalTag { @@ -131,6 +142,7 @@ pub enum InternalTag { VolumeAdjustment(i8), } +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] #[non_exhaustive] pub enum BannedType { @@ -138,6 +150,7 @@ pub enum BannedType { All, } +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] #[non_exhaustive] pub enum DoNotTrack { @@ -148,6 +161,7 @@ pub enum DoNotTrack { Discord, } +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] #[non_exhaustive] pub enum SongType { @@ -160,6 +174,7 @@ pub enum SongType { } /// Stores information about a single song +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct Song { pub location: Vec, @@ -171,19 +186,75 @@ pub struct Song { pub rating: Option, /// MIME type pub format: Option, + #[cfg_attr( + feature = "ts", + ts(type = "number"), + serde(serialize_with = "dur_ms", deserialize_with = "ms_dur") + )] pub duration: Duration, + #[cfg_attr(feature = "ts", ts(type = "number"))] pub play_time: Duration, #[serde(with = "ts_milliseconds_option")] + #[cfg_attr(feature = "ts", ts(type = "number"))] pub last_played: Option>, #[serde(with = "ts_milliseconds_option")] + #[cfg_attr(feature = "ts", ts(type = "number"))] pub date_added: Option>, #[serde(with = "ts_milliseconds_option")] + #[cfg_attr(feature = "ts", ts(type = "number"))] pub date_modified: Option>, pub album_art: Vec, + #[cfg_attr( + feature = "ts", + // The combination of "Tag" and "Map" doesn't seem to play well, possibly because of the 'Field' variant, + // which will cause type errors on the TS side otherwise + ts(type = "any"), + serde(serialize_with = "tags_strings") + )] pub tags: BTreeMap, pub internal_tags: Vec, } +#[cfg(feature = "ts")] +fn tags_strings( + tags: &BTreeMap, + serializer: S, +) -> Result { + let mut map = serializer.serialize_map(Some(tags.len()))?; + for (k, v) in tags { + map.serialize_entry(&k.to_string(), &v)?; + } + map.end() +} + +#[cfg(feature = "ts")] +fn dur_ms(duration: &Duration, serializer: S) -> Result { + serializer.serialize_u64(duration.as_secs()) +} +#[cfg(feature = "ts")] +fn ms_dur<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { + use std::marker::PhantomData; + + use serde::de::Visitor; + + struct De(PhantomData Duration>); + impl<'de> Visitor<'de> for De { + type Value = Duration; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("Duration as seconds in the form of a u64") + } + + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + Ok(Duration::from_secs(v)) + } + } + deserializer.deserialize_u64(De(PhantomData)) +} + impl Song { /// Get a tag's value /// @@ -257,13 +328,13 @@ impl Song { for item in tag.items() { let key = match item.key() { ItemKey::TrackTitle => Tag::Title, - ItemKey::TrackNumber => Tag::Track, + ItemKey::TrackNumber => Tag::TrackNumber, ItemKey::TrackArtist => Tag::Artist, ItemKey::AlbumArtist => Tag::AlbumArtist, ItemKey::Genre => Tag::Genre, ItemKey::Comment => Tag::Comment, ItemKey::AlbumTitle => Tag::Album, - ItemKey::DiscNumber => Tag::Disk, + ItemKey::DiscNumber => Tag::DiskNumber, ItemKey::Unknown(unknown) if unknown == "ACOUSTID_FINGERPRINT" || unknown == "Acoustid Fingerprint" => { @@ -400,7 +471,10 @@ impl Song { tags.insert(Tag::Artist, artist.clone()); } - tags.insert(Tag::Track, track.no.parse().unwrap_or((i + 1).to_string())); + tags.insert( + Tag::TrackNumber, + track.no.parse().unwrap_or((i + 1).to_string()), + ); match track.title.clone() { Some(title) => tags.insert(Tag::Title, title), None => match track.isrc.clone() { @@ -498,13 +572,16 @@ impl Song { } } +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum URI { Local(PathBuf), Cue { location: PathBuf, index: usize, + #[cfg_attr(feature = "ts", ts(type = "number"))] start: Duration, + #[cfg_attr(feature = "ts", ts(type = "number"))] end: Duration, }, Remote(Service, String), @@ -590,6 +667,7 @@ impl Display for URI { } } +#[cfg_attr(feature = "ts", derive(TS), ts(export))] #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Service { InternetRadio, @@ -1134,7 +1212,7 @@ impl MusicLibrary { //let norm_title = normalize(&album_title); let disc_num = song - .get_tag(&Tag::Disk) + .get_tag(&Tag::DiskNumber) .unwrap_or(&"".to_string()) .parse::() .unwrap_or(1); @@ -1143,7 +1221,7 @@ impl MusicLibrary { // If the album is in the list, add the track to the appropriate disc within the album Some(album) => match album.discs.get_mut(&disc_num) { Some(disc) => disc.push(( - song.get_tag(&Tag::Track) + song.get_tag(&Tag::TrackNumber) .unwrap_or(&String::new()) .parse::() .unwrap_or_default(), @@ -1153,7 +1231,7 @@ impl MusicLibrary { album.discs.insert( disc_num, vec![( - song.get_tag(&Tag::Track) + song.get_tag(&Tag::TrackNumber) .unwrap_or(&String::new()) .parse::() .unwrap_or_default(), @@ -1171,7 +1249,7 @@ impl MusicLibrary { discs: BTreeMap::from([( disc_num, vec![( - song.get_tag(&Tag::Track) + song.get_tag(&Tag::TrackNumber) .unwrap_or(&String::new()) .parse::() .unwrap_or_default(), @@ -1308,8 +1386,8 @@ mod test { &vec![ Tag::Field("location".to_string()), Tag::Album, - Tag::Disk, - Tag::Track, + Tag::DiskNumber, + Tag::TrackNumber, ], ) .unwrap(); diff --git a/dmp-core/src/music_storage/playlist.rs b/dmp-core/src/music_storage/playlist.rs index c340197..013389b 100644 --- a/dmp-core/src/music_storage/playlist.rs +++ b/dmp-core/src/music_storage/playlist.rs @@ -11,7 +11,6 @@ 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 thiserror::Error; diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 5c508a6..4fd8bf3 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -dmp-core = { path = "../dmp-core" } +dmp-core = { path = "../dmp-core", features = ["ts"] } tauri = { version = "2", features = [ "protocol-asset", "unstable"] } tauri-plugin-shell = "2" serde = { version = "1", features = ["derive"] } diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index ab46b55..4930ad0 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -11,7 +11,7 @@ use tauri::{AppHandle, Emitter, State, Wry}; use tempfile::TempDir; use uuid::Uuid; -use crate::{LAST_FM_API_KEY, LAST_FM_API_SECRET, wrappers::_Song}; +use crate::{LAST_FM_API_KEY, LAST_FM_API_SECRET}; #[tauri::command] pub async fn add_song_to_queue( @@ -47,7 +47,7 @@ pub async fn play_now( Err(e) => return Err(e.to_string()), }; app.emit("queue_updated", ()).unwrap(); - app.emit("now_playing_change", _Song::from(&song)).unwrap(); + app.emit("now_playing_change", &song).unwrap(); app.emit("playing", true).unwrap(); Ok(()) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index fcdf146..59ef013 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -21,12 +21,12 @@ use dmp_core::{ use parking_lot::RwLock; use tauri::{AppHandle, Emitter, Manager, http::Response}; use uuid::Uuid; -use wrappers::{_Song, stop}; +use wrappers::stop; use crate::wrappers::{ add_song_to_playlist, clear_queue, 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, queue_move_to + get_queue, get_song, import_playlist, next, pause, play, play_next_queue, prev, queue_move_to, + remove_from_queue, seek, set_volume, }; use commands::{ add_song_to_queue, display_album_art, last_fm_init_auth, play_now, remove_from_lib_playlist, @@ -212,7 +212,7 @@ fn start_controller(app: AppHandle) -> Result<(), String> { let next_song_notification = next_song_notification; while true { let song = next_song_notification.recv().unwrap(); - app.emit("now_playing_change", _Song::from(&song)).unwrap(); + app.emit("now_playing_change", &song).unwrap(); app.emit("queue_updated", ()).unwrap(); app.emit("playing", true).unwrap(); _ = now_playing.write().insert(song); diff --git a/src-tauri/src/wrappers.rs b/src-tauri/src/wrappers.rs index 90f159a..766d104 100644 --- a/src-tauri/src/wrappers.rs +++ b/src-tauri/src/wrappers.rs @@ -1,11 +1,10 @@ -use std::{collections::BTreeMap, path::PathBuf, thread::spawn}; +use std::{path::PathBuf, thread::spawn}; -use chrono::{DateTime, Utc, serde::ts_milliseconds_option}; use crossbeam::channel::Sender; use dmp_core::{ music_controller::controller::{ControllerHandle, PlayerLocation}, music_storage::{ - library::{Song, Tag, URI}, + library::{Song, Tag}, queue::QueueItemType, }, }; @@ -83,7 +82,7 @@ pub async fn next( Ok(s) => s, Err(e) => return Err(e.to_string()), }; - app.emit("now_playing_change", _Song::from(&song)).unwrap(); + app.emit("now_playing_change", song).unwrap(); app.emit("queue_updated", ()).unwrap(); app.emit("playing", true).unwrap(); Ok(()) @@ -99,7 +98,7 @@ pub async fn prev( Err(e) => return Err(e.to_string()), }; println!("prev"); - app.emit("now_playing_change", _Song::from(&song)).unwrap(); + app.emit("now_playing_change", song).unwrap(); app.emit("queue_updated", ()).unwrap(); Ok(()) } @@ -112,7 +111,7 @@ pub async fn now_playing(_ctrl_handle: State<'_, ControllerHandle>) -> Result<() #[tauri::command] pub async fn get_queue( ctrl_handle: State<'_, ControllerHandle>, -) -> Result, String> { +) -> Result, String> { Ok(ctrl_handle .queue_get_all() .await @@ -121,7 +120,7 @@ pub async fn get_queue( let QueueItemType::Single(song) = item.item else { unreachable!("There should be no albums in the queue right now") }; - (_Song::from(&song.song), song.location) + (song.song, song.location) }) .collect_vec()) } @@ -141,51 +140,9 @@ pub async fn remove_from_queue( } } -//Grab Album art from custom protocol -#[derive(Serialize, Debug, Clone)] -pub struct _Song { - pub location: Vec, - pub uuid: Uuid, - pub plays: i32, - pub format: Option, - pub duration: String, - #[serde(with = "ts_milliseconds_option")] - pub last_played: Option>, - #[serde(with = "ts_milliseconds_option")] - pub date_added: Option>, - #[serde(with = "ts_milliseconds_option")] - pub date_modified: Option>, - pub tags: BTreeMap, -} - -impl From<&Song> for _Song { - fn from(value: &Song) -> Self { - _Song { - location: value.location.clone(), - uuid: value.uuid.clone(), - plays: value.plays.clone(), - duration: value.duration.as_secs().to_string(), - format: value.format.clone().map(|format| format.to_string()), - last_played: value.last_played, - date_added: value.date_added, - date_modified: value.date_modified, - tags: value - .tags - .iter() - .map(|(k, v)| (k.to_string(), v.clone())) - .collect(), - } - } -} - #[tauri::command] -pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result, String> { - let songs = ctrl_handle - .lib_get_all() - .await - .iter() - .map(|song| _Song::from(song)) - .collect_vec(); +pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result, String> { + let songs = ctrl_handle.lib_get_all().await; Ok(songs) } @@ -193,23 +150,17 @@ pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result, uuid: Uuid, -) -> Result, String> { +) -> Result, String> { let playlist = match ctrl_handle.playlist_get(uuid).await { Ok(list) => list, Err(_) => todo!(), }; - - let songs = playlist - .tracks - .iter() - .map(|song| _Song::from(song)) - .collect::>(); println!( "Got Playlist {}, len {}", playlist.title, playlist.tracks.len() ); - Ok(songs) + Ok(playlist.tracks) } #[tauri::command] @@ -264,13 +215,13 @@ pub struct PlaylistPayload { pub async fn get_song( ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, -) -> Result<_Song, String> { +) -> Result { let song = ctrl_handle.lib_get_song(uuid).await.0; println!( "got song {}", &song.tags.get(&Tag::Title).unwrap_or(&String::new()) ); - Ok(_Song::from(&song)) + Ok(song) } #[tauri::command] @@ -334,7 +285,7 @@ pub async fn queue_move_to( match ctrl_handle.enqueue(0).await.map_err(|e| e.to_string()) { Ok(song) => { app.emit("queue_updated", ()).unwrap(); - app.emit("now_playing_change", _Song::from(&song)).unwrap(); + app.emit("now_playing_change", song).unwrap(); app.emit("playing", true).unwrap(); Ok(()) } diff --git a/src/App.tsx b/src/App.tsx index 51dd310..8843c02 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,19 +1,20 @@ import React, { MutableRefObject, useEffect, useRef, useState } from "react"; import { convertFileSrc, invoke } from "@tauri-apps/api/core"; import "./App.css"; -import { Config, playbackInfo } from "./types"; // import { EventEmitter } from "@tauri-apps/plugin-shell"; // import { listen } from "@tauri-apps/api/event"; // import { fetch } from "@tauri-apps/plugin-http"; import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; import { PhysicalPosition } from "@tauri-apps/api/window"; import { Menu, Submenu, SubmenuOptions } from "@tauri-apps/api/menu"; - +import { Config } from "./bindings/Config"; +import { PlaybackInfo } from "./bindings/PlaybackInfo"; +import { PlayerLocation } from "./bindings/PlayerLocation"; +import { URI } from "./bindings/URI"; +import { Song } from "./bindings/Song"; const appWindow = getCurrentWebviewWindow(); -type Location = "Library" | { "Playlist": string }; - // This needs to be changed to properly reflect cursor position // this will do for now. async function contextMenuPosition(event: React.MouseEvent) { @@ -43,16 +44,16 @@ function App() { useEffect(() => { - const unlisten = appWindow.listen("now_playing_change", ({ payload, }) => { + const unlisten = appWindow.listen("now_playing_change", ({ payload, }) => { const displayArtwork = () => { invoke('display_album_art', { uuid: payload.uuid }).then(() => {}) } - // console.log(event); + setNowPlaying( } /> ) @@ -62,15 +63,15 @@ function App() { }, []); useEffect(() => { - const unlisten = appWindow.listen("queue_updated", (_) => { + const unlisten = appWindow.listen("queue_updated", (_) => { // console.log(event); invoke('get_queue').then((_songs) => { - let songs = _songs as any[]; + let songs = _songs as [Song, PlayerLocation][]; setQueue( songs.filter((_, i) => i != 0).map((song, i) => { - const unlisten = appWindow.listen("playing", (isPlaying) => { - setPlaying(isPlaying.payload as boolean) + const unlisten = appWindow.listen("playing", (isPlaying) => { + setPlaying(isPlaying.payload) }) return () => { unlisten.then((f) => f()) } }, []); @@ -133,11 +134,11 @@ interface PlaylistHeadProps { function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary, playlistsInfo, setSelected }: PlaylistHeadProps) { function getPlaylist(playlist: PlaylistInfo) { invoke('get_playlist', { uuid: playlist.uuid }).then((list) => { - setLibrary([...(list as any[]).map((song, i) => { + setLibrary([...(list as Song[]).map((song, i) => { // console.log(song); const reload = () => getPlaylist(playlist) return ( - { - const unlisten = appWindow.listen("playlists_gotten", (_res) => { - // console.log(event); - let res = _res.payload as PlaylistInfo[]; - playlistsInfo.current = [...res]; - // console.log(playlistsInfo, res); + const unlisten = appWindow.listen("playlists_gotten", (_res) => { + const res = _res.payload; + // console.log(event); + playlistsInfo.current = [...res]; + // console.log(playlistsInfo, res); - setPlaylists([ - ...res.map( (list) => { - const _getPlaylist = () => getPlaylist(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(await contextMenuPosition(event)); - } + setPlaylists([ + ...res.map( (list) => { + const _getPlaylist = () => getPlaylist(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(await contextMenuPosition(event)); + } - return ( - - ) - }) - ]) + return ( + + ) + }) + ]) }) return () => { unlisten.then((f) => f()) } }, []); let handle_import = () => { invoke('import_playlist').then((_res) => { - let res = _res as any; + let res = _res as PlaylistInfo; setPlaylists([ ...playlists, @@ -207,11 +208,11 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary, playli setViewName("Library"); invoke('get_library').then((lib) => { let i = 0; - setLibrary([...(lib as any[]).map((song) => { + setLibrary([...(lib as Song[]).map((song) => { // console.log(song); i++; return ( - { - invoke('add_song_to_queue', { uuid: selectedSong.current!.uuid, location: selectedSong.current!.playerLocation }).then(() => {}); + invoke('add_song_to_queue', { uuid: selectedSong.current?.uuid, location: selectedSong.current?.playerLocation }).then(() => {}); } const playNow = () => invoke("play_now", { uuid: selectedSong.current!.uuid, location: selectedSong.current!.playerLocation }).then(() => {}) const playNext = () => invoke("play_next_queue", { uuid: selectedSong.current!.uuid, location: selectedSong.current!.playerLocation }).then(() => {}) @@ -289,14 +290,13 @@ function MainView({ lib_ref, viewName, playlistsInfo, setSelected, selectedSong useEffect(() => { - const unlisten = appWindow.listen("library_loaded", (_) => { + const unlisten = appWindow.listen("library_loaded", (_) => { console.log("library_loaded"); invoke('get_library').then((lib) => { - let i = 0; - setLibrary([...(lib as any[]).map((song) => { - i++; + setLibrary([...(lib as Song[]).map((song, i) => { + console.log("duration", song.duration) return ( - ) @@ -331,12 +331,12 @@ function MainView({ lib_ref, viewName, playlistsInfo, setSelected, selectedSong interface SongProps { - location: any, - playerLocation: string | {"Playlist" : any}, + location: URI[], + playerLocation: PlayerLocation, uuid: string, plays: number, format?: string, - duration: string, + duration: number, last_played?: string, date_added?: string, date_modified?: string, @@ -347,7 +347,7 @@ interface SongProps { reload?: () => void } -function Song(props: SongProps) { +function MainViewSong(props: SongProps) { // console.log(props.tags); // useEffect(() => { // const unlistenPromise = listen("add_song_to_queue", (event) => { @@ -363,16 +363,16 @@ function Song(props: SongProps) { // }, []); const setSelected = () => { props.setSelected(props); - console.log(props.tags.TrackTitle); + console.log(props.tags.Title); } return(
-

{ props.tags.TrackArtist }

-

{ props.tags.TrackTitle }

-

{ props.tags.AlbumTitle }

+

{ props.tags.Artist }

+

{ props.tags.Title }

+

{ props.tags.Album }

{ Math.round(+props.duration / 60) }: { (+props.duration % 60).toString().padStart(2, "0") } @@ -397,8 +397,7 @@ function PlayBar({ playing, setPlaying }: PlayBarProps) { const [lastFmLoggedIn, setLastFmLoggedIn] = useState(false); useEffect(() => { - const unlisten = appWindow.listen("playback_info", ({ payload, }) => { - const info = payload as playbackInfo; + const unlisten = appWindow.listen("playback_info", ({ payload: info, }) => { const pos_ = Array.isArray(info.position) ? info.position![0] : 0; const dur_ = Array.isArray(info.duration) ? info.duration![0] : 0; @@ -463,9 +462,9 @@ function PlayBar({ playing, setPlaying }: PlayBarProps) { } interface NowPlayingProps { - title: string, - artist: string, - album: string, + title: string | undefined, + artist: string | undefined, + album: string | undefined, artwork: JSX.Element } @@ -475,7 +474,7 @@ function NowPlaying({ title, artist, album, artwork }: NowPlayingProps) {

{ artwork }
-

{ title }

+

{ title? title : "Unknown Title" }

{ artist }

{ album }

@@ -491,7 +490,7 @@ interface QueueProps { interface selectedQueueSong { uuid: string, index: number - location: Location, + location: PlayerLocation, } function Queue({ songs, selectedSong, }: QueueProps) { @@ -530,8 +529,8 @@ function Queue({ songs, selectedSong, }: QueueProps) { } interface QueueSongProps { - song: any, - location: Location, + song: Song, + location: PlayerLocation, index: number, setSelectedSong: (song: selectedQueueSong) => void, } @@ -545,8 +544,8 @@ function QueueSong({ song, location, index, setSelectedSong }: QueueSongProps) {
-

{ song.tags.TrackTitle }

-

{ song.tags.TrackArtist }

+

{ song.tags.Title }

+

{ song.tags.Artist }

) diff --git a/src/bindings/AlbumArt.ts b/src/bindings/AlbumArt.ts new file mode 100644 index 0000000..40aaf7b --- /dev/null +++ b/src/bindings/AlbumArt.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { URI } from "./URI"; + +export type AlbumArt = { "Embedded": number } | { "External": URI }; diff --git a/src/bindings/BannedType.ts b/src/bindings/BannedType.ts new file mode 100644 index 0000000..43c1d2f --- /dev/null +++ b/src/bindings/BannedType.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type BannedType = "Shuffle" | "All"; diff --git a/src/bindings/Config.ts b/src/bindings/Config.ts new file mode 100644 index 0000000..d05106c --- /dev/null +++ b/src/bindings/Config.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ConfigConnections } from "./ConfigConnections"; +import type { ConfigLibraries } from "./ConfigLibraries"; + +export type Config = { path: string, backup_folder: string | null, libraries: ConfigLibraries, connections: ConfigConnections, state_path: string, }; diff --git a/src/bindings/ConfigConnections.ts b/src/bindings/ConfigConnections.ts new file mode 100644 index 0000000..8fcb93c --- /dev/null +++ b/src/bindings/ConfigConnections.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ConfigConnections = { discord_rpc_client_id: bigint | null, listenbrainz_token: string | null, last_fm_session: string | null, }; diff --git a/src/bindings/ConfigLibraries.ts b/src/bindings/ConfigLibraries.ts new file mode 100644 index 0000000..feeda4a --- /dev/null +++ b/src/bindings/ConfigLibraries.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ConfigLibrary } from "./ConfigLibrary"; + +export type ConfigLibraries = { default_library: string, library_folder: string, libraries: Array, }; diff --git a/src/bindings/ConfigLibrary.ts b/src/bindings/ConfigLibrary.ts new file mode 100644 index 0000000..ea3d364 --- /dev/null +++ b/src/bindings/ConfigLibrary.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ConfigLibrary = { name: string, path: string, uuid: string, scan_folders: Array | null, }; diff --git a/src/bindings/DoNotTrack.ts b/src/bindings/DoNotTrack.ts new file mode 100644 index 0000000..9f171cb --- /dev/null +++ b/src/bindings/DoNotTrack.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DoNotTrack = "LastFM" | "LibreFM" | "MusicBrainz" | "Discord"; diff --git a/src/bindings/InternalTag.ts b/src/bindings/InternalTag.ts new file mode 100644 index 0000000..919604d --- /dev/null +++ b/src/bindings/InternalTag.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DoNotTrack } from "./DoNotTrack"; +import type { SongType } from "./SongType"; + +export type InternalTag = { "DoNotTrack": DoNotTrack } | { "SongType": SongType } | { "SongLink": [string, SongType] } | { "VolumeAdjustment": number }; diff --git a/src/bindings/PlaybackInfo.ts b/src/bindings/PlaybackInfo.ts new file mode 100644 index 0000000..f708b84 --- /dev/null +++ b/src/bindings/PlaybackInfo.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PlaybackInfo = { position: [number, number], duration: [number, number], }; diff --git a/src/bindings/PlayerLocation.ts b/src/bindings/PlayerLocation.ts new file mode 100644 index 0000000..621a73a --- /dev/null +++ b/src/bindings/PlayerLocation.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PlayerLocation = "Test" | "Library" | { "Playlist": string } | "File" | "Custom"; diff --git a/src/bindings/Service.ts b/src/bindings/Service.ts new file mode 100644 index 0000000..bf51471 --- /dev/null +++ b/src/bindings/Service.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type Service = "InternetRadio" | "Spotify" | "Youtube" | "None"; diff --git a/src/bindings/Song.ts b/src/bindings/Song.ts new file mode 100644 index 0000000..f8ac73b --- /dev/null +++ b/src/bindings/Song.ts @@ -0,0 +1,14 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AlbumArt } from "./AlbumArt"; +import type { BannedType } from "./BannedType"; +import type { InternalTag } from "./InternalTag"; +import type { URI } from "./URI"; + +/** + * Stores information about a single song + */ +export type Song = { location: Array, uuid: string, plays: number, skips: number, favorited: boolean, banned: BannedType | null, rating: number | null, +/** + * MIME type + */ +format: string | null, duration: number, play_time: number, last_played: number, date_added: number, date_modified: number, album_art: Array, tags: any, internal_tags: Array, }; diff --git a/src/bindings/SongType.ts b/src/bindings/SongType.ts new file mode 100644 index 0000000..6942c97 --- /dev/null +++ b/src/bindings/SongType.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SongType = "Main" | "Instrumental" | "Remix" | { "Custom": string }; diff --git a/src/bindings/Tag.ts b/src/bindings/Tag.ts new file mode 100644 index 0000000..4c6fbb0 --- /dev/null +++ b/src/bindings/Tag.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * A tag for a song + */ +export type Tag = "Title" | "Album" | "Artist" | "AlbumArtist" | "Genre" | "Comment" | "TrackNumber" | "DiskNumber" | { "Field": string } | string; diff --git a/src/bindings/URI.ts b/src/bindings/URI.ts new file mode 100644 index 0000000..587e9ea --- /dev/null +++ b/src/bindings/URI.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Service } from "./Service"; + +export type URI = { "Local": string } | { "Cue": { location: string, index: number, start: number, end: number, } } | { "Remote": [Service, string] }; diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 0f2b034..0000000 --- a/src/types.ts +++ /dev/null @@ -1,70 +0,0 @@ -export interface Configlibrary { - name: string, - path: string, - uuid: string, - scan_folders?: string[] -} - -export interface ConfigLibraries { - default_library: string, - library_folder: string, - libraries: Configlibrary[] -} - -export interface Config { - path: string, - backup_folder?: string, - libraries: ConfigLibraries, - volume: number, - connections: ConfigConnections, -} - -export interface ConfigConnections { - discord_rpc_client_id?: number, - last_fm_session?: string, - listenbrainz_token?: string -} - -export interface Song { - location: URI[], - uuid: string, - plays: number, - skips: number, - favorited: boolean, - banned?: BannedType, - rating?: number, - format?: string, - duration: number, - play_time: number, - last_played?: number, - date_added?: number, - date_modified?: number, - album_art: AlbumArt[], - tags: Map, - internal_tags: InternalTag[], -} - -export enum InternalTag { - -} - -export enum Tag { - -} - -export enum AlbumArt { - -} - -export enum URI { - -} - -export enum BannedType { - -} - -export interface playbackInfo { - position?: [number, number], - duration?: [number, number], -} \ No newline at end of file