Initial Commit 2

This commit is contained in:
2021-12-30 23:29:56 +01:00
parent 7298e8de73
commit 1ad30399cf
9 changed files with 259 additions and 45 deletions

55
Cargo.lock generated
View File

@ -392,12 +392,24 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [ dependencies = [
"getrandom 0.1.16", "getrandom 0.1.16",
"libc", "libc",
"rand_chacha", "rand_chacha 0.2.2",
"rand_core", "rand_core 0.5.1",
"rand_hc", "rand_hc 0.2.0",
"rand_pcg", "rand_pcg",
] ]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.3",
"rand_hc 0.3.1",
]
[[package]] [[package]]
name = "rand_chacha" name = "rand_chacha"
version = "0.2.2" version = "0.2.2"
@ -405,7 +417,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core 0.5.1",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.3",
] ]
[[package]] [[package]]
@ -417,13 +439,31 @@ dependencies = [
"getrandom 0.1.16", "getrandom 0.1.16",
] ]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom 0.2.3",
]
[[package]] [[package]]
name = "rand_hc" name = "rand_hc"
version = "0.2.0" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [ dependencies = [
"rand_core", "rand_core 0.5.1",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core 0.6.3",
] ]
[[package]] [[package]]
@ -432,7 +472,7 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [ dependencies = [
"rand_core", "rand_core 0.5.1",
] ]
[[package]] [[package]]
@ -483,7 +523,7 @@ dependencies = [
"indexmap", "indexmap",
"js-sys", "js-sys",
"pulldown-cmark", "pulldown-cmark",
"rand", "rand 0.7.3",
"serde", "serde",
"serde_json", "serde_json",
"uuid", "uuid",
@ -551,6 +591,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"css_typegen", "css_typegen",
"rand 0.8.4",
"seed", "seed",
"serde", "serde",
"serde_json", "serde_json",

View File

@ -13,6 +13,7 @@ seed = "0.8.0"
serde = { version = "1", features = ['derive'] } serde = { version = "1", features = ['derive'] }
serde_json = "1" serde_json = "1"
anyhow = "*" anyhow = "*"
rand = "0.8.4"
[dependencies.css_typegen] [dependencies.css_typegen]
git = "https://github.com/hulthe/css_typegen.git" git = "https://github.com/hulthe/css_typegen.git"

View File

@ -11,9 +11,7 @@
<link data-trunk rel="scss" href="/static/styles/marquee.scss"> <link data-trunk rel="scss" href="/static/styles/marquee.scss">
<!-- fonts --> <!-- fonts -->
<!--
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu|Ubuntu+Mono&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu|Ubuntu+Mono&display=swap">
-->
<!-- pwa manifest --> <!-- pwa manifest -->
<link data-trunk rel="copy-file" href="/static/manifest.json"> <link data-trunk rel="copy-file" href="/static/manifest.json">
@ -34,6 +32,6 @@
</head> </head>
<body> <body>
<div id="app"></div> <div id="app", style="width: 100%;"></div>
</body> </body>
</html> </html>

View File

@ -1,13 +1,20 @@
use crate::css::C; use crate::css::C;
use crate::song::Song; use crate::song::Song;
use anyhow::anyhow; use anyhow::anyhow;
use rand::seq::SliceRandom;
use rand::thread_rng;
use seed::app::cmds::timeout;
use seed::browser::util::document; use seed::browser::util::document;
use seed::prelude::*; use seed::prelude::*;
use seed::{attrs, br, button, div, empty, error, img, input, log, p, span, C, IF}; use seed::{attrs, button, div, empty, error, img, input, p, span, C, IF};
use std::cmp::Reverse;
pub struct Model { pub struct Model {
songs: Vec<Song>, songs: Vec<Song>,
filtered_songs: Vec<usize>,
show_elements: usize, show_elements: usize,
filter_video: bool,
filter_duets: bool,
} }
const SCROLL_THRESHOLD: usize = 50; const SCROLL_THRESHOLD: usize = 50;
@ -16,22 +23,41 @@ const INITIAL_ELEM_COUNT: usize = 100;
//#[derive(Clone, Debug)] //#[derive(Clone, Debug)]
pub enum Msg { pub enum Msg {
Search(String), Search(String),
ToggleVideo,
ToggleDuets,
Shuffle,
Scroll, Scroll,
} }
pub fn init(_url: Url, _orders: &mut impl Orders<Msg>) -> Model { pub fn init(_url: Url, _orders: &mut impl Orders<Msg>) -> Model {
let songs: Vec<Song> =
serde_json::from_str(include_str!("../static/songs.json")).expect("parse songs");
let mut filtered_songs: Vec<usize> = (0..songs.len()).collect();
filtered_songs.shuffle(&mut thread_rng());
Model { Model {
songs: serde_json::from_str(include_str!("../static/songs.json")) songs,
.expect("failed to parsed songs"), filtered_songs,
show_elements: INITIAL_ELEM_COUNT, show_elements: INITIAL_ELEM_COUNT,
filter_video: false,
filter_duets: false,
} }
} }
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) { pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
match msg { match msg {
Msg::Search(query) if query.is_empty() => update(Msg::Shuffle, model, orders),
Msg::Search(query) => { Msg::Search(query) => {
log!("search query"); model.filtered_songs.sort_by_cached_key(|&i| {
let song = &model.songs[i];
let top_score = Reverse(song.fuzzy_compare(&query));
(top_score, &song.title, &song.artist, &song.song_hash)
});
} }
Msg::ToggleVideo => model.filter_video = !model.filter_video,
Msg::ToggleDuets => model.filter_duets = !model.filter_duets,
Msg::Shuffle => model.filtered_songs.shuffle(&mut thread_rng()),
Msg::Scroll => { Msg::Scroll => {
let (scroll, max_scroll) = match get_scroll() { let (scroll, max_scroll) = match get_scroll() {
Ok(v) => v, Ok(v) => v,
@ -46,10 +72,9 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
// when there are fewer elements than this below the scroll viewport, add more // when there are fewer elements than this below the scroll viewport, add more
const ELEMENT_HEIGHT: i32 = 48; const ELEMENT_HEIGHT: i32 = 48;
log!("scroll={}, height={}", scroll, max_scroll);
if scroll_left < ELEMENT_HEIGHT * SCROLL_THRESHOLD as i32 { if scroll_left < ELEMENT_HEIGHT * SCROLL_THRESHOLD as i32 {
log!("showing more items");
model.show_elements += 1; model.show_elements += 1;
orders.perform_cmd(timeout(32, || Msg::Scroll));
} }
} }
} }
@ -87,6 +112,13 @@ pub fn view(model: &Model) -> Vec<Node<Msg>> {
], ],
_ => empty![], _ => empty![],
}, },
IF![song.video.is_some() => div![
C![C.video_icon, C.tooltip],
span![
C![C.tooltiptext],
"Musikvideo",
],
]],
], ],
] ]
}; };
@ -94,33 +126,48 @@ pub fn view(model: &Model) -> Vec<Node<Msg>> {
vec![ vec![
div![ div![
C![C.song_search_bar], C![C.song_search_bar],
div![ input![
input![ input_ev(Ev::Input, Msg::Search),
attrs! { attrs! {
At::Placeholder => "Search", At::Placeholder => "Search",
}, },
C![C.song_search_field, C.tooltip], C![C.song_search_field],
], ],
button![ button![
C![C.song_sort_button, C.tooltip], C![C.song_sort_button, C.tooltip],
span![C![C.tooltiptext], "awawawaw", br![], "aawawaw?"], IF![model.filter_duets => C![C.song_sort_button_selected]],
], ev(Ev::Click, |_| Msg::ToggleDuets),
button![ span![C![C.tooltiptext], "Endast Duetter"],
C![C.song_sort_button, C.tooltip], "D",
span![C![C.tooltiptext], "awawawaw"], ],
], button![
button![ C![C.song_sort_button, C.tooltip],
C![C.song_sort_button, C.song_sort_button_right, C.tooltip], IF![model.filter_video => C![C.song_sort_button_selected]],
span![C![C.tooltiptext], "awawawaw"], ev(Ev::Click, |_| Msg::ToggleVideo),
], span![C![C.tooltiptext], "Endast med Video"],
"V",
],
button![
C![C.song_sort_button, C.song_sort_button_right, C.tooltip],
IF![model.filter_video => C![C.song_sort_button_selected]],
ev(Ev::Click, |_| Msg::Shuffle),
span![C![C.tooltiptext], "Blanda låtar"],
"🔀",
], ],
], ],
div![ div![
C![C.song_list], C![C.song_list],
attrs! {At::Id => SONG_LIST_ID}, attrs! {At::Id => SONG_LIST_ID},
ev(Ev::Scroll, |_| Msg::Scroll), ev(Ev::Scroll, |_| Msg::Scroll),
model.songs.iter().take(model.show_elements).map(song_card), model
IF![model.show_elements < model.songs.len() => div![C![C.center, C.penguin]]], .filtered_songs
.iter()
.map(|&i| &model.songs[i])
.filter(|song| !model.filter_video || song.video.is_some())
.filter(|song| !model.filter_duets || song.duet().is_some())
.map(song_card)
.take(model.show_elements),
//IF![model.show_elements < model.songs.len() => div![C![C.center, C.penguin]]],
], ],
] ]
} }

67
src/fuzzy.rs Normal file
View File

@ -0,0 +1,67 @@
use std::cmp::{Ord, Ordering, PartialOrd};
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct FuzzyScore {
pub score: i32,
pub matches: Vec<FuzzyCharMatch>,
}
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
pub struct FuzzyCharMatch {
pub base_str_index: usize,
pub search_str_index: usize,
}
impl PartialOrd for FuzzyScore {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FuzzyScore {
fn cmp(&self, other: &Self) -> Ordering {
self.score.cmp(&other.score)
}
}
/// Compare a base string to a user-input search
///
/// Returns a tuple of the match score, as well as the indices of every char in `search` which maps
/// to an index in `base`
pub fn compare<B, S>(base: B, search: S) -> FuzzyScore
where
B: Iterator<Item = char> + Clone,
S: IntoIterator<Item = char>,
{
let mut base = base.into_iter().enumerate();
// How alike the search string is to self.name
//let mut score = -(search.len() as i32);
let mut score = 0;
// Vector of which char index in s maps to which char index in self.name
let mut matches = vec![];
for (i, sc) in search.into_iter().enumerate() {
let sc = sc.to_ascii_lowercase();
let mut add = 3;
let mut base_tmp = base.clone();
while let Some((j, bc)) = base_tmp.next() {
let bc = bc.to_ascii_lowercase();
if bc == sc {
matches.push(FuzzyCharMatch {
search_str_index: i,
base_str_index: j,
});
score += add;
base = base_tmp;
break;
} else {
add = 2;
}
}
}
FuzzyScore { score, matches }
}

View File

@ -1,5 +1,6 @@
mod app; mod app;
mod css; mod css;
mod fuzzy;
mod song; mod song;
use seed::prelude::wasm_bindgen; use seed::prelude::wasm_bindgen;

View File

@ -1,4 +1,6 @@
use crate::fuzzy::{self, FuzzyScore};
use serde::Deserialize; use serde::Deserialize;
use std::cmp::max;
#[derive(Deserialize, Debug, Clone, Default)] #[derive(Deserialize, Debug, Clone, Default)]
pub struct Song { pub struct Song {
@ -16,3 +18,18 @@ pub struct Song {
#[serde(rename = "duetsingerp2")] #[serde(rename = "duetsingerp2")]
pub duet_singer_2: Option<String>, pub duet_singer_2: Option<String>,
} }
impl Song {
pub fn duet(&self) -> Option<(&str, &str)> {
self.duet_singer_1
.as_deref()
.zip(self.duet_singer_2.as_deref())
}
pub fn fuzzy_compare(&self, query: &str) -> FuzzyScore {
let title_score = fuzzy::compare(self.title.chars(), query.chars());
let artist_score = fuzzy::compare(self.artist.chars(), query.chars());
max(title_score, artist_score)
}
}

21
static/images/video.svg Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="64"
height="64"
viewBox="0 0 16.933333 16.933334"
version="1.1"
id="svg5"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2" />
<g
id="layer1">
<path
style="fill:#ffffff;stroke-width:0.0400104"
d="M 3.8618918,16.847379 C 3.5531533,16.691409 3.4069553,16.365377 3.501334,16.04331 3.542104,15.904183 3.7461635,15.680279 4.754658,14.668113 l 1.2046249,-1.209011 -1.6453677,-0.01131 c -1.5902084,-0.01093 -1.6490699,-0.01408 -1.7558086,-0.0938 -0.060743,-0.04537 -0.1475633,-0.132205 -0.1929343,-0.192962 -0.081388,-0.10899 -0.08264,-0.151159 -0.093468,-3.148353 -0.00768,-2.127506 0.00178,-3.0378868 0.031602,-3.0378868 0.023417,0 0.1093766,0.033727 0.1910216,0.074948 0.7345785,0.3708761 1.850302,0.3432692 2.6691067,-0.066045 C 5.7138612,6.7085388 6.2607299,6.1897789 6.519511,5.6973179 6.6012857,5.5417011 6.6740408,5.4143778 6.6811899,5.4143778 c 0.00715,0 0.1397658,0.158753 0.2947045,0.3527844 C 7.2814145,6.149769 7.7470505,6.5441028 8.1767625,6.7841447 9.2563186,7.387198 10.599477,7.45749 11.765755,6.9719669 12.07361,6.8438058 12.717706,6.4465246 12.820341,6.3214922 c 0.0302,-0.036792 0.07071,-0.066894 0.09002,-0.066894 0.01931,0 0.03511,0.4321134 0.03511,0.960252 0,0.5281385 0.01007,0.9602519 0.02238,0.9602519 0.01231,0 0.520942,-0.4463311 1.130297,-0.991848 0.609353,-0.5455169 1.13121,-1.0061868 1.15968,-1.0237117 0.02895,-0.01782 0.07745,-0.010555 0.11003,0.016489 0.05008,0.041566 0.05826,0.4790598 0.05826,3.116261 0,3.0603016 -0.0042,3.1639316 -0.127269,3.1639316 -0.01718,0 -0.544543,-0.459119 -1.171905,-1.020267 -0.627363,-0.561147 -1.149843,-1.020268 -1.161068,-1.020268 -0.01122,0 -0.02041,0.570467 -0.02041,1.267705 0,1.207082 -0.004,1.276024 -0.08424,1.441715 -0.05771,0.119213 -0.132808,0.198792 -0.238469,0.252696 -0.145606,0.07428 -0.242097,0.07868 -1.725333,0.07868 H 9.3263383 l 1.1670447,1.170307 c 0.641873,0.643668 1.186109,1.220992 1.209411,1.282941 0.187564,0.498638 -0.240394,1.01204 -0.772351,0.926552 -0.153075,-0.0246 -0.28226,-0.142979 -1.7157132,-1.572241 L 7.664555,13.718401 6.093911,15.284123 C 4.7678227,16.606056 4.4983476,16.856627 4.363225,16.8934 4.1350567,16.955495 4.0627726,16.94886 3.8618918,16.847377 Z M 3.1656223,6.3133233 C 2.3679602,6.0944358 1.7351637,5.415766 1.562666,4.5941638 1.4887316,4.242016 1.4887316,4.0660812 1.562666,3.7139334 1.7851483,2.6542553 2.689375,1.9162056 3.7630675,1.9179159 4.3889609,1.9189745 4.9013558,2.1315077 5.3434821,2.5736334 5.7815991,3.0117503 5.9823409,3.5117648 5.9831752,4.1669999 5.9844152,5.138787 5.370442,5.9838299 4.4511832,6.275568 4.0910672,6.3898546 3.5070228,6.4070076 3.1656223,6.3133233 Z M 9.3845417,6.2918711 C 8.4419841,6.0457608 7.7286787,5.4656719 7.2873968,4.586388 6.9681783,3.9503232 6.878522,3.0823809 7.0608854,2.3935869 7.3531567,1.2896647 8.178748,0.44194365 9.2845167,0.11035277 9.5893612,0.01893829 9.7213451,0.0020182 10.144742,8.106388e-5 10.745297,-0.00267115 11.046898,0.06391438 11.565113,0.31364492 c 0.839924,0.40476359 1.465284,1.16723538 1.705916,2.07994198 0.117201,0.4445347 0.116438,1.1533912 -0.0017,1.5980982 -0.166979,0.628469 -0.559697,1.2513414 -1.040633,1.6505053 -0.320956,0.2663784 -0.961885,0.5837273 -1.351649,0.6692517 -0.40784,0.089491 -1.110077,0.080283 -1.4924962,-0.019571 z"
id="path836" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -15,7 +15,7 @@ body {
background-color: #0c2738; background-color: #0c2738;
color: #ffffff; color: #ffffff;
height: 100%; height: 100%;
font-family: Open Sans,serif; font-family: Ubuntu,serif;
} }
.nobr { .nobr {
@ -39,6 +39,9 @@ body {
.song_search_bar { .song_search_bar {
position: relative; position: relative;
padding: 1em 1em .5em; padding: 1em 1em .5em;
max-width: 35em;
width: 80%;
margin: auto;
} }
.song_search_field { .song_search_field {
@ -63,6 +66,7 @@ body {
background-color: #427493; background-color: #427493;
transition: 0.4s; transition: 0.4s;
color: #ffffff; color: #ffffff;
font-weight: 900;
} }
.song_sort_button:hover { .song_sort_button:hover {
@ -76,7 +80,7 @@ body {
} }
.song_sort_button_selected { .song_sort_button_selected {
color: #ffff00; color: #00ff00;
transition: 0.1s; transition: 0.1s;
} }
@ -99,18 +103,23 @@ body {
flex-direction: row; flex-direction: row;
border-radius: 1em; border-radius: 1em;
background: black; background: black;
max-width: 40em;
margin: auto;
margin-bottom: 1em; margin-bottom: 1em;
animation: song_item_enter 1s 1; animation: song_item_enter 1s 1;
box-shadow: #09babe 1px 1px;
} }
@keyframes song_item_enter { @keyframes song_item_enter {
from { from {
margin-left: -16em; opacity: 0;
margin-right: 16em; /*margin-left: -16em;
margin-right: 16em;*/
} }
to { to {
margin-left: 0; opacity: 1;
margin-right: 0; /*margin-left: 0;
margin-right: 0;*/
} }
} }
@ -129,8 +138,9 @@ body {
} }
.song_item_cover { .song_item_cover {
height: 5em;
width: 5em; width: 5em;
height: auto;
object-fit: cover;
border-top-left-radius: 1em; border-top-left-radius: 1em;
border-bottom-left-radius: 1em; border-bottom-left-radius: 1em;
} }
@ -153,7 +163,10 @@ body {
flex-grow: 0; flex-grow: 0;
flex-shrink: 1; flex-shrink: 1;
padding-top: 1.5em; padding-top: 1.5em;
padding-right: 1.5em; }
.song_gizmos div {
margin-right: 1em;
} }
.duet_icon { .duet_icon {
@ -164,6 +177,14 @@ body {
height: 2em; height: 2em;
} }
.video_icon {
background-image: url("/images/video.svg");
background-size: contain;
background-repeat: no-repeat;
width: 2em;
height: 2em;
}
.hidden { .hidden {
visibility: hidden; visibility: hidden;
} }