Compare commits
13 Commits
main
...
31-use-mod
Author | SHA1 | Date | |
---|---|---|---|
2fffc16c82 | |||
08fcba5495 | |||
076d3a2865 | |||
13d6d07e8e | |||
cf07ec2982 | |||
aa76a068d6 | |||
2037122dc0 | |||
9327ec19f5 | |||
ece6d19fc3 | |||
633000062c | |||
082e6b9269 | |||
ec33b09fa9 | |||
e1d3bb4099 |
36
Cargo.lock
generated
36
Cargo.lock
generated
@ -52,7 +52,7 @@ dependencies = [
|
||||
"actix-rt",
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"ahash 0.8.6",
|
||||
"ahash 0.8.11",
|
||||
"base64 0.21.5",
|
||||
"bitflags 2.4.1",
|
||||
"brotli",
|
||||
@ -201,7 +201,7 @@ dependencies = [
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"actix-web-codegen",
|
||||
"ahash 0.8.6",
|
||||
"ahash 0.8.11",
|
||||
"bytes",
|
||||
"bytestring",
|
||||
"cfg-if",
|
||||
@ -290,9 +290,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd"
|
||||
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
@ -301,9 +301,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.6"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
@ -1193,7 +1193,7 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash 0.7.7",
|
||||
"ahash 0.7.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1208,7 +1208,7 @@ version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
dependencies = [
|
||||
"ahash 0.8.6",
|
||||
"ahash 0.8.11",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
@ -3114,9 +3114,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.89"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
|
||||
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
@ -3124,9 +3124,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.89"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
|
||||
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
@ -3151,9 +3151,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.89"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
|
||||
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@ -3161,9 +3161,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.89"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
|
||||
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -3174,9 +3174,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.89"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
|
@ -17,7 +17,7 @@ leptos = { version = "0.5", features = ["nightly"] }
|
||||
leptos_meta = { version = "0.5", features = ["nightly"] }
|
||||
leptos_actix = { version = "0.5", optional = true }
|
||||
leptos_router = { version = "0.5", features = ["nightly"] }
|
||||
wasm-bindgen = "=0.2.89"
|
||||
wasm-bindgen = "=0.2.92"
|
||||
leptos_icons = { version = "0.1.0", default_features = false, features = [
|
||||
"BsPlayFill",
|
||||
"BsPauseFill",
|
||||
|
36
src/api/albums.rs
Normal file
36
src/api/albums.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use leptos::*;
|
||||
use crate::models::Album;
|
||||
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use crate::database::get_db_conn;
|
||||
use diesel::prelude::*;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the Album associated with an album id
|
||||
///
|
||||
/// # Arguments
|
||||
/// `album_id_arg` The id of the Album to get the album for
|
||||
///
|
||||
/// # Returns
|
||||
/// A Result containing an Album if the operation was successful, or an error if the operation failed
|
||||
#[server(endpoint = "albums/get-album")]
|
||||
pub async fn get_album(album_id_arg: Option<i32>) -> Result<Album, ServerFnError> {
|
||||
|
||||
use crate::schema::albums::dsl::*;
|
||||
|
||||
let my_id = album_id_arg.ok_or(ServerFnError::ServerError("Album id must be present (Some) to get Album".to_string()))?;
|
||||
|
||||
let mut my_album_vec: Vec<Album> = albums
|
||||
.filter(id.eq(my_id))
|
||||
.limit(1)
|
||||
.load(&mut get_db_conn())?;
|
||||
|
||||
let my_album = my_album_vec.pop().ok_or(ServerFnError::ServerError("Album not found".to_string()))?;
|
||||
|
||||
Ok(my_album)
|
||||
}
|
2
src/api/mod.rs
Normal file
2
src/api/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod songs;
|
||||
pub mod albums;
|
59
src/api/songs.rs
Normal file
59
src/api/songs.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use leptos::*;
|
||||
use crate::models::Artist;
|
||||
use crate::models::Song;
|
||||
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use crate::database::get_db_conn;
|
||||
use diesel::prelude::*;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a Vector of Artists associated with a Song
|
||||
///
|
||||
/// # Arguments
|
||||
/// `song_id_arg` - The id of the Song to get the Artists for
|
||||
///
|
||||
/// # Returns
|
||||
/// A Result containing a Vector of Artists if the operation was successful, or an error if the operation failed
|
||||
#[server(endpoint = "songs/get-artists")]
|
||||
pub async fn get_artists(song_id_arg: Option<i32>) -> Result<Vec<Artist>, ServerFnError> {
|
||||
use crate::schema::artists::dsl::*;
|
||||
use crate::schema::song_artists::dsl::*;
|
||||
|
||||
let my_id = song_id_arg.ok_or(ServerFnError::ServerError("Song id must be present (Some) to get artists".to_string()))?;
|
||||
|
||||
let my_artists = artists
|
||||
.inner_join(song_artists)
|
||||
.filter(song_id.eq(my_id))
|
||||
.select(artists::all_columns())
|
||||
.load(&mut get_db_conn())?;
|
||||
|
||||
Ok(my_artists)
|
||||
}
|
||||
|
||||
/// Gets the song associated with a song id
|
||||
///
|
||||
/// # Arguments
|
||||
/// `song_id_arg` - The id of the Song to get the song for
|
||||
///
|
||||
/// # Returns
|
||||
/// A Result containing a Song if the operation was successful, or an error if the operation failed
|
||||
#[server(endpoint = "songs/get-song")]
|
||||
pub async fn get_song(song_id_arg: Option<i32>) -> Result<Song, ServerFnError> {
|
||||
use crate::schema::songs::dsl::*;
|
||||
|
||||
let my_id = song_id_arg.ok_or(ServerFnError::ServerError("Song id must be present (Some) to get Song".to_string()))?;
|
||||
|
||||
let mut my_song_vec: Vec<Song> = songs
|
||||
.filter(id.eq(my_id))
|
||||
.limit(1)
|
||||
.load(&mut get_db_conn())?;
|
||||
|
||||
let my_song = my_song_vec.pop().ok_or(ServerFnError::ServerError("Song not found".to_string()))?;
|
||||
|
||||
Ok(my_song)
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
pub mod app;
|
||||
pub mod auth;
|
||||
pub mod songdata;
|
||||
pub mod playstatus;
|
||||
pub mod playbar;
|
||||
pub mod database;
|
||||
@ -8,6 +7,7 @@ pub mod queue;
|
||||
pub mod song;
|
||||
pub mod models;
|
||||
pub mod pages;
|
||||
pub mod api;
|
||||
pub mod users;
|
||||
pub mod search;
|
||||
use cfg_if::cfg_if;
|
||||
|
@ -239,7 +239,7 @@ impl Album {
|
||||
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))]
|
||||
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::songs))]
|
||||
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct Song {
|
||||
/// A unique id for the song
|
||||
#[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))]
|
||||
|
118
src/playbar.rs
118
src/playbar.rs
@ -1,4 +1,6 @@
|
||||
use crate::playstatus::PlayStatus;
|
||||
use crate::api::songs::get_artists;
|
||||
use crate::api::albums::get_album;
|
||||
use leptos::ev::MouseEvent;
|
||||
use leptos::html::{Audio, Div};
|
||||
use leptos::leptos_dom::*;
|
||||
@ -111,26 +113,6 @@ fn toggle_queue(status: impl SignalUpdate<Value = PlayStatus>) {
|
||||
|
||||
}
|
||||
|
||||
/// Set the source of the audio player
|
||||
///
|
||||
/// Logs an error if the audio element is not available
|
||||
///
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `status` - The `PlayStatus` to get the audio element from, as a signal
|
||||
/// * `src` - The source to set the audio player to
|
||||
///
|
||||
fn set_play_src(status: impl SignalUpdate<Value = PlayStatus>, src: String) {
|
||||
status.update(|status| {
|
||||
if let Some(audio) = status.get_audio() {
|
||||
audio.set_src(&src);
|
||||
log!("Player set src to: {}", src);
|
||||
} else {
|
||||
error!("Unable to set src: Audio element not available");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// The play, pause, and skip buttons
|
||||
#[component]
|
||||
fn PlayControls(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
@ -151,10 +133,8 @@ fn PlayControls(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
status.update(|status| last_played_song = status.history.pop_back());
|
||||
|
||||
if let Some(last_played_song) = last_played_song {
|
||||
// Push the popped song to the front of the queue, and play it
|
||||
let next_src = last_played_song.song_path.clone();
|
||||
// Push the popped song to the front of the queue, and set status to playing
|
||||
status.update(|status| status.queue.push_front(last_played_song));
|
||||
set_play_src(status, next_src);
|
||||
set_playing(status, true);
|
||||
} else {
|
||||
warn!("Unable to skip back: No previous song");
|
||||
@ -245,26 +225,46 @@ fn PlayDuration(elapsed_secs: MaybeSignal<i64>, total_secs: MaybeSignal<i64>) ->
|
||||
fn MediaInfo(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
let name = Signal::derive(move || {
|
||||
status.with(|status| {
|
||||
status.queue.front().map_or("No media playing".into(), |song| song.name.clone())
|
||||
status.queue.front().map_or("No media playing".into(), |song| song.title.clone())
|
||||
})
|
||||
});
|
||||
|
||||
let artist = Signal::derive(move || {
|
||||
let song_id = Signal::derive(move || {
|
||||
status.with(|status| {
|
||||
status.queue.front().map_or("".into(), |song| song.artist.clone())
|
||||
status.queue.front().map_or(None, |song| song.id)
|
||||
})
|
||||
});
|
||||
|
||||
let album = Signal::derive(move || {
|
||||
let song_artists_resource = create_resource(song_id, move |song_id| async move {
|
||||
if let Some(song_id) = song_id {
|
||||
let artists_vec = get_artists(Some(song_id)).await.map_or(Vec::new(), |artists| artists);
|
||||
// convert the vec of artists to a string of artists separated by commas
|
||||
let artists_string = artists_vec.iter().map(|artist| artist.name.clone()).collect::<Vec<String>>().join(", ");
|
||||
artists_string
|
||||
} else {
|
||||
"".into()
|
||||
}
|
||||
});
|
||||
|
||||
let album_id = Signal::derive(move || {
|
||||
status.with(|status| {
|
||||
status.queue.front().map_or("".into(), |song| song.album.clone())
|
||||
status.queue.front().map_or(None, |song| song.album_id)
|
||||
})
|
||||
});
|
||||
|
||||
let album_resource = create_resource(album_id, move |album_id| async move {
|
||||
// get the album name attribute or return ""
|
||||
if let Some(album_id) = album_id {
|
||||
get_album(Some(album_id)).await.map_or("".into(), |album| album.title)
|
||||
} else {
|
||||
"".into()
|
||||
}
|
||||
});
|
||||
|
||||
let image = Signal::derive(move || {
|
||||
status.with(|status| {
|
||||
// TODO Use some default / unknown image?
|
||||
status.queue.front().map_or("".into(), |song| song.image_path.clone())
|
||||
status.queue.front().map_or("".into(), |song| song.image_path.clone().unwrap_or("".into()))
|
||||
})
|
||||
});
|
||||
|
||||
@ -273,8 +273,29 @@ fn MediaInfo(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
<img class="media-info-img" align="left" src={image}/>
|
||||
<div class="media-info-text">
|
||||
{name}
|
||||
<br/>
|
||||
{artist} - {album}
|
||||
<br/>
|
||||
<Suspense
|
||||
fallback=move || {
|
||||
view! {}
|
||||
}
|
||||
>
|
||||
{move || {
|
||||
song_artists_resource.get().map_or(view!{{}""}, |artists_string| view! {
|
||||
{artists_string}" - "
|
||||
})
|
||||
}}
|
||||
</Suspense>
|
||||
<Suspense
|
||||
fallback=move || {
|
||||
view! {}
|
||||
}
|
||||
>
|
||||
{move || {
|
||||
album_resource.get().map_or(view!{{}""}, |album_name| view! {
|
||||
""{album_name}
|
||||
})
|
||||
}}
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -346,6 +367,33 @@ fn QueueToggle(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
/// The main play bar component, containing the progress bar, media info, play controls, and play duration
|
||||
#[component]
|
||||
pub fn PlayBar(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
|
||||
// Set the source of the audio player to the first song in the queue
|
||||
let current_song_path = create_memo(
|
||||
move |_| {
|
||||
status.with(|status| {
|
||||
status.queue.front().map(|song| song.storage_path.clone())
|
||||
})
|
||||
}
|
||||
);
|
||||
create_effect(move |_| {
|
||||
current_song_path.with(|current_song_path| {
|
||||
status.with_untracked(|status| {
|
||||
if let Some(audio) = status.get_audio() {
|
||||
if let Some(song_path) = current_song_path {
|
||||
audio.set_src(&song_path);
|
||||
log!("Player set src to: {}", song_path);
|
||||
} else {
|
||||
// We are treating this as a non-fatal error because the queue could be empty or finished
|
||||
warn!("Unable to set src: No song in queue");
|
||||
}
|
||||
} else {
|
||||
error!("Unable to set src: Audio element not available");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Listen for key down events -- arrow keys don't seem to trigger key press events
|
||||
let _arrow_key_handle = window_event_listener(ev::keydown, move |e: ev::KeyboardEvent| {
|
||||
if e.key() == "ArrowRight" {
|
||||
@ -402,11 +450,11 @@ pub fn PlayBar(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
status.with_untracked(|status| {
|
||||
// Start playing the first song in the queue, if available
|
||||
if let Some(song) = status.queue.front() {
|
||||
log!("Starting playing with song: {}", song.name);
|
||||
log!("Starting playing with song: {}", song.title);
|
||||
|
||||
// Don't use the set_play_src / set_playing helper function
|
||||
// here because we already have access to the audio element
|
||||
audio.set_src(&song.song_path);
|
||||
audio.set_src(&song.storage_path);
|
||||
|
||||
if let Err(e) = audio.play() {
|
||||
error!("Error playing audio on load: {:?}", e);
|
||||
@ -455,7 +503,7 @@ pub fn PlayBar(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
let prev_song = status.queue.pop_front();
|
||||
|
||||
if let Some(prev_song) = prev_song {
|
||||
log!("Adding song to history: {}", prev_song.name);
|
||||
log!("Adding song to history: {}", prev_song.title);
|
||||
status.history.push_back(prev_song);
|
||||
} else {
|
||||
log!("Queue empty, no previous song to add to history");
|
||||
@ -464,7 +512,7 @@ pub fn PlayBar(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
|
||||
// Get the next song to play, if available
|
||||
let next_src = status.with_untracked(|status| {
|
||||
status.queue.front().map(|song| song.song_path.clone())
|
||||
status.queue.front().map(|song| song.storage_path.clone())
|
||||
});
|
||||
|
||||
if let Some(audio) = audio_ref.get() {
|
||||
|
@ -3,7 +3,7 @@ use leptos::NodeRef;
|
||||
use leptos::html::Audio;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::songdata::SongData;
|
||||
use crate::models::Song;
|
||||
|
||||
/// Represents the global state of the audio player feature of LibreTunes
|
||||
pub struct PlayStatus {
|
||||
@ -14,9 +14,9 @@ pub struct PlayStatus {
|
||||
/// A reference to the HTML audio element
|
||||
pub audio_player: Option<NodeRef<Audio>>,
|
||||
/// A queue of songs that have been played, ordered from oldest to newest
|
||||
pub history: VecDeque<SongData>,
|
||||
pub history: VecDeque<Song>,
|
||||
/// A queue of songs that have yet to be played, ordered from next up to last
|
||||
pub queue: VecDeque<SongData>,
|
||||
pub queue: VecDeque<Song>,
|
||||
}
|
||||
|
||||
impl PlayStatus {
|
||||
|
@ -99,7 +99,7 @@ pub fn Queue(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
on:dragenter=move |e: DragEvent| on_drag_enter(e, index)
|
||||
on:dragover=on_drag_over
|
||||
>
|
||||
<Song song_image_path=song.image_path.clone() song_title=song.name.clone() song_artist=song.artist.clone() />
|
||||
<Song song_id_arg=song.id song_image_path=song.image_path.clone().unwrap_or("".to_string()) song_title=song.title.clone() />
|
||||
<Show
|
||||
when=move || index != 0
|
||||
fallback=|| view!{
|
||||
|
32
src/song.rs
32
src/song.rs
@ -1,13 +1,41 @@
|
||||
use leptos::*;
|
||||
use leptos::logging::*;
|
||||
use crate::api::songs::get_artists;
|
||||
|
||||
#[component]
|
||||
pub fn Song(song_image_path: String, song_title: String, song_artist: String) -> impl IntoView {
|
||||
pub fn Song(song_id_arg: Option<i32>, song_image_path: String, song_title: String) -> impl IntoView {
|
||||
|
||||
let song_id = Signal::derive(move || song_id_arg);
|
||||
|
||||
let song_artists_resource = create_resource(song_id, move |song_id| async move {
|
||||
let artists_vec = get_artists(song_id).await.unwrap_or_else(|_| {
|
||||
warn!("Error when searching for artists for song");
|
||||
Vec::new()
|
||||
});
|
||||
// convert the vec of artists to a string of artists separated by commas
|
||||
let artists_string = artists_vec.iter().map(|artist| artist.name.clone()).collect::<Vec<String>>().join(", ");
|
||||
artists_string
|
||||
});
|
||||
|
||||
view!{
|
||||
<div class="queue-song">
|
||||
<img src={song_image_path} alt={song_title.clone()} />
|
||||
<div class="queue-song-info">
|
||||
<h3>{song_title}</h3>
|
||||
<p>{song_artist}</p>
|
||||
<Suspense
|
||||
fallback=move || view! {<p class="fallback-artists">""</p>}
|
||||
>
|
||||
{move || {
|
||||
song_artists_resource.get().map(|artists_string| {
|
||||
if artists_string.is_empty() {
|
||||
view! {<p class="fallback-artists">""</p>}
|
||||
}
|
||||
else {
|
||||
view! {<p class="artists">{artists_string}</p>}
|
||||
}
|
||||
})
|
||||
}}
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
/// Holds information about a song
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SongData {
|
||||
/// Song name
|
||||
pub name: String,
|
||||
/// Song artist
|
||||
pub artist: String,
|
||||
/// Song album
|
||||
pub album: String,
|
||||
/// Path to song file, relative to the root of the web server.
|
||||
/// For example, `"/assets/audio/Song.mp3"`
|
||||
pub song_path: String,
|
||||
/// Path to song image, relative to the root of the web server.
|
||||
/// For example, `"/assets/images/Song.jpg"`
|
||||
pub image_path: String,
|
||||
}
|
@ -48,7 +48,12 @@
|
||||
color: #fff; /* Adjust text color for song */
|
||||
}
|
||||
|
||||
p {
|
||||
.fallback-artists {
|
||||
margin: 14px 0 0 0; /* Adjust margin for blank space to align text */
|
||||
}
|
||||
|
||||
.artists {
|
||||
font-size: 14px;
|
||||
margin: 0; /* Remove default margin for paragraph */
|
||||
color: #aaa; /* Adjust text color for artist */
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user