Initial Commit
This commit is contained in:
138
src/app.rs
Normal file
138
src/app.rs
Normal file
@ -0,0 +1,138 @@
|
||||
use crate::css::C;
|
||||
use crate::song::Song;
|
||||
use anyhow::anyhow;
|
||||
use seed::browser::util::document;
|
||||
use seed::prelude::*;
|
||||
use seed::{attrs, br, button, div, empty, error, img, input, log, p, span, C, IF};
|
||||
|
||||
pub struct Model {
|
||||
songs: Vec<Song>,
|
||||
show_elements: usize,
|
||||
}
|
||||
|
||||
const SCROLL_THRESHOLD: usize = 50;
|
||||
const INITIAL_ELEM_COUNT: usize = 100;
|
||||
|
||||
//#[derive(Clone, Debug)]
|
||||
pub enum Msg {
|
||||
Search(String),
|
||||
Scroll,
|
||||
}
|
||||
|
||||
pub fn init(_url: Url, _orders: &mut impl Orders<Msg>) -> Model {
|
||||
Model {
|
||||
songs: serde_json::from_str(include_str!("../static/songs.json"))
|
||||
.expect("failed to parsed songs"),
|
||||
show_elements: INITIAL_ELEM_COUNT,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
match msg {
|
||||
Msg::Search(query) => {
|
||||
log!("search query");
|
||||
}
|
||||
Msg::Scroll => {
|
||||
let (scroll, max_scroll) = match get_scroll() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!(e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let scroll_left: i32 = max_scroll - scroll;
|
||||
|
||||
// when there are fewer elements than this below the scroll viewport, add more
|
||||
const ELEMENT_HEIGHT: i32 = 48;
|
||||
|
||||
log!("scroll={}, height={}", scroll, max_scroll);
|
||||
if scroll_left < ELEMENT_HEIGHT * SCROLL_THRESHOLD as i32 {
|
||||
log!("showing more items");
|
||||
model.show_elements += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(model: &Model) -> Vec<Node<Msg>> {
|
||||
let song_card = |song: &Song| -> Node<Msg> {
|
||||
div![
|
||||
C![C.song_item],
|
||||
img![
|
||||
C![C.song_item_cover],
|
||||
match song.cover {
|
||||
Some(_) => attrs! {At::Src => format!("/images/songs/{}.png", song.song_hash)},
|
||||
None => attrs! {At::Src => "/images/default_cover.png"},
|
||||
},
|
||||
],
|
||||
div![
|
||||
C![C.song_item_info],
|
||||
div![C![C.song_item_title], song.title.to_string()],
|
||||
div![C![C.song_item_artist], song.artist.to_string()],
|
||||
],
|
||||
div![
|
||||
C![C.song_gizmos],
|
||||
match (&song.duet_singer_1, &song.duet_singer_2) {
|
||||
(Some(p1), Some(p2)) => div![
|
||||
C![C.duet_icon, C.tooltip],
|
||||
span![
|
||||
C![C.tooltiptext],
|
||||
"Duet",
|
||||
div![
|
||||
C![C.marquee],
|
||||
p![" 🗲 ", p1, " 🗲 ", p2, " 🗲 ", p1, " 🗲 ", p2]
|
||||
],
|
||||
],
|
||||
],
|
||||
_ => empty![],
|
||||
},
|
||||
],
|
||||
]
|
||||
};
|
||||
|
||||
vec![
|
||||
div![
|
||||
C![C.song_search_bar],
|
||||
div![
|
||||
input![
|
||||
attrs! {
|
||||
At::Placeholder => "Search",
|
||||
},
|
||||
C![C.song_search_field, C.tooltip],
|
||||
],
|
||||
button![
|
||||
C![C.song_sort_button, C.tooltip],
|
||||
span![C![C.tooltiptext], "awawawaw", br![], "aawawaw?"],
|
||||
],
|
||||
button![
|
||||
C![C.song_sort_button, C.tooltip],
|
||||
span![C![C.tooltiptext], "awawawaw"],
|
||||
],
|
||||
button![
|
||||
C![C.song_sort_button, C.song_sort_button_right, C.tooltip],
|
||||
span![C![C.tooltiptext], "awawawaw"],
|
||||
],
|
||||
],
|
||||
],
|
||||
div![
|
||||
C![C.song_list],
|
||||
attrs! {At::Id => SONG_LIST_ID},
|
||||
ev(Ev::Scroll, |_| Msg::Scroll),
|
||||
model.songs.iter().take(model.show_elements).map(song_card),
|
||||
IF![model.show_elements < model.songs.len() => div![C![C.center, C.penguin]]],
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
const SONG_LIST_ID: &str = "song_list";
|
||||
|
||||
fn get_scroll() -> anyhow::Result<(i32, i32)> {
|
||||
let list = document()
|
||||
.get_element_by_id(SONG_LIST_ID)
|
||||
.ok_or(anyhow!("Failed to access song list element"))?;
|
||||
let scroll = list.scroll_top();
|
||||
let height = list.client_height();
|
||||
let max = (list.scroll_height() - height).max(0);
|
||||
Ok((scroll, max))
|
||||
}
|
||||
7
src/css.rs
Normal file
7
src/css.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use css_typegen::css_typegen;
|
||||
|
||||
// NOTE: Remember to edit index.html when adding new css-files!
|
||||
|
||||
// Generate rust types for css-classes.
|
||||
// Used for autocompletion and extra compile-time checks.
|
||||
css_typegen!("static/styles");
|
||||
11
src/lib.rs
Normal file
11
src/lib.rs
Normal file
@ -0,0 +1,11 @@
|
||||
mod app;
|
||||
mod css;
|
||||
mod song;
|
||||
|
||||
use seed::prelude::wasm_bindgen;
|
||||
use seed::App;
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn start() {
|
||||
App::start("app", app::init, app::update, app::view);
|
||||
}
|
||||
18
src/song.rs
Normal file
18
src/song.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Default)]
|
||||
pub struct Song {
|
||||
pub title: String,
|
||||
pub artist: String,
|
||||
pub cover: Option<String>,
|
||||
pub song_hash: String,
|
||||
pub language: Option<String>,
|
||||
pub video: Option<String>,
|
||||
pub year: Option<String>,
|
||||
pub genre: Option<String>,
|
||||
pub bpm: String,
|
||||
#[serde(rename = "duetsingerp1")]
|
||||
pub duet_singer_1: Option<String>,
|
||||
#[serde(rename = "duetsingerp2")]
|
||||
pub duet_singer_2: Option<String>,
|
||||
}
|
||||
Reference in New Issue
Block a user