Compare commits
1 Commits
4b6844b85e
...
07aa9908e8
| Author | SHA1 | Date | |
|---|---|---|---|
| 07aa9908e8 |
@@ -1,3 +1,6 @@
|
|||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::prelude::*;
|
use yew_router::prelude::*;
|
||||||
|
|
||||||
@@ -17,7 +20,7 @@ pub fn library_page() -> Html {
|
|||||||
let artists = artists.clone();
|
let artists = artists.clone();
|
||||||
let error = error.clone();
|
let error = error.clone();
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
match api::list_artists(200, 0).await {
|
match api::list_artists(0, 0).await {
|
||||||
Ok(a) => artists.set(Some(a)),
|
Ok(a) => artists.set(Some(a)),
|
||||||
Err(e) => error.set(Some(e.0)),
|
Err(e) => error.set(Some(e.0)),
|
||||||
}
|
}
|
||||||
@@ -40,6 +43,33 @@ pub fn library_page() -> Html {
|
|||||||
return html! { <p class="loading">{ "Loading..." }</p> };
|
return html! { <p class="loading">{ "Loading..." }</p> };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Pre-compute which artist IDs are first in their letter group (for anchor IDs)
|
||||||
|
let mut seen_letters = HashSet::new();
|
||||||
|
let first_of_letter: HashMap<i32, char> = artists
|
||||||
|
.iter()
|
||||||
|
.filter_map(|a| {
|
||||||
|
let first = a.name.chars().next().unwrap_or('#').to_ascii_uppercase();
|
||||||
|
let letter = if first.is_ascii_alphabetic() {
|
||||||
|
first
|
||||||
|
} else {
|
||||||
|
'#'
|
||||||
|
};
|
||||||
|
if seen_letters.insert(letter) {
|
||||||
|
Some((a.id, letter))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let active_letters: HashSet<char> = first_of_letter.values().copied().collect();
|
||||||
|
|
||||||
|
// Build scroll track letter data
|
||||||
|
let letters: Vec<char> = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect();
|
||||||
|
let scroll_track_items: Vec<(String, bool)> = letters
|
||||||
|
.iter()
|
||||||
|
.map(|&c| (c.to_string(), active_letters.contains(&c)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div>
|
<div>
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
@@ -50,6 +80,28 @@ pub fn library_page() -> Html {
|
|||||||
if artists.is_empty() {
|
if artists.is_empty() {
|
||||||
<p class="text-muted">{ "No artists in library. Use Search to add some!" }</p>
|
<p class="text-muted">{ "No artists in library. Use Search to add some!" }</p>
|
||||||
} else {
|
} else {
|
||||||
|
<div class="scroll-track">
|
||||||
|
{ for scroll_track_items.iter().map(|(letter, active)| {
|
||||||
|
let letter_c = letter.clone();
|
||||||
|
let active = *active;
|
||||||
|
let class = if active { "scroll-track-letter has-artists" } else { "scroll-track-letter" };
|
||||||
|
let onclick = Callback::from(move |_: MouseEvent| {
|
||||||
|
if active {
|
||||||
|
if let Some(window) = web_sys::window() {
|
||||||
|
if let Some(doc) = window.document() {
|
||||||
|
if let Some(el) = doc.get_element_by_id(&format!("letter-{}", letter_c)) {
|
||||||
|
let _ = el.dyn_into::<web_sys::HtmlElement>()
|
||||||
|
.map(|el| el.scroll_into_view());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
html! {
|
||||||
|
<div class={class} onclick={onclick}>{ letter }</div>
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -62,6 +114,7 @@ pub fn library_page() -> Html {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{ for artists.iter().map(|a| {
|
{ for artists.iter().map(|a| {
|
||||||
|
let anchor_id = first_of_letter.get(&a.id).map(|c| format!("letter-{c}"));
|
||||||
let artist_id = a.id;
|
let artist_id = a.id;
|
||||||
let artist_name = a.name.clone();
|
let artist_name = a.name.clone();
|
||||||
let artist_mbid = a.musicbrainz_id.clone();
|
let artist_mbid = a.musicbrainz_id.clone();
|
||||||
@@ -157,7 +210,7 @@ pub fn library_page() -> Html {
|
|||||||
let watched_bar_style = format!("width:{watched_pct}%;background:{watched_color};");
|
let watched_bar_style = format!("width:{watched_pct}%;background:{watched_color};");
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<tr>
|
<tr id={anchor_id}>
|
||||||
<td>
|
<td>
|
||||||
<Link<Route> to={Route::Artist { id: a.id.to_string() }}>
|
<Link<Route> to={Route::Artist { id: a.id.to_string() }}>
|
||||||
{ &a.name }
|
{ &a.name }
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub fn playlists_page() -> Html {
|
|||||||
let all_artists = all_artists.clone();
|
let all_artists = all_artists.clone();
|
||||||
use_effect_with((), move |_| {
|
use_effect_with((), move |_| {
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
if let Ok(artists) = api::list_artists(500, 0).await {
|
if let Ok(artists) = api::list_artists(0, 0).await {
|
||||||
all_artists.set(artists);
|
all_artists.set(artists);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -445,4 +445,38 @@ tr[draggable="true"]:active { cursor: grabbing; }
|
|||||||
.album-art-lg { width: 120px; height: 120px; }
|
.album-art-lg { width: 120px; height: 120px; }
|
||||||
.album-header { flex-direction: column; }
|
.album-header { flex-direction: column; }
|
||||||
.artist-photo { width: 80px; height: 80px; }
|
.artist-photo { width: 80px; height: 80px; }
|
||||||
|
.scroll-track { right: 2px; width: 14px; }
|
||||||
|
.scroll-track-letter { font-size: 0.5rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alphabetical scroll track */
|
||||||
|
.scroll-track {
|
||||||
|
position: fixed;
|
||||||
|
right: 8px;
|
||||||
|
top: 60px;
|
||||||
|
bottom: 20px;
|
||||||
|
width: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.scroll-track-letter {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: default;
|
||||||
|
padding: 0 2px;
|
||||||
|
line-height: 1;
|
||||||
|
user-select: none;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
.scroll-track-letter.has-artists {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.scroll-track-letter.has-artists:hover {
|
||||||
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,11 @@ async fn list_artists(
|
|||||||
query: web::Query<PaginationParams>,
|
query: web::Query<PaginationParams>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
auth::require_auth(&session)?;
|
auth::require_auth(&session)?;
|
||||||
let artists = queries::artists::list(state.db.conn(), query.limit, query.offset).await?;
|
let artists = if query.limit == 0 {
|
||||||
|
queries::artists::list_all(state.db.conn()).await?
|
||||||
|
} else {
|
||||||
|
queries::artists::list(state.db.conn(), query.limit, query.offset).await?
|
||||||
|
};
|
||||||
|
|
||||||
let wanted = queries::wanted::list(state.db.conn(), None, None).await?;
|
let wanted = queries::wanted::list(state.db.conn(), None, None).await?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user