use config::AppConfig;
use dioxus::prelude::*;
use reader::Track;
use std::cmp::Ordering;
use std::collections::HashSet;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SortField {
Title,
Artist,
Album,
Duration,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SortDirection {
Asc,
Desc,
}
pub type SortState = Option<(SortField, SortDirection)>;
pub fn next_sort_state(current: SortState, field: SortField) -> SortState {
match current {
Some((current_field, SortDirection::Asc)) if current_field == field => {
Some((field, SortDirection::Desc))
}
Some((current_field, SortDirection::Desc)) if current_field == field => None,
_ => Some((field, SortDirection::Asc)),
}
}
pub fn toggle_sort_state(mut sort_state: Signal<SortState>, field: SortField) {
let next = next_sort_state(*sort_state.peek(), field);
sort_state.set(next);
}
pub fn sort_icon(sort_state: SortState, field: SortField) -> &'static str {
match sort_state {
Some((current_field, SortDirection::Asc)) if current_field == field => {
"fa-solid fa-sort-up"
}
Some((current_field, SortDirection::Desc)) if current_field == field => {
"fa-solid fa-sort-down"
}
_ => "fa-solid fa-sort",
}
}
pub fn sorted_track_pairs<T: Clone>(
tracks: &[(Track, T)],
sort_state: SortState,
) -> Vec<(Track, T)> {
let tracks_for_sorting: Vec<Track> = tracks.iter().map(|(track, _)| track.clone()).collect();
sorted_track_indices(&tracks_for_sorting, sort_state)
.into_iter()
.map(|idx| tracks[idx].clone())
.collect()
}
pub fn sorted_track_indices(tracks: &[Track], sort_state: SortState) -> Vec<usize> {
let mut indices: Vec<usize> = (0..tracks.len()).collect();
if let Some((field, direction)) = sort_state {
indices.sort_by(|&left_idx, &right_idx| {
let left = &tracks[left_idx];
let right = &tracks[right_idx];
let primary = match field {
SortField::Title => compare_text(&left.title, &right.title),
SortField::Artist => compare_text(&left.artist, &right.artist),
SortField::Album => compare_text(&left.album, &right.album),
SortField::Duration => left.duration.cmp(&right.duration),
};
let directional = match direction {
SortDirection::Asc => primary,
SortDirection::Desc => primary.reverse(),
};
match field {
SortField::Album => directional
.then_with(|| {
left.disc_number
.unwrap_or(0)
.cmp(&right.disc_number.unwrap_or(0))
})
.then_with(|| {
left.track_number
.unwrap_or(0)
.cmp(&right.track_number.unwrap_or(0))
})
.then_with(|| compare_text(&left.title, &right.title))
.then_with(|| left_idx.cmp(&right_idx)),
_ => directional.then_with(|| left_idx.cmp(&right_idx)),
}
});
}
indices
}
fn compare_text(left: &str, right: &str) -> Ordering {
left.to_lowercase().cmp(&right.to_lowercase())
}
#[derive(Props, Clone, PartialEq)]
pub struct ShowcaseProps {
pub name: String,
pub description: String,
#[props(default)]
pub on_description_click: Option<EventHandler<()>>,
pub cover_url: Option<utils::CoverUrl>,
pub tracks: Vec<Track>,
pub on_play_all: EventHandler<()>,
pub on_play: EventHandler<usize>,
pub on_queue: Option<EventHandler<usize>>,
pub on_add_to_playlist: Option<EventHandler<usize>>,
pub on_delete_track: Option<EventHandler<usize>>,
pub on_remove_from_playlist: Option<EventHandler<usize>>,
pub on_view_metadata: Option<EventHandler<usize>>,
pub on_download_track: Option<EventHandler<usize>>,
pub active_track: Option<reader::TrackId>,
pub on_click_menu: Option<EventHandler<usize>>,
pub on_close_menu: Option<EventHandler<()>>,
pub actions: Option<Element>,
pub on_download_all: Option<EventHandler<()>>,
pub on_delete_all: Option<EventHandler<()>>,
#[props(default = false)]
pub is_album: bool,
#[props(default = false)]
pub is_downloading_all: bool,
#[props(default = false)]
pub is_selection_mode: bool,
#[props(default = HashSet::new())]
pub selected_tracks: HashSet<reader::TrackId>,
pub on_select: Option<EventHandler<(usize, bool)>>,
pub on_select_all: Option<EventHandler<bool>>,
#[props(default = false)]
pub all_selected: bool,
pub on_long_press: Option<EventHandler<usize>>,
pub on_cover_click: Option<EventHandler<()>>,
#[props(default = false)]
pub is_reorderable: bool,
#[props(default)]
pub on_move_up: EventHandler<usize>,
#[props(default)]
pub on_move_down: EventHandler<usize>,
}
#[component]
pub fn Showcase(props: ShowcaseProps) -> Element {
let config = use_context::<Signal<AppConfig>>();
match config.read().ui_style {
config::UiStyle::Modern => rsx! {
crate::modern::showcase::ShowcaseModern { ..props }
},
config::UiStyle::Normal => rsx! {
crate::normal::showcase::ShowcaseNormal { ..props }
},
}
}