Compare commits
3 Commits
Author | SHA1 | Date |
---|---|---|
Emilia Allison | 2ed3d750c9 | |
Emilia Allison | 85416a554f | |
Emilia Allison | 496d07455a |
|
@ -1,2 +1,3 @@
|
||||||
/target
|
/target
|
||||||
.env
|
.env
|
||||||
|
dist/
|
||||||
|
|
|
@ -372,7 +372,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cool_spotify_server"
|
name = "cool_spotify_server"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
@ -620,6 +620,17 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "front"
|
name = "front"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"gloo-net",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"wasm-logger",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
|
@ -694,6 +705,40 @@ dependencies = [
|
||||||
"wasi",
|
"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]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.20"
|
version = "0.3.20"
|
||||||
|
@ -1268,6 +1313,26 @@ version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
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]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
|
@ -2093,6 +2158,17 @@ version = "0.2.87"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
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]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.64"
|
version = "0.3.64"
|
||||||
|
|
|
@ -6,3 +6,27 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[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',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
[build]
|
||||||
|
release = true
|
||||||
|
public_url = "/cool-stuff/cool-spotify-blend/"
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link data-trunk rel="scss" href="assets/scss/index.scss">
|
||||||
|
<title>Cool Spotify Blend</title>
|
||||||
|
</head>
|
||||||
|
</html>
|
|
@ -0,0 +1,11 @@
|
||||||
|
use gloo_net::http::Request;
|
||||||
|
|
||||||
|
use crate::spotify_types::*;
|
||||||
|
|
||||||
|
pub async fn fetch() -> Result<Vec<(TrackObject, UserProfile)>, gloo_net::Error> {
|
||||||
|
Ok(Request::get("https://spotify.api.ilia.moe/status")
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json()
|
||||||
|
.await?)
|
||||||
|
}
|
|
@ -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() {
|
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<web_sys::Element, JsValue> {
|
||||||
|
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<web_sys::Element, JsValue> {
|
||||||
|
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<web_sys::Element, JsValue> {
|
||||||
|
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<web_sys::Element, JsValue> {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
../../server/src/spotify_types.rs
|
|
@ -22,6 +22,7 @@ pub struct AlbumObject {
|
||||||
pub release_date: String,
|
pub release_date: String,
|
||||||
pub uri: Uri,
|
pub uri: Uri,
|
||||||
pub genres: Option<String>,
|
pub genres: Option<String>,
|
||||||
|
pub images: Vec<ImageObject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
@ -51,9 +52,9 @@ pub struct SpotifyId(pub String);
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct UserProfile {
|
pub struct UserProfile {
|
||||||
id: String,
|
pub id: String,
|
||||||
display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
images: Option<Vec<ImageObject>>,
|
pub images: Option<Vec<ImageObject>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
|
Loading…
Reference in New Issue