215 lines
9.0 KiB
Rust
215 lines
9.0 KiB
Rust
use web_sys::HtmlInputElement;
|
|
use web_sys::HtmlSelectElement;
|
|
use yew::prelude::*;
|
|
|
|
use crate::api;
|
|
use crate::types::*;
|
|
|
|
enum SearchResults {
|
|
None,
|
|
Artists(Vec<ArtistResult>),
|
|
Albums(Vec<AlbumResult>),
|
|
Tracks(Vec<TrackResult>),
|
|
}
|
|
|
|
#[function_component(SearchPage)]
|
|
pub fn search_page() -> Html {
|
|
let query = use_state(|| String::new());
|
|
let search_type = use_state(|| "artist".to_string());
|
|
let results = use_state(|| SearchResults::None);
|
|
let error = use_state(|| None::<String>);
|
|
let message = use_state(|| None::<String>);
|
|
|
|
let on_search = {
|
|
let query = query.clone();
|
|
let search_type = search_type.clone();
|
|
let results = results.clone();
|
|
let error = error.clone();
|
|
Callback::from(move |e: SubmitEvent| {
|
|
e.prevent_default();
|
|
let q = (*query).clone();
|
|
let st = (*search_type).clone();
|
|
let results = results.clone();
|
|
let error = error.clone();
|
|
wasm_bindgen_futures::spawn_local(async move {
|
|
error.set(None);
|
|
match st.as_str() {
|
|
"artist" => match api::search_artist(&q, 10).await {
|
|
Ok(r) => results.set(SearchResults::Artists(r)),
|
|
Err(e) => error.set(Some(e.0)),
|
|
},
|
|
"album" => match api::search_album(&q, None, 10).await {
|
|
Ok(r) => results.set(SearchResults::Albums(r)),
|
|
Err(e) => error.set(Some(e.0)),
|
|
},
|
|
"track" => match api::search_track(&q, None, 10).await {
|
|
Ok(r) => results.set(SearchResults::Tracks(r)),
|
|
Err(e) => error.set(Some(e.0)),
|
|
},
|
|
_ => {}
|
|
}
|
|
});
|
|
})
|
|
};
|
|
|
|
let on_add_artist = {
|
|
let message = message.clone();
|
|
let error = error.clone();
|
|
move |name: String, mbid: String| {
|
|
let message = message.clone();
|
|
let error = error.clone();
|
|
wasm_bindgen_futures::spawn_local(async move {
|
|
match api::add_artist(&name, Some(&mbid)).await {
|
|
Ok(s) => message.set(Some(format!(
|
|
"Added {} tracks ({} already owned)",
|
|
s.tracks_added, s.tracks_already_owned
|
|
))),
|
|
Err(e) => error.set(Some(e.0)),
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
let on_add_album = {
|
|
let message = message.clone();
|
|
let error = error.clone();
|
|
move |artist: String, title: String, mbid: String| {
|
|
let message = message.clone();
|
|
let error = error.clone();
|
|
wasm_bindgen_futures::spawn_local(async move {
|
|
match api::add_album(&artist, &title, Some(&mbid)).await {
|
|
Ok(s) => message.set(Some(format!(
|
|
"Added {} tracks ({} already owned)",
|
|
s.tracks_added, s.tracks_already_owned
|
|
))),
|
|
Err(e) => error.set(Some(e.0)),
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
html! {
|
|
<div>
|
|
<div class="page-header">
|
|
<h2>{ "Search" }</h2>
|
|
<p>{ "Find music on MusicBrainz and add to your library" }</p>
|
|
</div>
|
|
|
|
<form onsubmit={on_search}>
|
|
<div class="search-bar">
|
|
<input
|
|
type="text"
|
|
placeholder="Search..."
|
|
value={(*query).clone()}
|
|
oninput={let q = query.clone(); Callback::from(move |e: InputEvent| {
|
|
let input: HtmlInputElement = e.target_unchecked_into();
|
|
q.set(input.value());
|
|
})}
|
|
/>
|
|
<select onchange={let st = search_type.clone(); Callback::from(move |e: Event| {
|
|
let select: HtmlSelectElement = e.target_unchecked_into();
|
|
st.set(select.value());
|
|
})}>
|
|
<option value="artist" selected=true>{ "Artist" }</option>
|
|
<option value="album">{ "Album" }</option>
|
|
<option value="track">{ "Track" }</option>
|
|
</select>
|
|
<button type="submit" class="btn btn-primary">{ "Search" }</button>
|
|
</div>
|
|
</form>
|
|
|
|
if let Some(ref msg) = *message {
|
|
<div class="card" style="border-color: var(--success);">
|
|
<p>{ msg }</p>
|
|
</div>
|
|
}
|
|
if let Some(ref err) = *error {
|
|
<div class="card error">{ err }</div>
|
|
}
|
|
|
|
{ match &*results {
|
|
SearchResults::None => html! {},
|
|
SearchResults::Artists(items) => html! {
|
|
<table>
|
|
<thead>
|
|
<tr><th>{ "Name" }</th><th>{ "Country" }</th><th>{ "Type" }</th><th>{ "Score" }</th><th></th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{ for items.iter().map(|a| {
|
|
let name = a.name.clone();
|
|
let mbid = a.id.clone();
|
|
let on_add = on_add_artist.clone();
|
|
html! {
|
|
<tr>
|
|
<td>
|
|
{ &a.name }
|
|
if let Some(ref d) = a.disambiguation {
|
|
<span class="text-muted text-sm">{ format!(" ({d})") }</span>
|
|
}
|
|
</td>
|
|
<td>{ a.country.as_deref().unwrap_or("") }</td>
|
|
<td>{ a.artist_type.as_deref().unwrap_or("") }</td>
|
|
<td>{ a.score }</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-success"
|
|
onclick={Callback::from(move |_| on_add(name.clone(), mbid.clone()))}>
|
|
{ "Add" }
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
}
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
},
|
|
SearchResults::Albums(items) => html! {
|
|
<table>
|
|
<thead>
|
|
<tr><th>{ "Title" }</th><th>{ "Artist" }</th><th>{ "Year" }</th><th>{ "Score" }</th><th></th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{ for items.iter().map(|a| {
|
|
let artist = a.artist.clone();
|
|
let title = a.title.clone();
|
|
let mbid = a.id.clone();
|
|
let on_add = on_add_album.clone();
|
|
html! {
|
|
<tr>
|
|
<td>{ &a.title }</td>
|
|
<td>{ &a.artist }</td>
|
|
<td>{ a.year.as_deref().unwrap_or("") }</td>
|
|
<td>{ a.score }</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-success"
|
|
onclick={Callback::from(move |_| on_add(artist.clone(), title.clone(), mbid.clone()))}>
|
|
{ "Add" }
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
}
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
},
|
|
SearchResults::Tracks(items) => html! {
|
|
<table>
|
|
<thead>
|
|
<tr><th>{ "Title" }</th><th>{ "Artist" }</th><th>{ "Album" }</th><th>{ "Score" }</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{ for items.iter().map(|t| html! {
|
|
<tr>
|
|
<td>{ &t.title }</td>
|
|
<td>{ &t.artist }</td>
|
|
<td>{ t.album.as_deref().unwrap_or("") }</td>
|
|
<td>{ t.score }</td>
|
|
</tr>
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
},
|
|
}}
|
|
</div>
|
|
}
|
|
}
|