diff --git a/Cargo.lock b/Cargo.lock index 0ea1c2d..195bd07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1311,8 +1311,10 @@ checksum = "f41f2deec9249d16ef6b1a8442fbe16013f67053797052aa0b7d2f5ebd0f0098" dependencies = [ "icondata_ai", "icondata_bs", + "icondata_cg", "icondata_core", "icondata_io", + "icondata_ri", ] [[package]] @@ -1333,6 +1335,15 @@ dependencies = [ "icondata_core", ] +[[package]] +name = "icondata_cg" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b90cda35aa524761219a8dbd006513734e08a4cf92ee05820b01749e76435462" +dependencies = [ + "icondata_core", +] + [[package]] name = "icondata_core" version = "0.0.2" @@ -1348,6 +1359,15 @@ dependencies = [ "icondata_core", ] +[[package]] +name = "icondata_ri" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d3adc5b64b22d10ab23a7b1a005f4cb52f3d08909f578fbaa09af9f9c0b7b" +dependencies = [ + "icondata_core", +] + [[package]] name = "ident_case" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 659bfc4..d22827c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,8 @@ leptos_icons = { version = "0.1.0", default_features = false, features = [ "BsPauseFill", "BsSkipStartFill", "BsSkipEndFill", + "RiPlayListMediaFill", + "CgTrash", "IoReturnUpBackSharp", "AiEyeFilled", "AiEyeInvisibleFilled" diff --git a/src/app.rs b/src/app.rs index 5955bb5..6df1766 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,3 +1,6 @@ +use crate::playbar::PlayBar; +use crate::playstatus::PlayStatus; +use crate::queue::Queue; use leptos::*; use leptos_meta::*; use leptos_router::*; @@ -34,7 +37,13 @@ pub fn App() -> impl IntoView { /// Renders the home page of your application. #[component] fn HomePage() -> impl IntoView { - view! {} + let mut play_status = PlayStatus::default(); + let play_status = create_rw_signal(play_status); + + view! { + + + } } /// 404 - Not Found diff --git a/src/lib.rs b/src/lib.rs index f73732d..95ab806 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ pub mod songdata; pub mod playstatus; pub mod playbar; pub mod database; +pub mod queue; +pub mod song; pub mod models; pub mod pages; pub mod users; diff --git a/src/playbar.rs b/src/playbar.rs index 5aa8a90..fcab9a7 100644 --- a/src/playbar.rs +++ b/src/playbar.rs @@ -6,6 +6,7 @@ use leptos::html::{Audio, Div}; use leptos::leptos_dom::*; use leptos::*; use leptos_icons::BsIcon::*; +use leptos_icons::RiIcon::*; use leptos_icons::*; /// Width and height of the forward/backward skip buttons @@ -13,6 +14,9 @@ const SKIP_BTN_SIZE: &str = "3.5em"; /// Width and height of the play/pause button const PLAY_BTN_SIZE: &str = "5em"; +// Width and height of the queue button +const QUEUE_BTN_SIZE: &str = "3.5em"; + /// Threshold in seconds for skipping to the previous song instead of skipping to the start of the current song const MIN_SKIP_BACK_TIME: f64 = 5.0; @@ -32,7 +36,7 @@ const ARROW_KEY_SKIP_TIME: f64 = 5.0; /// * `None` if the audio element is not available /// * `Some((current_time, duration))` if the audio element is available /// -fn get_song_time_duration(status: impl SignalWithUntracked) -> Option<(f64, f64)> { +pub fn get_song_time_duration(status: impl SignalWithUntracked) -> Option<(f64, f64)> { status.with_untracked(|status| { if let Some(audio) = status.get_audio() { Some((audio.current_time(), audio.duration())) @@ -53,7 +57,7 @@ fn get_song_time_duration(status: impl SignalWithUntracked) /// * `status` - The `PlayStatus` to get the audio element from, as a signal /// * `time` - The time to skip to, in seconds /// -fn skip_to(status: impl SignalUpdate, time: f64) { +pub fn skip_to(status: impl SignalUpdate, time: f64) { if time.is_infinite() || time.is_nan() { error!("Unable to skip to non-finite time: {}", time); return @@ -77,7 +81,7 @@ fn skip_to(status: impl SignalUpdate, time: f64) { /// * `status` - The `PlayStatus` to get the audio element from, as a signal /// * `play` - `true` to play the song, `false` to pause it /// -fn set_playing(status: impl SignalUpdate, play: bool) { +pub fn set_playing(status: impl SignalUpdate, play: bool) { status.update(|status| { if let Some(audio) = status.get_audio() { if play { @@ -101,6 +105,14 @@ fn set_playing(status: impl SignalUpdate, play: bool) { }); } +fn toggle_queue(status: impl SignalUpdate) { + status.update(|status| { + status.queue_open = !status.queue_open; + }); + + +} + /// Set the source of the audio player /// /// Logs an error if the audio element is not available @@ -309,6 +321,30 @@ fn ProgressBar(percentage: MaybeSignal, status: RwSignal) -> im } } +#[component] +fn QueueToggle(status: RwSignal) -> impl IntoView { + + let update_queue = move |_| { + toggle_queue(status); + log!("queue button pressed, queue status: {:?}", status.with_untracked(|status| status.queue_open)); + }; + + // We use this to prevent the buttons from being focused when clicked + // If buttons were focused on clicks, then pressing space bar to play/pause would "click" the button + // and trigger unwanted behavior + let prevent_focus = move |e: MouseEvent| { + e.prevent_default(); + }; + + view! { +
+ +
+ } +} + /// The main play bar component, containing the progress bar, media info, play controls, and play duration #[component] pub fn PlayBar(status: RwSignal) -> impl IntoView { @@ -457,6 +493,7 @@ pub fn PlayBar(status: RwSignal) -> impl IntoView { + } } diff --git a/src/playstatus.rs b/src/playstatus.rs index 363d3cf..5952ee9 100644 --- a/src/playstatus.rs +++ b/src/playstatus.rs @@ -9,6 +9,8 @@ use crate::songdata::SongData; pub struct PlayStatus { /// Whether or not the audio player is currently playing pub playing: bool, + /// Whether or not the queue is open + pub queue_open: bool, /// A reference to the HTML audio element pub audio_player: Option>, /// A queue of songs that have been played, ordered from oldest to newest @@ -53,6 +55,7 @@ impl Default for PlayStatus { fn default() -> Self { Self { playing: false, + queue_open: false, audio_player: None, history: VecDeque::new(), queue: VecDeque::new(), diff --git a/src/queue.rs b/src/queue.rs new file mode 100644 index 0000000..fc786b9 --- /dev/null +++ b/src/queue.rs @@ -0,0 +1,121 @@ +use crate::playstatus::PlayStatus; +use crate::song::Song; +use leptos::ev::MouseEvent; +use leptos::leptos_dom::*; +use leptos::*; +use leptos_icons::*; +use leptos_icons::CgIcon::*; +use leptos::ev::DragEvent; + +const RM_BTN_SIZE: &str = "2.5rem"; + +fn remove_song_fn(index: usize, status: RwSignal) { + if index == 0 { + log!("Error: Trying to remove currently playing song (index 0) from queue"); + } else { + log!("Remove Song from Queue: Song is not currently playing, deleting song from queue and not adding to history"); + status.update(|status| { + status.queue.remove(index); + }); + } +} + +#[component] +pub fn Queue(status: RwSignal) -> impl IntoView { + + let remove_song = move |index: usize| { + remove_song_fn(index, status); + log!("Removed song {}", index + 1); + }; + + let prevent_focus = move |e: MouseEvent| { + e.prevent_default(); + }; + + let index_being_dragged = create_rw_signal(-1); + + let index_being_hovered = create_rw_signal(-1); + + let on_drag_start = move |_e: DragEvent, index: usize| { + // set the index of the item being dragged + index_being_dragged.set(index as i32); + }; + + let on_drop = move |e: DragEvent| { + e.prevent_default(); + // if the index of the item being dragged is not the same as the index of the item being hovered over + if index_being_dragged.get() != index_being_hovered.get() && index_being_dragged.get() > 0 && index_being_hovered.get() > 0 { + // get the index of the item being dragged + let dragged_index = index_being_dragged.get_untracked() as usize; + // get the index of the item being hovered over + let hovered_index = index_being_hovered.get_untracked() as usize; + // update the queue + status.update(|status| { + // remove the dragged item from the list + let dragged_item = status.queue.remove(dragged_index); + // insert the dragged item at the index of the item being hovered over + status.queue.insert(hovered_index, dragged_item.unwrap()); + }); + // reset the index of the item being dragged + index_being_dragged.set(-1); + // reset the index of the item being hovered over + index_being_hovered.set(-1); + log!("drag end. Moved item from index {} to index {}", dragged_index, hovered_index); + } + else { + // reset the index of the item being dragged + index_being_dragged.set(-1); + // reset the index of the item being hovered over + index_being_hovered.set(-1); + } + }; + + let on_drag_enter = move |_e: DragEvent, index: usize| { + // set the index of the item being hovered over + index_being_hovered.set(index as i32); + }; + + let on_drag_over = move |e: DragEvent| { + e.prevent_default(); + }; + + view!{ + +
+
+

Queue

+
+
    + { + move || status.with(|status| status.queue.iter() + .enumerate() + .map(|(index, song)| view! { +
    + + Playing

    + }> + +
    +
    + }) + .collect::>()) + } +
+
+
+ + } +} diff --git a/src/song.rs b/src/song.rs new file mode 100644 index 0000000..615cfc6 --- /dev/null +++ b/src/song.rs @@ -0,0 +1,14 @@ +use leptos::*; + +#[component] +pub fn Song(song_image_path: String, song_title: String, song_artist: String) -> impl IntoView { + view!{ +
+ {song_title.clone()} +
+

{song_title}

+

{song_artist}

+
+
+ } +} \ No newline at end of file diff --git a/src/songdata.rs b/src/songdata.rs index c24c682..7e0dd0c 100644 --- a/src/songdata.rs +++ b/src/songdata.rs @@ -1,5 +1,5 @@ /// Holds information about a song -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SongData { /// Song name pub name: String, diff --git a/style/main.scss b/style/main.scss index 60b68fd..6706ef1 100644 --- a/style/main.scss +++ b/style/main.scss @@ -1,5 +1,6 @@ @import 'playbar.scss'; @import 'theme.scss'; +@import 'queue.scss'; @import 'login.scss'; @import 'signup.scss'; diff --git a/style/playbar.scss b/style/playbar.scss index 058b6c4..6d91b94 100644 --- a/style/playbar.scss +++ b/style/playbar.scss @@ -88,4 +88,28 @@ right: 10px; top: 13px; } + + .queue-toggle { + position: absolute; + bottom: 13px; + top: 13px; + right: 90px; + + button { + .controlbtn { + color: $text-controls-color; + } + + .controlbtn:hover { + color: $controls-hover-color; + } + + .controlbtn:active { + color: $controls-click-color; + } + + background-color: transparent; + border: transparent; + } + } } diff --git a/style/queue.scss b/style/queue.scss new file mode 100644 index 0000000..aa5e637 --- /dev/null +++ b/style/queue.scss @@ -0,0 +1,76 @@ +@import 'theme.scss'; + +.queue { + position: fixed; + top: 0; + right: 0; + width: 400px; + height: calc(100% - 78.9px); /* Adjust height to fit the queue */ + background-color: #424242; /* Queue background color */ + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + overflow-y: auto; /* Add scroll bar when queue is too long */ + + .queue-header { + background-color: #333; /* Header background color */ + color: #fff; /* Header text color */ + padding: 10px; + text-align: center; + } + + ul { + list-style: none; + padding: 0; + margin: 0; + + .queue-item { + display: flex; + align-items: center; + padding-left: 10px; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; + + .queue-song { + display: flex; + align-items: center; + padding: 10px; + border-bottom: 1px solid #ccc; /* Separator line color */ + + img { + max-width: 50px; /* Adjust maximum width for images */ + margin-right: 10px; /* Add spacing between image and text */ + border-radius: 5px; /* Add border radius to image */ + } + + .queue-song-info { + h3 { + margin: 0; /* Remove default margin for heading */ + color: #fff; /* Adjust text color for song */ + } + + p { + margin: 0; /* Remove default margin for paragraph */ + color: #aaa; /* Adjust text color for artist */ + } + } + } + + button { + background: none; + border: none; + color: #fff; + cursor: pointer; + margin-left: auto; + } + + p { + color: #fff; + font-weight: bold; + margin-left: auto; + background: none; + border: none; + } + } + } +} + \ No newline at end of file diff --git a/style/theme.scss b/style/theme.scss index 3e5d81c..2d80285 100644 --- a/style/theme.scss +++ b/style/theme.scss @@ -6,6 +6,7 @@ $controls-click-color: #909090; $play-bar-background-color: #212121; $play-grad-start: #0a0533; $play-grad-end: $accent-color; +$queue-background-color: $play-bar-background-color; $auth-inputs: #796dd4; $auth-containers: white;