diff --git a/assets/backgrounds/22.gif b/assets/backgrounds/22.gif new file mode 100644 index 0000000..fe6968e Binary files /dev/null and b/assets/backgrounds/22.gif differ diff --git a/assets/backgrounds/22.mp4 b/assets/backgrounds/22.mp4 new file mode 100644 index 0000000..df243f8 Binary files /dev/null and b/assets/backgrounds/22.mp4 differ diff --git a/assets/backgrounds/22.webm b/assets/backgrounds/22.webm new file mode 100644 index 0000000..1d0392e Binary files /dev/null and b/assets/backgrounds/22.webm differ diff --git a/assets/default_album.png b/assets/default_album.png new file mode 100644 index 0000000..17f293a Binary files /dev/null and b/assets/default_album.png differ diff --git a/src/copland.rs b/src/copland.rs index 5520a10..7a5a869 100644 --- a/src/copland.rs +++ b/src/copland.rs @@ -271,7 +271,7 @@ impl Component for Copland { self.theme = theme; let el = self.background_video.cast::().unwrap(); el.load(); - el.play().ok(); + // el.onloadeddata().unwrap(). true } CoplandMsg::NewSticky => { diff --git a/src/main.rs b/src/main.rs index cf5f351..85e3fac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use yew::prelude::*; use rand::Rng; -pub const MAX_BACKGROUND_INDEX: u32 = 22; +pub const MAX_BACKGROUND_INDEX: u32 = 23; #[derive(Deserialize)] pub struct NoteJson { diff --git a/src/windows/spotify.rs b/src/windows/spotify.rs index 37e2d90..dbe66a6 100644 --- a/src/windows/spotify.rs +++ b/src/windows/spotify.rs @@ -1,10 +1,14 @@ +use std::vec; + use yew::prelude::*; use gloo::net::websocket::{Message as WsMessage, futures::WebSocket}; use wasm_bindgen_futures::spawn_local; use futures::{channel::mpsc::UnboundedSender, SinkExt, StreamExt}; -use serde_json::Value; use gloo::timers::callback::Interval; use js_sys::Date; +use gloo::net::http::Request; +use serde_json::Value; + struct LanyardData { album_art: String, @@ -18,13 +22,19 @@ struct LanyardData { #[derive(Debug)] pub enum Msg { LanyardMessage(WsMessage), - UpdateTime + UpdateTime, + UpdateHistory, + SaveHistory(Vec), + ToggleShowHistory } pub struct Spotify { lanyard_ws_write: UnboundedSender, lanyard_data: Option, - update_timer: Option + update_timer: Option, + show_history: bool, + history: Vec, + last_fm_current: Option } impl Component for Spotify { type Message = Msg; @@ -53,10 +63,19 @@ impl Component for Spotify { } }); + let link = ctx.link().clone(); + link.send_message(Msg::UpdateHistory); + Interval::new(10_000, move || + link.send_message(Msg::UpdateHistory) + ).forget(); + Self { lanyard_ws_write: tx, lanyard_data: None, - update_timer: None + update_timer: None, + show_history: false, + history: vec![], + last_fm_current: None, } } @@ -108,6 +127,50 @@ impl Component for Spotify { Msg::UpdateTime => { true }, + Msg::UpdateHistory => { + let save_history = ctx.link().callback(Msg::SaveHistory); + + spawn_local(async move { + let text = Request::get("https://api.rovi.me/lastfm") + .send() + .await + .unwrap() + .text() + .await + .unwrap(); + let json: Value = serde_json::from_str(&text).unwrap(); + let tracks = json["recenttracks"]["track"].as_array().unwrap(); + let tracks: Vec = tracks.iter().map(|t| { + let t = t.clone(); + LastFmHistoryHOCProps { + current_time: Date::now() as u64 / 1000, + album_art: t["image"][3]["#text"].as_str().unwrap_or_default().to_string(), + album: t["album"]["#text"].as_str().unwrap_or_default().to_string(), + song: t["name"].as_str().unwrap_or_default().to_string(), + artist: t["artist"]["#text"].as_str().unwrap_or_default().to_string(), + listened_at: t["date"]["uts"].as_str().unwrap_or("0").parse().unwrap_or_default(), + } + }) + .collect(); + + log::info!("Updated last fm history."); + save_history.emit(tracks); + }); + false + }, + Msg::SaveHistory(history) => { + if let Some(current) = history.iter().find(|p| p.listened_at == 0) { + self.last_fm_current = Some(current.clone()); + } else { + self.last_fm_current = None; + } + self.history = history; + true + }, + Msg::ToggleShowHistory => { + self.show_history = !self.show_history; + true + }, _ => { log::info!("not covered!"); false @@ -115,7 +178,46 @@ impl Component for Spotify { } } - fn view(&self, _ctx: &Context) -> Html { + fn view(&self, ctx: &Context) -> Html { + let current_time = Date::now() as u64 / 1000; + + let history = html! { +
+
+
+ { self.history.iter() + .filter(|p| p.listened_at != 0) + .map(|p| html! { + + } ).collect::() } +
+
+
+ }; + + let toggle_show_history = ctx.link().callback(|_| Msg::ToggleShowHistory); + let button_open = if self.show_history { + Some("open") + } else { + None + }; + + + let history = html! { +
+ + if self.show_history { + { history } + } +
+ }; + if let Some(lanyard_data) = self.lanyard_data.as_ref() { let current_time = Date::now() as u64; @@ -129,20 +231,131 @@ impl Component for Spotify { } html!{ -
- Spotify album art -
-

{ lanyard_data.song_name.clone() }

-

{ "On " }{ lanyard_data.album_name.clone() }

-

{ "By " }{ lanyard_data.artist_name.clone() }

-

{ "Elapsed: " }{ format_time(elapsed_time) }{" / "}{ format_time(total_time) }

+
+
+ Spotify album art +
+

{ lanyard_data.song_name.clone() }

+

{ "On " }{ lanyard_data.album_name.clone() }

+

{ "By " }{ lanyard_data.artist_name.clone() }

+

{ "Elapsed: " }{ format_time(elapsed_time) }{" / "}{ format_time(total_time) }

+
+
+ { history } +
+ } + } else if let Some(last_fm_current) = self.last_fm_current.as_ref() { + html!{ +
+
+ Spotify album art +
+

{ last_fm_current.song.clone() }

+

{ "On " }{ last_fm_current.album.clone() }

+

{ "By " }{ last_fm_current.artist.clone() }

+

{"Currently listening"}

+
+ { history }
} } else { html! { -

{ "Not currently listening to anything :(" }

+
+

{ "Not currently listening to anything :(" }

+ { history } +
} } } +} + + +#[derive(Properties, PartialEq, Debug, Clone)] +pub struct LastFmHistoryHOCProps { + pub current_time: u64, + pub album_art: String, + pub album: String, + pub song: String, + pub artist: String, + pub listened_at: u64, +} + +#[derive(Properties, PartialEq)] +pub struct LastFmHistoryProps { + pub album_art: String, + pub song: String, + pub artist: String, + pub formatted_time: String, +} + +#[function_component(LastFmHistoryHOC)] +pub fn last_fm_history_hoc(props: &LastFmHistoryHOCProps) -> Html { + let elapsed = props.current_time - props.listened_at; + + let s_per_minute = 60; + let s_per_hour = 3600; + let s_per_day = 86400; + let s_per_month = 2592000; + let s_per_year = 31536000; + + let minutes_passed = elapsed / s_per_minute; + let hours_passed = elapsed / s_per_hour; + let days_passed = elapsed / s_per_day; + let months_passed = elapsed / s_per_month; + let years_passed = elapsed / s_per_year; + + let formatted_time = if years_passed > 1 { + format!("{} years ago", years_passed) + } else if years_passed == 1 { + String::from("1 year ago") + } else if months_passed > 1 { + format!("{} months ago", months_passed) + } else if months_passed == 1 { + String::from("1 month ago") + } else if days_passed > 1 { + format!("{} days ago", days_passed) + } else if days_passed == 1 { + String::from("1 day ago") + } else if hours_passed > 1 { + format!("{} hours ago", hours_passed) + } else if hours_passed == 1 { + String::from("1 hour ago") + } else if minutes_passed > 1 { + format!("{} minutes ago", minutes_passed) + } else if minutes_passed == 1 { + String::from("1 minute ago") + } else if elapsed > 1 { + format!("{} seconds ago", elapsed) + } else { + String::from("1 second ago") + }; + + html! { + + } +} + +#[function_component(LastFmHistory)] +pub fn last_fm_history(props: &LastFmHistoryProps) -> Html { + html!{ +
+ Track album art +
+

{ props.song.clone() }

+

{ props.artist.clone() }

+

{ props.formatted_time.clone() }

+
+
+ } } \ No newline at end of file diff --git a/style.css b/style.css index b32975b..e582e6e 100644 --- a/style.css +++ b/style.css @@ -153,4 +153,59 @@ video.background { min-height: 50px; max-height: 100px; background-color: rgba(255, 255, 255, 0.4); +} +.history-container { + margin-top: 10px; +} +.history-container > button { + width: 100%; + position: relative; +} +.history-container > button::after { + content: url("data:image/svg+xml;charset=utf-8,"); + bottom: 0px; + right: 5px; + position: absolute; +} +.history-container > button.open::after { + content: url("data:image/svg+xml;charset=utf-8,"); +} +.lastfm-scroll-container { + max-height: 200px; + overflow-y: scroll; +} +.lastfm-container { + display: flex; + flex-direction: column; + gap: 10px; +} +.lastfm-container img { + width: 50px; + height: 50px; + position: relative; +} +.lastfm-container img:before { + content: ' '; + display: block; + position: absolute; + height: 50px; + width: 50px; + background-image: url("/assets/default_album.png"); +} +.lastfm-container > div { + display: flex; + flex-direction: row; + gap: 10px; +} +.lastfm-container > div > div { + min-width: 0; + display: flex; + flex-direction: column; + justify-content: space-around; +} +.lastfm-container > div > div > p { + margin: 0; + /* white-space: nowrap; */ + /* overflow: hidden; */ + /* text-overflow: ellipsis; */ } \ No newline at end of file