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 = [
"getrandom 0.1.16",
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc 0.2.0",
"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]]
name = "rand_chacha"
version = "0.2.2"
@ -405,7 +417,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"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]]
@ -417,13 +439,31 @@ dependencies = [
"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]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
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]]
@ -432,7 +472,7 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core",
"rand_core 0.5.1",
]
[[package]]
@ -483,7 +523,7 @@ dependencies = [
"indexmap",
"js-sys",
"pulldown-cmark",
"rand",
"rand 0.7.3",
"serde",
"serde_json",
"uuid",
@ -551,6 +591,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"css_typegen",
"rand 0.8.4",
"seed",
"serde",
"serde_json",

View File

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

View File

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

View File

@ -1,13 +1,20 @@
use crate::css::C;
use crate::song::Song;
use anyhow::anyhow;
use rand::seq::SliceRandom;
use rand::thread_rng;
use seed::app::cmds::timeout;
use seed::browser::util::document;
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 {
songs: Vec<Song>,
filtered_songs: Vec<usize>,
show_elements: usize,
filter_video: bool,
filter_duets: bool,
}
const SCROLL_THRESHOLD: usize = 50;
@ -16,22 +23,41 @@ const INITIAL_ELEM_COUNT: usize = 100;
//#[derive(Clone, Debug)]
pub enum Msg {
Search(String),
ToggleVideo,
ToggleDuets,
Shuffle,
Scroll,
}
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 {
songs: serde_json::from_str(include_str!("../static/songs.json"))
.expect("failed to parsed songs"),
songs,
filtered_songs,
show_elements: INITIAL_ELEM_COUNT,
filter_video: false,
filter_duets: false,
}
}
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
match msg {
Msg::Search(query) if query.is_empty() => update(Msg::Shuffle, model, orders),
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 => {
let (scroll, max_scroll) = match get_scroll() {
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
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;
orders.perform_cmd(timeout(32, || Msg::Scroll));
}
}
}
@ -87,6 +112,13 @@ pub fn view(model: &Model) -> Vec<Node<Msg>> {
],
_ => 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![
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"],
],
input![
input_ev(Ev::Input, Msg::Search),
attrs! {
At::Placeholder => "Search",
},
C![C.song_search_field],
],
button![
C![C.song_sort_button, C.tooltip],
IF![model.filter_duets => C![C.song_sort_button_selected]],
ev(Ev::Click, |_| Msg::ToggleDuets),
span![C![C.tooltiptext], "Endast Duetter"],
"D",
],
button![
C![C.song_sort_button, C.tooltip],
IF![model.filter_video => C![C.song_sort_button_selected]],
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![
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]]],
model
.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 css;
mod fuzzy;
mod song;
use seed::prelude::wasm_bindgen;

View File

@ -1,4 +1,6 @@
use crate::fuzzy::{self, FuzzyScore};
use serde::Deserialize;
use std::cmp::max;
#[derive(Deserialize, Debug, Clone, Default)]
pub struct Song {
@ -16,3 +18,18 @@ pub struct Song {
#[serde(rename = "duetsingerp2")]
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;
color: #ffffff;
height: 100%;
font-family: Open Sans,serif;
font-family: Ubuntu,serif;
}
.nobr {
@ -39,6 +39,9 @@ body {
.song_search_bar {
position: relative;
padding: 1em 1em .5em;
max-width: 35em;
width: 80%;
margin: auto;
}
.song_search_field {
@ -63,6 +66,7 @@ body {
background-color: #427493;
transition: 0.4s;
color: #ffffff;
font-weight: 900;
}
.song_sort_button:hover {
@ -76,7 +80,7 @@ body {
}
.song_sort_button_selected {
color: #ffff00;
color: #00ff00;
transition: 0.1s;
}
@ -99,18 +103,23 @@ body {
flex-direction: row;
border-radius: 1em;
background: black;
max-width: 40em;
margin: auto;
margin-bottom: 1em;
animation: song_item_enter 1s 1;
box-shadow: #09babe 1px 1px;
}
@keyframes song_item_enter {
from {
margin-left: -16em;
margin-right: 16em;
opacity: 0;
/*margin-left: -16em;
margin-right: 16em;*/
}
to {
margin-left: 0;
margin-right: 0;
opacity: 1;
/*margin-left: 0;
margin-right: 0;*/
}
}
@ -129,8 +138,9 @@ body {
}
.song_item_cover {
height: 5em;
width: 5em;
height: auto;
object-fit: cover;
border-top-left-radius: 1em;
border-bottom-left-radius: 1em;
}
@ -153,7 +163,10 @@ body {
flex-grow: 0;
flex-shrink: 1;
padding-top: 1.5em;
padding-right: 1.5em;
}
.song_gizmos div {
margin-right: 1em;
}
.duet_icon {
@ -164,6 +177,14 @@ body {
height: 2em;
}
.video_icon {
background-image: url("/images/video.svg");
background-size: contain;
background-repeat: no-repeat;
width: 2em;
height: 2em;
}
.hidden {
visibility: hidden;
}