dango-music-player/src/App.tsx
2024-12-28 01:00:40 -05:00

383 lines
11 KiB
TypeScript

import { useEffect, useState } from "react";
import { convertFileSrc, invoke } from "@tauri-apps/api/core";
import "./App.css";
import { Config } 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";
const appWindow = getCurrentWebviewWindow();
function App() {
const library = useState<JSX.Element[]>([]);
const [queue, setQueue] = useState<JSX.Element[]>([]);
const [playing, setPlaying] = useState(false);
const [playlists, setPlaylists] = useState<JSX.Element[]>([]);
const [viewName, setViewName] = useState("Library");
const [nowPlaying, setNowPlaying] = useState<JSX.Element>(
<NowPlaying
title="Title"
album="Album"
artist="Artist"
artwork={<img src={convertFileSrc("abc") + "?" + "default" } id="nowPlayingArtwork" alt="Now Playing Artwork" key={'default_image'} />}
/>
);
useEffect(() => {
const unlisten = appWindow.listen<any>("now_playing_change", ({ payload, }) => {
const displayArtwork = () => {
invoke('display_album_art', { uuid: payload.uuid }).then(() => {})
}
// console.log(event);
setNowPlaying(
<NowPlaying
title={ payload.tags.TrackTitle }
album={ payload.tags.AlbumTitle }
artist={ payload.tags.TrackArtist }
artwork={ <img src={convertFileSrc("abc") + "?" + payload.uuid } id="nowPlayingArtwork" alt="Now Playing Artwork" key={payload.uuid} onDoubleClick={ displayArtwork } /> }
/>
)
})
return () => { unlisten.then((f) => f()) }
}, []);
useEffect(() => {
const unlisten = appWindow.listen<any>("queue_updated", (_) => {
// console.log(event);
invoke('get_queue').then((_songs) => {
let songs = _songs as any[]
setQueue(
songs.filter((_, i) => i != 0).map((song, i) =>
<QueueSong
song={ song[0] }
location={ song[1] as "Library" | {"Playlist" : string}}
index={i+1}
key={ song.uuid + '_' + Math.floor((Math.random() * 100_000) + 1) + '_' + Date.now() }
/>
)
)
})
})
return () => { unlisten.then((f) => f()) }
}, []);
useEffect(() => {
const unlisten = appWindow.listen<any>("playing", (_) => {
setPlaying(true)
})
return () => { unlisten.then((f) => f()) }
}, []);
useEffect(() => {
const unlisten = appWindow.listen<any>("paused", (_) => {
setPlaying(false)
})
return () => { unlisten.then((f) => f()) }
}, []);
useEffect(() => {
getConfig();
}, [])
return (
<main className="container">
<div className="leftSide">
<PlaylistHead playlists={ playlists } setPlaylists={ setPlaylists } setViewName={ setViewName } setLibrary={ library[1] } />
<MainView lib_ref={ library } viewName={ viewName } />
<PlayBar playing={ playing } setPlaying={ setPlaying } />
</div>
<div className="rightSide">
{ nowPlaying }
<Queue songs={ queue } />
</div>
</main>
);
}
export default App;
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[]>>,
}
function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: PlaylistHeadProps) {
useEffect(() => {
const unlisten = appWindow.listen<any[]>("playlists_gotten", (_res) => {
// console.log(event);
let res = _res.payload;
setPlaylists([
...res.map( (item) => {
return (
<button onClick={ () => {
invoke('get_playlist', { uuid: item.uuid }).then((list) => {
setLibrary([...(list as any[]).map((song) => {
// console.log(song);
return (
<Song
key={ song.uuid }
location={ song.location }
playerLocation={ {"Playlist" : item.uuid } }
uuid={ song.uuid }
plays={ song.plays }
duration={ song.duration }
tags={ song.tags }
/>
)
})])
})
setViewName( item.name )
} } key={ 'playlist_' + item.uuid }>{ item.name }</button>
)
})
])
})
return () => { unlisten.then((f) => f()) }
}, []);
let handle_import = () => {
invoke('import_playlist').then((_res) => {
let res = _res as any;
setPlaylists([
...playlists,
<button onClick={ () => {
invoke('get_playlist', { uuid: res.uuid }).then((list) => {
console.log((list as any[]).length);
setLibrary([...(list as any[]).map((song) => {
// console.log(song);
return (
<Song
key={ song.uuid }
location={ song.location }
playerLocation={ {"Playlist" : res.uuid } }
uuid={ song.uuid }
plays={ song.plays }
duration={ song.duration }
tags={ song.tags }
/>
)
})])
})
setViewName( res.name )
} } key={ 'playlist_' + res.uuid }>{ res.name }</button>
])
console.log(res.name);
})
}
return (
<section className="playlistHead">
<button onClick={() => {
setViewName("Library");
invoke('get_library').then((lib) => {
setLibrary([...(lib as any[]).map((song) => {
console.log(song);
return (
<Song
key={ song.uuid }
location={ song.location }
playerLocation="Library"
uuid={ song.uuid }
plays={ song.plays }
duration={ song.duration }
tags={ song.tags }
/>
)
})])
})
} }>Library</button>
{ playlists }
<button onClick={ handle_import }>Import .m3u Playlist</button>
</section>
)
}
interface MainViewProps {
lib_ref: [JSX.Element[], React.Dispatch<React.SetStateAction<JSX.Element[]>>],
viewName: string
}
function MainView({ lib_ref, viewName }: MainViewProps) {
const [library, setLibrary] = lib_ref;
useEffect(() => {
const unlisten = appWindow.listen<any>("library_loaded", (_) => {
console.log("library_loaded");
invoke('get_library').then((lib) => {
setLibrary([...(lib as any[]).map((song) => {
return (
<Song
key={ song.uuid }
location={ song.location }
playerLocation="Library"
uuid={ song.uuid }
plays={ song.plays }
duration={ song.duration }
tags={ song.tags }
/>
)
})])
})
invoke('get_playlists').then(() => {})
})
return () => { unlisten.then((f) => f()) }
}, []);
return (
<div className="mainView">
<h1>{ viewName }</h1>
<div>{ library }</div>
</div>
)
}
interface SongProps {
location: any,
playerLocation: string | {"Playlist" : any},
uuid: string,
plays: number,
format?: string,
duration: string,
last_played?: string,
date_added?: string,
date_modified?: string,
tags: any
}
function Song(props: SongProps) {
// console.log(props.tags);
return(
<div onClick={() => {
invoke("play_now", { uuid: props.uuid, location: props.playerLocation }).then(() => {})
}} className="song">
<p className="artist">{ props.tags.AlbumArtist }</p>
<p className="title">{ props.tags.TrackTitle }</p>
<p className="album">{ props.tags.AlbumTitle }</p>
<p className="duration">
{ Math.round(+props.duration / 60) }:
{ (+props.duration % 60).toString().padStart(2, "0") }
</p>
{/*<button onClick={(_) => {
invoke('add_song_to_queue', { uuid: props.uuid, location: props.playerLocation }).then(() => {} )
}}
>Add to Queue</button>
<button onClick={() => {
invoke("play_now", { uuid: props.uuid, location: props.playerLocation }).then(() => {})
}}>Play Now</button>*/}
</div>
)
}
interface PlayBarProps {
playing: boolean,
setPlaying: React.Dispatch<React.SetStateAction<boolean>>
}
function PlayBar({ playing, setPlaying }: PlayBarProps) {
return (
<section id="playBar" className="playBar">
<div className="topHalf">
<div>
<button>shuffle</button>
<button>loop</button>
</div>
<button onClick={ () => invoke('prev').then(() => {}) }>prev</button>
<button onClick={ (_) => {
setPlaying( playing ? false : true );
invoke( playing ? 'pause' : 'play' ).then(() => {})
}}>{ playing ? 'pause' : 'play' }</button>
<button onClick={ () => invoke('next').then(() => {}) }>next</button>
<input type="range" name="volume" id="volumeSlider" onChange={ (volume) => {
invoke('set_volume', { volume: volume.target.value }).then(() => {})
}} />
</div>
<input type="range" name="seek" id="seekBar" />
</section>
)
}
interface NowPlayingProps {
title: string,
artist: string,
album: string,
artwork: JSX.Element
}
function NowPlaying({ title, artist, album, artwork }: NowPlayingProps) {
return (
<section className="nowPlaying">
{ artwork }
<h3>{ title }</h3>
<p>{ artist }</p>
<p>{ album }</p>
</section>
)
}
interface QueueProps {
songs: JSX.Element[],
}
function Queue({ songs }: QueueProps) {
return (
<section className="Queue">
{ songs }
</section>
)
}
interface QueueSongProps {
song: any,
location: "Library" | {"Playlist": string},
index: number,
}
function QueueSong({ song, location, index }: QueueSongProps) {
console.log(song.tags);
let removeFromQueue = () => {
invoke('remove_from_queue', { index: index }).then(() => {})
}
let playNow = () => {
invoke('play_now', { uuid: song.uuid, location: location }).then(() => {})
}
return (
<div className="queueSong" onAuxClick={ removeFromQueue } onClickCapture={ playNow }>
<img className="queueSongCoverArt" src={ convertFileSrc('abc') + '?' + song.uuid } key={ 'coverArt_' + song.uuid }/>
<div className="queueSongTags">
<p className="queueSongTitle">{ song.tags.TrackTitle }</p>
<p className="queueSongArtist">{ song.tags.TrackArtist }</p>
</div>
</div>
)
}
function getConfig(): any {
invoke('get_config').then( (_config) => {
let config = _config as Config;
if (config.libraries.libraries.length == 0) {
newWindow()
} else {
// console.log("else");
invoke('lib_already_created').then(() => {})
}
})
}
function newWindow() {
invoke('new_library_window').then(() => {})
}