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

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