Improve search performance
This commit is contained in:
67
src/app.rs
67
src/app.rs
@ -1,4 +1,5 @@
|
|||||||
use crate::css::C;
|
use crate::css::C;
|
||||||
|
use crate::fuzzy::FuzzyScore;
|
||||||
use crate::query::ParsedQuery;
|
use crate::query::ParsedQuery;
|
||||||
use crate::song::Song;
|
use crate::song::Song;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
@ -12,12 +13,21 @@ use std::cmp::Reverse;
|
|||||||
use web_sys::Element;
|
use web_sys::Element;
|
||||||
|
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
songs: Vec<Song>,
|
songs: Vec<(Reverse<FuzzyScore>, Song)>,
|
||||||
|
|
||||||
|
/// The search string
|
||||||
query: String,
|
query: String,
|
||||||
filtered_songs: Vec<usize>,
|
|
||||||
hidden_songs: usize,
|
/// The number of songs currently in view. Goes up when the user scrolls down.
|
||||||
shown_songs: usize,
|
shown_songs: usize,
|
||||||
|
|
||||||
|
/// The number of songs that didn't match the search critera
|
||||||
|
hidden_songs: usize,
|
||||||
|
|
||||||
|
/// Whether we're filtering by video
|
||||||
filter_video: bool,
|
filter_video: bool,
|
||||||
|
|
||||||
|
/// Whether we're filtering by duets
|
||||||
filter_duets: bool,
|
filter_duets: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,23 +35,33 @@ const SCROLL_THRESHOLD: usize = 50;
|
|||||||
const INITIAL_ELEM_COUNT: usize = 100;
|
const INITIAL_ELEM_COUNT: usize = 100;
|
||||||
|
|
||||||
pub enum Msg {
|
pub enum Msg {
|
||||||
|
/// The user entered something into the search field
|
||||||
Search(String),
|
Search(String),
|
||||||
|
|
||||||
|
/// The user pressed the Toggle Video button
|
||||||
ToggleVideo,
|
ToggleVideo,
|
||||||
|
|
||||||
|
/// The user pressed the Toggle Duets button
|
||||||
ToggleDuets,
|
ToggleDuets,
|
||||||
|
|
||||||
|
/// The user pressed the Shuffle button
|
||||||
Shuffle,
|
Shuffle,
|
||||||
|
|
||||||
|
/// The user scrolled the song list
|
||||||
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> =
|
let mut songs: Vec<Song> =
|
||||||
serde_json::from_str(include_str!("../static/songs.json")).expect("parse songs");
|
serde_json::from_str(include_str!("../static/songs.json")).expect("parse songs");
|
||||||
let mut filtered_songs: Vec<usize> = (0..songs.len()).collect();
|
songs.shuffle(&mut thread_rng());
|
||||||
filtered_songs.shuffle(&mut thread_rng());
|
|
||||||
|
|
||||||
Model {
|
Model {
|
||||||
songs,
|
songs: songs
|
||||||
|
.into_iter()
|
||||||
|
.map(|song| (Default::default(), song))
|
||||||
|
.collect(),
|
||||||
query: String::new(),
|
query: String::new(),
|
||||||
filtered_songs,
|
|
||||||
hidden_songs: 0,
|
hidden_songs: 0,
|
||||||
shown_songs: INITIAL_ELEM_COUNT,
|
shown_songs: INITIAL_ELEM_COUNT,
|
||||||
filter_video: false,
|
filter_video: false,
|
||||||
@ -64,19 +84,19 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
update(Msg::Shuffle, model, orders)
|
update(Msg::Shuffle, model, orders)
|
||||||
} else {
|
} else {
|
||||||
let query = ParsedQuery::parse(&model.query);
|
let query = ParsedQuery::parse(&model.query);
|
||||||
model.filtered_songs.sort_by_cached_key(|&i| {
|
model.filter_duets = query.duet == Some(true);
|
||||||
let song = &model.songs[i];
|
model.filter_video = query.video == Some(true);
|
||||||
let score = song.fuzzy_compare(&query);
|
|
||||||
if score < Default::default() {
|
// calculate search scores & sort list
|
||||||
|
for (score, song) in model.songs.iter_mut() {
|
||||||
|
let new_score = song.fuzzy_compare(&query);
|
||||||
|
if new_score < Default::default() {
|
||||||
model.hidden_songs += 1;
|
model.hidden_songs += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let top_score = Reverse(score);
|
*score = Reverse(new_score);
|
||||||
|
}
|
||||||
(top_score, &song.title, &song.artist, &song.song_hash)
|
model.songs.sort_unstable();
|
||||||
});
|
|
||||||
model.filter_duets = query.duet == Some(true);
|
|
||||||
model.filter_video = query.video == Some(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Msg::ToggleVideo => {
|
Msg::ToggleVideo => {
|
||||||
@ -100,7 +120,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
model.shown_songs = INITIAL_ELEM_COUNT;
|
model.shown_songs = INITIAL_ELEM_COUNT;
|
||||||
scroll_to_top();
|
scroll_to_top();
|
||||||
model.query.clear();
|
model.query.clear();
|
||||||
model.filtered_songs.shuffle(&mut thread_rng());
|
model.songs.shuffle(&mut thread_rng());
|
||||||
}
|
}
|
||||||
Msg::Scroll => {
|
Msg::Scroll => {
|
||||||
let (scroll, max_scroll) = match get_scroll() {
|
let (scroll, max_scroll) = match get_scroll() {
|
||||||
@ -171,6 +191,7 @@ pub fn view(model: &Model) -> Vec<Node<Msg>> {
|
|||||||
"Duet",
|
"Duet",
|
||||||
div![
|
div![
|
||||||
C![C.marquee],
|
C![C.marquee],
|
||||||
|
// add duplicates to get the repeating marquee effect
|
||||||
p![" 🗲 ", p1, " 🗲 ", p2, " 🗲 ", p1, " 🗲 ", p2]
|
p![" 🗲 ", p1, " 🗲 ", p2, " 🗲 ", p1, " 🗲 ", p2]
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -219,11 +240,11 @@ pub fn view(model: &Model) -> Vec<Node<Msg>> {
|
|||||||
attrs! {At::Id => SONG_LIST_ID},
|
attrs! {At::Id => SONG_LIST_ID},
|
||||||
ev(Ev::Scroll, |_| Msg::Scroll),
|
ev(Ev::Scroll, |_| Msg::Scroll),
|
||||||
model
|
model
|
||||||
.filtered_songs
|
.songs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&i| &model.songs[i])
|
.map(|(_, song)| song)
|
||||||
.map(song_card)
|
.map(song_card)
|
||||||
.take(model.filtered_songs.len() - model.hidden_songs)
|
.take(model.songs.len() - model.hidden_songs)
|
||||||
.take(model.shown_songs),
|
.take(model.shown_songs),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
@ -234,7 +255,7 @@ const SONG_LIST_ID: &str = "song_list";
|
|||||||
fn get_song_list_element() -> anyhow::Result<Element> {
|
fn get_song_list_element() -> anyhow::Result<Element> {
|
||||||
document()
|
document()
|
||||||
.get_element_by_id(SONG_LIST_ID)
|
.get_element_by_id(SONG_LIST_ID)
|
||||||
.ok_or(anyhow!("Failed to access song list element"))
|
.ok_or_else(|| anyhow!("Failed to access song list element"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_to_top() {
|
fn scroll_to_top() {
|
||||||
|
|||||||
@ -3,12 +3,12 @@ use crate::query::ParsedQuery;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone, Default)]
|
#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Song {
|
pub struct Song {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub artist: String,
|
pub artist: String,
|
||||||
pub cover: Option<String>,
|
|
||||||
pub song_hash: String,
|
pub song_hash: String,
|
||||||
|
pub cover: Option<String>,
|
||||||
pub language: Option<String>,
|
pub language: Option<String>,
|
||||||
pub video: Option<String>,
|
pub video: Option<String>,
|
||||||
pub year: Option<String>,
|
pub year: Option<String>,
|
||||||
|
|||||||
Reference in New Issue
Block a user