Front end page

This commit is contained in:
Emilia Allison 2023-07-13 15:40:03 -04:00
parent 496d07455a
commit 85416a554f
Signed by: emilia
GPG Key ID: 7A3F8997BFE894E0
9 changed files with 353 additions and 5 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/target
.env
dist/

78
Cargo.lock generated
View File

@ -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"

View File

@ -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',
]

View File

@ -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;
}
}

8
front/index.html Normal file
View File

@ -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>

11
front/src/fetch.rs Normal file
View File

@ -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?)
}

View File

@ -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<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
}

1
front/src/spotify_types.rs Symbolic link
View File

@ -0,0 +1 @@
../../server/src/spotify_types.rs

View File

@ -52,9 +52,9 @@ pub struct SpotifyId(pub String);
#[derive(Deserialize, Serialize, Debug, Clone)]
#[non_exhaustive]
pub struct UserProfile {
id: String,
display_name: Option<String>,
images: Option<Vec<ImageObject>>,
pub id: String,
pub display_name: Option<String>,
pub images: Option<Vec<ImageObject>>,
}
#[derive(Deserialize, Serialize, Debug, Clone)]