diff --git a/.gitignore b/.gitignore
index fedaa2b..bc97e80 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/target
.env
+dist/
diff --git a/Cargo.lock b/Cargo.lock
index d97ac9d..1011435 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -372,7 +372,7 @@ dependencies = [
[[package]]
name = "cool_spotify_server"
-version = "0.1.0"
+version = "0.1.1"
dependencies = [
"actix-web",
"dotenvy",
@@ -620,6 +620,17 @@ dependencies = [
[[package]]
name = "front"
version = "0.1.0"
+dependencies = [
+ "gloo-net",
+ "js-sys",
+ "log",
+ "serde",
+ "serde_json",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-logger",
+ "web-sys",
+]
[[package]]
name = "futures-channel"
@@ -694,6 +705,40 @@ dependencies = [
"wasi",
]
+[[package]]
+name = "gloo-net"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3000ef231a67d5bfee6b35f2c0f6f5c8d45b3381ef5bbbea603690ec4e539762"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-sink",
+ "gloo-utils",
+ "http",
+ "js-sys",
+ "pin-project",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-utils"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e"
+dependencies = [
+ "js-sys",
+ "serde",
+ "serde_json",
+ "wasm-bindgen",
+ "web-sys",
+]
+
[[package]]
name = "h2"
version = "0.3.20"
@@ -1268,6 +1313,26 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+[[package]]
+name = "pin-project"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.22",
+]
+
[[package]]
name = "pin-project-lite"
version = "0.2.9"
@@ -2093,6 +2158,17 @@ version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
+[[package]]
+name = "wasm-logger"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718"
+dependencies = [
+ "log",
+ "wasm-bindgen",
+ "web-sys",
+]
+
[[package]]
name = "web-sys"
version = "0.3.64"
diff --git a/front/Cargo.toml b/front/Cargo.toml
index adb5b38..646d084 100644
--- a/front/Cargo.toml
+++ b/front/Cargo.toml
@@ -6,3 +6,27 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+wasm-bindgen = "0.2.74"
+js-sys = "0.3.51"
+wasm-bindgen-futures = "0.4.24"
+
+gloo-net = { version = "0.3", features = ["http"] }
+
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+
+log = "0.4"
+wasm-logger = "0.2"
+
+[dependencies.web-sys]
+version = "0.3.4"
+features = [
+ 'Window',
+ 'Document',
+ 'Element',
+ 'HtmlElement',
+ 'Node',
+ 'HtmlAnchorElement',
+ 'HtmlImageElement',
+]
+
diff --git a/front/assets/scss/index.scss b/front/assets/scss/index.scss
new file mode 100644
index 0000000..895a931
--- /dev/null
+++ b/front/assets/scss/index.scss
@@ -0,0 +1,86 @@
+@use "sass:math";
+
+$card-height: 8vh;
+$card-gap: 1.5vh;
+
+body {
+ box-sizing: border-box;
+}
+
+a {
+ text-decoration: none;
+ font-family: sans-serif;
+ color: black;
+}
+
+img {
+}
+
+.outer-div {
+ display: flex;
+ flex-direction: column;
+}
+
+.inner-div {
+ height: $card-height;
+ margin-top: ($card-gap * 0.5);
+ margin-bottom: ($card-gap * 0.5);
+
+ display: flex;
+ justify-content: space-between;
+
+ box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
+
+ &:nth-child(odd) {
+ background: #e0e0e0;
+ }
+}
+
+.track-info {
+ height: 100%;
+ width: 70%;
+
+ display: grid;
+ grid-template-columns: ($card-height * 1.1) 1fr;
+ grid-template-rows: 1fr 1fr;
+ align-items: center;
+
+ img {
+ height: $card-height;
+ grid-column: 1 / 2;
+ grid-row: 1 / span 2;
+ }
+ p {
+ margin: 0;
+ padding: 0;
+ }
+
+ .track_name {
+ grid-column: 2 / span 1;
+ grid-row: 1 / span 1;
+ }
+ .track_album {
+ grid-column: 2 / span 1;
+ grid-row: 2 / span 1;
+ }
+}
+
+.user-info {
+ height: 100%;
+ width: 30%;
+
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+
+ img {
+ height: $card-height;
+ }
+ p {
+ margin: 0;
+ margin-right: ($card-height * 0.1);
+ padding: 0;
+
+ text-align: right;
+ }
+}
diff --git a/front/index.html b/front/index.html
new file mode 100644
index 0000000..9f50d24
--- /dev/null
+++ b/front/index.html
@@ -0,0 +1,8 @@
+
+
+
+
+
+ Cool Spotify Blend
+
+
diff --git a/front/src/fetch.rs b/front/src/fetch.rs
new file mode 100644
index 0000000..8d80aef
--- /dev/null
+++ b/front/src/fetch.rs
@@ -0,0 +1,11 @@
+use gloo_net::http::Request;
+
+use crate::spotify_types::*;
+
+pub async fn fetch() -> Result, gloo_net::Error> {
+ Ok(Request::get("https://spotify.api.ilia.moe/status")
+ .send()
+ .await?
+ .json()
+ .await?)
+}
diff --git a/front/src/main.rs b/front/src/main.rs
index e7a11a9..4ad3234 100644
--- a/front/src/main.rs
+++ b/front/src/main.rs
@@ -1,3 +1,144 @@
+mod fetch;
+mod spotify_types; // Symlinked from server because I do not want to separate this into another
+ // crate right now.
+ // This is a stupid way to do this.
+
+use wasm_bindgen::prelude::*;
+use wasm_bindgen_futures::spawn_local;
+use web_sys::{Document, HtmlAnchorElement, HtmlImageElement};
+
+use crate::spotify_types::*;
+
+// Convenience alias
+type TrackUserTuple = (TrackObject, UserProfile);
+
fn main() {
- println!("Hello, world!");
+ wasm_logger::init(wasm_logger::Config::default());
+
+ log::info!("wasm executing now :)");
+ spawn_local(app());
+}
+
+async fn app() {
+ let window = web_sys::window().expect("We have to have a window.");
+ let document = window.document().expect("Window should have a document.");
+ let body = document.body().expect("Document should have a body.");
+
+ let outer_div = generate_outer_div(&document).unwrap();
+ body.append_child(&outer_div).unwrap();
+
+ let data = fetch::fetch().await;
+ if data.is_err() {
+ log::error!("Could not fetch data, nothing to do.");
+ log::error!("{:?}", data.unwrap_err());
+ return (); // This is actually a graceful exit!
+ }
+ let data = data.unwrap(); // We know this is fine from above
+ let rows = data.iter()
+ .map(|row| generate_row(&document, &row))
+ .flatten(); // Intentionally not collecting--next step is to consume.
+ for row in rows {
+ outer_div.append_child(&row).unwrap();
+ }
+
+}
+
+fn generate_outer_div(document: &Document) -> Result {
+ let outer_div = document.create_element("div")?;
+ outer_div.set_class_name("outer-div");
+ Ok(outer_div)
+}
+
+fn generate_row(document: &Document, data: &TrackUserTuple) -> Result {
+ let container = document.create_element("div")?;
+ container.set_class_name("inner-div");
+
+ let track_info = generate_track_info(document, data)?;
+ let user_info = generate_user_info(document, data)?;
+
+ container.append_child(&track_info).unwrap();
+ container.append_child(&user_info).unwrap();
+
+ Ok(container)
+}
+
+fn generate_track_info(document: &Document, data: &TrackUserTuple) -> Result {
+ let track_info = document.create_element("div")?;
+ track_info.set_class_name("track-info");
+
+ // Scoping these to prevent accidentally referencing the wrong thing
+ // and to free memory sooner maybe
+ {
+ let track_name = document.create_element("p")?;
+ track_name.set_class_name("track_name");
+ let track_name_a: HtmlAnchorElement = document.create_element("a")?.dyn_into().unwrap();
+ track_name_a.set_text_content(Some(&data.0.name));
+ track_name_a.set_href(&create_track_link(&data.0.uri));
+ track_name.append_child(&track_name_a).unwrap();
+ track_info.append_child(&track_name).unwrap();
+ }
+
+ if let Some(album) = &data.0.album {
+ {
+ let track_album = document.create_element("p")?;
+ track_album.set_class_name("track_album");
+ let track_album_a: HtmlAnchorElement = document.create_element("a")?.dyn_into().unwrap();
+ track_album_a.set_text_content(Some(&album.name));
+ track_album_a.set_href(&create_album_link(&album.uri));
+
+ track_album.append_child(&track_album_a).unwrap();
+
+ track_info.append_child(&track_album).unwrap();
+ }
+ if let Some(image) = &album.images.get(0) {
+ let track_album_img: HtmlImageElement = document.create_element("img")?.dyn_into().unwrap();
+ track_album_img.set_alt("Album cover");
+ track_album_img.set_src(&image.url);
+
+ track_info.append_child(&track_album_img).unwrap();
+ }
+ }
+
+ Ok(track_info)
+}
+
+fn generate_user_info(document: &Document, data: &TrackUserTuple) -> Result {
+ let user_info = document.create_element("div")?;
+ user_info.set_class_name("user-info");
+
+ {
+ let user_name = document.create_element("p")?;
+ user_name.set_class_name("user_name");
+
+ let user_name_a: HtmlAnchorElement = document.create_element("a")?.dyn_into().unwrap();
+ if let Some(display_name) = &data.1.display_name {
+ user_name_a.set_text_content(Some(&display_name));
+ } else {
+ user_name_a.set_text_content(Some(&data.1.id));
+ }
+ user_name_a.set_href(&create_user_link(&data.1.id));
+ user_name.append_child(&user_name_a).unwrap();
+
+ user_info.append_child(&user_name).unwrap();
+ }
+
+ if let Some(Some(image)) = data.1.images.as_ref().map(|imgs| imgs.get(0)) {
+ let user_img: HtmlImageElement = document.create_element("img")?.dyn_into().unwrap();
+ user_img.set_alt("Profile picture");
+ user_img.set_src(&image.url);
+
+ user_info.append_child(&user_img).unwrap();
+ }
+
+ Ok(user_info)
+}
+
+fn create_track_link(uri: &Uri) -> String {
+ "https://open.spotify.com/track/".to_owned() + &uri.get_suffix()
+}
+fn create_album_link(uri: &Uri) -> String {
+ "https://open.spotify.com/album/".to_owned() + &uri.get_suffix()
+}
+fn create_user_link(id: &str) -> String {
+ "https://open.spotify.com/user/".to_owned() + id
}
diff --git a/front/src/spotify_types.rs b/front/src/spotify_types.rs
new file mode 120000
index 0000000..2620b47
--- /dev/null
+++ b/front/src/spotify_types.rs
@@ -0,0 +1 @@
+../../server/src/spotify_types.rs
\ No newline at end of file
diff --git a/server/src/spotify_types.rs b/server/src/spotify_types.rs
index b903faa..d42f16f 100644
--- a/server/src/spotify_types.rs
+++ b/server/src/spotify_types.rs
@@ -52,9 +52,9 @@ pub struct SpotifyId(pub String);
#[derive(Deserialize, Serialize, Debug, Clone)]
#[non_exhaustive]
pub struct UserProfile {
- id: String,
- display_name: Option,
- images: Option>,
+ pub id: String,
+ pub display_name: Option,
+ pub images: Option>,
}
#[derive(Deserialize, Serialize, Debug, Clone)]