Re-organized providers and added a few

This commit is contained in:
Connor Johnstone
2026-03-20 14:52:16 -04:00
parent fed86c9e85
commit eaaff5f98f
12 changed files with 353 additions and 185 deletions

View File

@@ -147,6 +147,10 @@ pub fn artist_page(props: &Props) -> Html {
html! {
<div>
if let Some(ref banner) = d.artist_banner {
<div class="artist-banner" style={format!("background-image: url('{banner}')")}>
</div>
}
<div class="page-header">
<div class="flex items-center justify-between">
<div class="flex gap-2 items-center">

View File

@@ -265,6 +265,56 @@ pub fn settings_page() -> Html {
html! {}
};
let lastfm_key_html = {
let key_set = ytauth.as_ref().map(|s| s.lastfm_api_key_set).unwrap_or(false);
if key_set {
html! {
<p class="text-sm" style="margin: 0.25rem 0 0 0;">
<span style="color: var(--success);">{ "\u{2713}" }</span>
{ " API key configured via " }
<code>{ "SHANTY_LASTFM_API_KEY" }</code>
</p>
}
} else {
html! {
<p class="text-sm" style="margin: 0.25rem 0 0 0; color: var(--warning);">
{ "Set " }
<code>{ "SHANTY_LASTFM_API_KEY" }</code>
{ " environment variable. Get a key at " }
<a href="https://www.last.fm/api/account/create" target="_blank">{ "last.fm/api/account/create" }</a>
{ " (use any name, leave callback URL blank)." }
</p>
}
}
};
let fanart_key_html = {
let key_set = ytauth
.as_ref()
.map(|s| s.fanart_api_key_set)
.unwrap_or(false);
if key_set {
html! {
<p class="text-sm" style="margin: 0.25rem 0 0 0;">
<span style="color: var(--success);">{ "\u{2713}" }</span>
{ " API key configured via " }
<code>{ "SHANTY_FANART_API_KEY" }</code>
{ ". Provides artist thumbnails and HD banners." }
</p>
}
} else {
html! {
<p class="text-sm" style="margin: 0.25rem 0 0 0; color: var(--warning);">
{ "Set " }
<code>{ "SHANTY_FANART_API_KEY" }</code>
{ " environment variable. Get a key at " }
<a href="https://fanart.tv/get-an-api-key/" target="_blank">{ "fanart.tv" }</a>
{ "." }
</p>
}
}
};
html! {
<div>
<div class="page-header">
@@ -441,6 +491,82 @@ pub fn settings_page() -> Html {
{ ytauth_html }
</div>
// Metadata Providers
<div class="card">
<h3>{ "Metadata Providers" }</h3>
<div class="form-group">
<label>{ "Music Database" }</label>
<select onchange={let config = config.clone(); Callback::from(move |e: Event| {
let select: HtmlSelectElement = e.target_unchecked_into();
let mut cfg = (*config).clone().unwrap();
cfg.metadata.metadata_source = select.value();
config.set(Some(cfg));
})}>
{ for [("musicbrainz", "MusicBrainz")].iter().map(|(v, label)| html! {
<option value={*v} selected={c.metadata.metadata_source == *v}>{ label }</option>
})}
</select>
</div>
<div class="form-group">
<label>{ "Artist Images" }</label>
<select onchange={let config = config.clone(); Callback::from(move |e: Event| {
let select: HtmlSelectElement = e.target_unchecked_into();
let mut cfg = (*config).clone().unwrap();
cfg.metadata.artist_image_source = select.value();
config.set(Some(cfg));
})}>
{ for [("wikipedia", "Wikipedia"), ("fanarttv", "fanart.tv")].iter().map(|(v, label)| html! {
<option value={*v} selected={c.metadata.artist_image_source == *v}>{ label }</option>
})}
</select>
</div>
if c.metadata.artist_image_source == "fanarttv" {
{ fanart_key_html.clone() }
}
<div class="form-group">
<label>{ "Artist Bios" }</label>
<select onchange={let config = config.clone(); Callback::from(move |e: Event| {
let select: HtmlSelectElement = e.target_unchecked_into();
let mut cfg = (*config).clone().unwrap();
cfg.metadata.artist_bio_source = select.value();
config.set(Some(cfg));
})}>
{ for [("wikipedia", "Wikipedia"), ("lastfm", "Last.fm")].iter().map(|(v, label)| html! {
<option value={*v} selected={c.metadata.artist_bio_source == *v}>{ label }</option>
})}
</select>
</div>
if c.metadata.artist_bio_source == "lastfm" {
{ lastfm_key_html.clone() }
}
<div class="form-group">
<label>{ "Lyrics" }</label>
<select onchange={let config = config.clone(); Callback::from(move |e: Event| {
let select: HtmlSelectElement = e.target_unchecked_into();
let mut cfg = (*config).clone().unwrap();
cfg.metadata.lyrics_source = select.value();
config.set(Some(cfg));
})}>
{ for [("lrclib", "LRCLIB")].iter().map(|(v, label)| html! {
<option value={*v} selected={c.metadata.lyrics_source == *v}>{ label }</option>
})}
</select>
</div>
<div class="form-group">
<label>{ "Cover Art" }</label>
<select onchange={let config = config.clone(); Callback::from(move |e: Event| {
let select: HtmlSelectElement = e.target_unchecked_into();
let mut cfg = (*config).clone().unwrap();
cfg.metadata.cover_art_source = select.value();
config.set(Some(cfg));
})}>
{ for [("coverartarchive", "Cover Art Archive")].iter().map(|(v, label)| html! {
<option value={*v} selected={c.metadata.cover_art_source == *v}>{ label }</option>
})}
</select>
</div>
</div>
// Indexing
<div class="card">
<h3>{ "Indexing" }</h3>

View File

@@ -67,6 +67,8 @@ pub struct FullArtistDetail {
pub artist_photo: Option<String>,
#[serde(default)]
pub artist_bio: Option<String>,
#[serde(default)]
pub artist_banner: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
@@ -176,6 +178,10 @@ pub struct YtAuthStatus {
pub ytdlp_latest: Option<String>,
#[serde(default)]
pub ytdlp_update_available: bool,
#[serde(default)]
pub lastfm_api_key_set: bool,
#[serde(default)]
pub fanart_api_key_set: bool,
}
// --- Downloads ---
@@ -289,6 +295,8 @@ pub struct AppConfig {
pub download: DownloadConfigFe,
#[serde(default)]
pub indexing: IndexingConfigFe,
#[serde(default)]
pub metadata: MetadataConfigFe,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
@@ -341,3 +349,45 @@ pub struct IndexingConfigFe {
#[serde(default)]
pub concurrency: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MetadataConfigFe {
#[serde(default = "default_metadata_source")]
pub metadata_source: String,
#[serde(default = "default_artist_image_source")]
pub artist_image_source: String,
#[serde(default = "default_artist_bio_source")]
pub artist_bio_source: String,
#[serde(default = "default_lyrics_source")]
pub lyrics_source: String,
#[serde(default = "default_cover_art_source")]
pub cover_art_source: String,
}
impl Default for MetadataConfigFe {
fn default() -> Self {
Self {
metadata_source: default_metadata_source(),
artist_image_source: default_artist_image_source(),
artist_bio_source: default_artist_bio_source(),
lyrics_source: default_lyrics_source(),
cover_art_source: default_cover_art_source(),
}
}
}
fn default_metadata_source() -> String {
"musicbrainz".into()
}
fn default_artist_image_source() -> String {
"wikipedia".into()
}
fn default_artist_bio_source() -> String {
"wikipedia".into()
}
fn default_lyrics_source() -> String {
"lrclib".into()
}
fn default_cover_art_source() -> String {
"coverartarchive".into()
}