Initial Commit 2
This commit is contained in:
101
src/app.rs
101
src/app.rs
@ -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
67
src/fuzzy.rs
Normal 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 }
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
mod app;
|
||||
mod css;
|
||||
mod fuzzy;
|
||||
mod song;
|
||||
|
||||
use seed::prelude::wasm_bindgen;
|
||||
|
||||
17
src/song.rs
17
src/song.rs
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user