Compare commits
9 Commits
main
...
148-create
Author | SHA1 | Date | |
---|---|---|---|
d73a114d35 | |||
f8a28ca75c | |||
0afdbf2ad5 | |||
03889d4efe | |||
f6fb92e361 | |||
00f4b2e28f | |||
340129a859 | |||
7828992e80 | |||
9600e8c442 |
@ -1,10 +1,152 @@
|
|||||||
|
use html::Li;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
use crate::search::search;
|
||||||
|
use crate::song::Song;
|
||||||
|
use crate::models::Album;
|
||||||
|
use crate::models::Artist;
|
||||||
|
use crate::models::Song;
|
||||||
|
use crate::util::state::GlobalState;
|
||||||
|
use leptos::ev::*;
|
||||||
|
use leptos::leptos_dom::*;
|
||||||
|
use leptos_icons::*;
|
||||||
|
|
||||||
|
const OPTIONS_BTN_SIZE: &str = "2.5rem";
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Search() -> impl IntoView {
|
pub fn Search() -> impl IntoView {
|
||||||
|
|
||||||
|
let status = GlobalState::play_status();
|
||||||
|
|
||||||
|
let search_query = create_rw_signal(String::new());
|
||||||
|
let search_results = create_rw_signal((Vec::<(Album, f32)>::new(), Vec::<(Artist, f32)>::new(), Vec::<(Song, f32)>::new()));
|
||||||
|
let search_limit = 10;
|
||||||
|
|
||||||
|
let on_input = move |e: Event| {
|
||||||
|
search_query.set(event_target_value(&e));
|
||||||
|
|
||||||
|
log!("Search Query: {:?}", search_query.get_untracked());
|
||||||
|
|
||||||
|
spawn_local(async move {
|
||||||
|
log!("Searching for: {:?}", search_query.get_untracked());
|
||||||
|
let results = search(search_query.get_untracked(), search_limit).await;
|
||||||
|
|
||||||
|
match results {
|
||||||
|
Ok((albums, artists, songs)) => {
|
||||||
|
search_results.set((albums, artists, songs));
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log!("Error searching albums: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let on_disabled = move |_e: FocusEvent| {
|
||||||
|
status.update(|status| {
|
||||||
|
status.search_active = false;
|
||||||
|
});
|
||||||
|
log!("Search Bar Disabled");
|
||||||
|
};
|
||||||
|
|
||||||
|
let on_enabled = move |_e: FocusEvent| {
|
||||||
|
status.update(|status| {
|
||||||
|
status.search_active = true;
|
||||||
|
});
|
||||||
|
log!("Search Bar Enabled");
|
||||||
|
};
|
||||||
|
|
||||||
|
let prevent_focus = move |e: MouseEvent| {
|
||||||
|
e.prevent_default();
|
||||||
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="search-container home-component">
|
<div class="search-container home-component">
|
||||||
<h1>Searching...</h1>
|
<div class="search-bar">
|
||||||
</div>
|
<input type="search" placeholder="Search" on:input=on_input on:blur=on_disabled on:focus=on_enabled/>
|
||||||
}
|
</div>
|
||||||
|
<div class="search-results">
|
||||||
|
// Display 3 columns of search results: songs, albums, and artists
|
||||||
|
<ul class="search-result-list">
|
||||||
|
{move || search_results.with(|results| -> Vec<leptos::HtmlElement<Li>> {
|
||||||
|
let mut song_list = Vec::new();
|
||||||
|
for (song, _) in results.2.clone() {
|
||||||
|
song_list.push(view! {
|
||||||
|
<li class="search-result">
|
||||||
|
<div class="result-container">
|
||||||
|
<Song song_image_path=match song.image_path.clone() {
|
||||||
|
Some(path) => path,
|
||||||
|
None => "".to_string()
|
||||||
|
} song_title=song.title.clone() song_artist="".to_string() />
|
||||||
|
<div class="right-side-result">
|
||||||
|
<div class="search-item-type">
|
||||||
|
"(Song)"
|
||||||
|
</div>
|
||||||
|
<button class="search-result-options" on:mousedown=prevent_focus>
|
||||||
|
<Icon class="search-result-options-icon" width=OPTIONS_BTN_SIZE height=OPTIONS_BTN_SIZE icon=icondata::BsThreeDotsVertical />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
});
|
||||||
|
}
|
||||||
|
song_list
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
<ul class="search-result-list">
|
||||||
|
{move || search_results.with(|results| -> Vec<leptos::HtmlElement<Li>> {
|
||||||
|
let mut album_list = Vec::new();
|
||||||
|
for (album, _) in results.0.clone() {
|
||||||
|
album_list.push(view! {
|
||||||
|
<li class="search-result">
|
||||||
|
<div class="result-container">
|
||||||
|
<div class="search-result-album">
|
||||||
|
{album.title.clone()}
|
||||||
|
{match album.release_date {
|
||||||
|
Some(date) => format!(" ({})", date),
|
||||||
|
None => "".to_string()
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div class="right-side-result">
|
||||||
|
<div class="search-item-type">
|
||||||
|
"(Album)"
|
||||||
|
</div>
|
||||||
|
<button class="search-result-options" on:mousedown=prevent_focus>
|
||||||
|
<Icon class="search-result-options-icon" width=OPTIONS_BTN_SIZE height=OPTIONS_BTN_SIZE icon=icondata::BsThreeDotsVertical />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
});
|
||||||
|
}
|
||||||
|
album_list
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
<ul class="search-result-list">
|
||||||
|
{move || search_results.with(|results| -> Vec<leptos::HtmlElement<Li>> {
|
||||||
|
let mut artist_list = Vec::new();
|
||||||
|
for (artist, _) in results.1.clone() {
|
||||||
|
artist_list.push(view! {
|
||||||
|
<li class="search-result">
|
||||||
|
<div class="result-container">
|
||||||
|
<div class="search-result-artist">
|
||||||
|
{artist.name.clone()}
|
||||||
|
</div>
|
||||||
|
<div class="right-side-result">
|
||||||
|
<div class="search-item-type">
|
||||||
|
"(Artist)"
|
||||||
|
</div>
|
||||||
|
<button class="search-result-options" on:mousedown=prevent_focus>
|
||||||
|
<Icon class="search-result-options-icon" width=OPTIONS_BTN_SIZE height=OPTIONS_BTN_SIZE icon=icondata::BsThreeDotsVertical />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
});
|
||||||
|
}
|
||||||
|
artist_list
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
@ -124,7 +124,7 @@ pub fn Upload(open: RwSignal<bool>) -> impl IntoView {
|
|||||||
>
|
>
|
||||||
<ul class="artist_results search-results">
|
<ul class="artist_results search-results">
|
||||||
{
|
{
|
||||||
move || filtered_artists.get().iter().enumerate().map(|(_index,filtered_artist)| view! {
|
move || filtered_artists.get().iter().enumerate().map(|(_index,(filtered_artist, _score))| view! {
|
||||||
<Artist artist=filtered_artist.clone() artists=artists set_artists=set_artists set_filtered=set_filtered_artists/>
|
<Artist artist=filtered_artist.clone() artists=artists set_artists=set_artists set_filtered=set_filtered_artists/>
|
||||||
}).collect::<Vec<_>>()
|
}).collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
@ -142,7 +142,7 @@ pub fn Upload(open: RwSignal<bool>) -> impl IntoView {
|
|||||||
>
|
>
|
||||||
<ul class="album_results search-results">
|
<ul class="album_results search-results">
|
||||||
{
|
{
|
||||||
move || filtered_albums.get().iter().enumerate().map(|(_index,filtered_album)| view! {
|
move || filtered_albums.get().iter().enumerate().map(|(_index,(filtered_album, _score))| view! {
|
||||||
<Album album=filtered_album.clone() _albums=albums set_albums=set_albums set_filtered=set_filtered_albums/>
|
<Album album=filtered_album.clone() _albums=albums set_albums=set_albums set_filtered=set_filtered_albums/>
|
||||||
}).collect::<Vec<_>>()
|
}).collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
@ -182,15 +182,15 @@ pub fn Upload(open: RwSignal<bool>) -> impl IntoView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Artist(artist: Artist, artists: ReadSignal<String>, set_artists: WriteSignal<String>, set_filtered: WriteSignal<Vec<Artist>>) -> impl IntoView {
|
pub fn Artist(artist: Artist, artists: ReadSignal<String>, set_artists: WriteSignal<String>, set_filtered: WriteSignal<Vec<(Artist, f32)>>) -> impl IntoView {
|
||||||
// Converts artist name to artist id and adds it to the artist input
|
// Converts artist name to artist id and adds it to the artist input
|
||||||
let add_artist = move |_| {
|
let add_artist = move |_| {
|
||||||
//Create an empty string to hold previous artist ids
|
//Create an empty string to hold previous artist ids
|
||||||
let mut s: String = String::from("");
|
let mut s: String = String::from("");
|
||||||
//Get the current artist input
|
//Get the current artist input
|
||||||
let all_artirts: String = artists.get();
|
let all_artists: String = artists.get();
|
||||||
//Split the input into a vector of artists separated by commas
|
//Split the input into a vector of artists separated by commas
|
||||||
let mut ids: Vec<&str> = all_artirts.split(",").collect();
|
let mut ids: Vec<&str> = all_artists.split(",").collect();
|
||||||
//If there is only one artist in the input, get their id equivalent and add it to the string
|
//If there is only one artist in the input, get their id equivalent and add it to the string
|
||||||
if ids.len() == 1 {
|
if ids.len() == 1 {
|
||||||
let value_str = match artist.id.clone() {
|
let value_str = match artist.id.clone() {
|
||||||
@ -227,7 +227,7 @@ pub fn Artist(artist: Artist, artists: ReadSignal<String>, set_artists: WriteSig
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Album(album: Album, _albums: ReadSignal<String>, set_albums: WriteSignal<String>, set_filtered: WriteSignal<Vec<Album>>) -> impl IntoView {
|
pub fn Album(album: Album, _albums: ReadSignal<String>, set_albums: WriteSignal<String>, set_filtered: WriteSignal<Vec<(Album, f32)>>) -> impl IntoView {
|
||||||
//Converts album title to album id to upload a song
|
//Converts album title to album id to upload a song
|
||||||
let add_album = move |_| {
|
let add_album = move |_| {
|
||||||
let value_str = match album.id.clone() {
|
let value_str = match album.id.clone() {
|
||||||
|
@ -531,7 +531,7 @@ impl Album {
|
|||||||
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))]
|
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))]
|
||||||
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::songs))]
|
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::songs))]
|
||||||
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
|
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Song {
|
pub struct Song {
|
||||||
/// A unique id for the song
|
/// A unique id for the song
|
||||||
#[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))]
|
#[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))]
|
||||||
|
@ -489,7 +489,7 @@ pub fn PlayBar() -> impl IntoView {
|
|||||||
|
|
||||||
// Listen for key down events -- arrow keys don't seem to trigger key press events
|
// 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| {
|
let _arrow_key_handle = window_event_listener(ev::keydown, move |e: ev::KeyboardEvent| {
|
||||||
if e.key() == "ArrowRight" {
|
if e.key() == "ArrowRight" && status.with_untracked(|status| status.search_active) == false {
|
||||||
e.prevent_default();
|
e.prevent_default();
|
||||||
log!("Right arrow key pressed, skipping forward by {} seconds", ARROW_KEY_SKIP_TIME);
|
log!("Right arrow key pressed, skipping forward by {} seconds", ARROW_KEY_SKIP_TIME);
|
||||||
|
|
||||||
@ -502,7 +502,7 @@ pub fn PlayBar() -> impl IntoView {
|
|||||||
error!("Unable to skip forward: Unable to get current duration");
|
error!("Unable to skip forward: Unable to get current duration");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if e.key() == "ArrowLeft" {
|
} else if e.key() == "ArrowLeft" && status.with_untracked(|status| status.search_active) == false {
|
||||||
e.prevent_default();
|
e.prevent_default();
|
||||||
log!("Left arrow key pressed, skipping backward by {} seconds", ARROW_KEY_SKIP_TIME);
|
log!("Left arrow key pressed, skipping backward by {} seconds", ARROW_KEY_SKIP_TIME);
|
||||||
|
|
||||||
@ -519,7 +519,7 @@ pub fn PlayBar() -> impl IntoView {
|
|||||||
|
|
||||||
// Listen for space bar presses to play/pause
|
// Listen for space bar presses to play/pause
|
||||||
let _space_bar_handle = window_event_listener(ev::keypress, move |e: ev::KeyboardEvent| {
|
let _space_bar_handle = window_event_listener(ev::keypress, move |e: ev::KeyboardEvent| {
|
||||||
if e.key() == " " {
|
if e.key() == " " && status.with_untracked(|status| status.search_active) == false {
|
||||||
e.prevent_default();
|
e.prevent_default();
|
||||||
log!("Space bar pressed, toggling play/pause");
|
log!("Space bar pressed, toggling play/pause");
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ pub struct PlayStatus {
|
|||||||
pub playing: bool,
|
pub playing: bool,
|
||||||
/// Whether or not the queue is open
|
/// Whether or not the queue is open
|
||||||
pub queue_open: bool,
|
pub queue_open: bool,
|
||||||
|
/// Whether or not the search bar is active (useful for knowing when spacebar to play/pause, etc should be disabled)
|
||||||
|
pub search_active: bool,
|
||||||
/// A reference to the HTML audio element
|
/// A reference to the HTML audio element
|
||||||
pub audio_player: Option<NodeRef<Audio>>,
|
pub audio_player: Option<NodeRef<Audio>>,
|
||||||
/// A queue of songs that have been played, ordered from oldest to newest
|
/// A queue of songs that have been played, ordered from oldest to newest
|
||||||
@ -56,6 +58,7 @@ impl Default for PlayStatus {
|
|||||||
Self {
|
Self {
|
||||||
playing: false,
|
playing: false,
|
||||||
queue_open: false,
|
queue_open: false,
|
||||||
|
search_active: false,
|
||||||
audio_player: None,
|
audio_player: None,
|
||||||
history: VecDeque::new(),
|
history: VecDeque::new(),
|
||||||
queue: VecDeque::new(),
|
queue: VecDeque::new(),
|
||||||
|
@ -39,10 +39,11 @@ if #[cfg(feature = "ssr")] {
|
|||||||
/// # Returns
|
/// # Returns
|
||||||
/// A Result containing a vector of albums if the search was successful, or an error if the search failed
|
/// A Result containing a vector of albums if the search was successful, or an error if the search failed
|
||||||
#[server(endpoint = "search_albums")]
|
#[server(endpoint = "search_albums")]
|
||||||
pub async fn search_albums(query: String, limit: i64) -> Result<Vec<Album>, ServerFnError> {
|
pub async fn search_albums(query: String, limit: i64) -> Result<Vec<(Album, f32)>, ServerFnError> {
|
||||||
use crate::schema::albums::dsl::*;
|
use crate::schema::albums::dsl::*;
|
||||||
|
|
||||||
Ok(albums
|
Ok(albums
|
||||||
|
.select((albums::all_columns(), trgm_distance(title, query.clone())))
|
||||||
.filter(trgm_similar(title, query.clone()))
|
.filter(trgm_similar(title, query.clone()))
|
||||||
.order_by(trgm_distance(title, query))
|
.order_by(trgm_distance(title, query))
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
@ -58,10 +59,11 @@ pub async fn search_albums(query: String, limit: i64) -> Result<Vec<Album>, Serv
|
|||||||
/// # Returns
|
/// # Returns
|
||||||
/// A Result containing a vector of artists if the search was successful, or an error if the search failed
|
/// A Result containing a vector of artists if the search was successful, or an error if the search failed
|
||||||
#[server(endpoint = "search_artists")]
|
#[server(endpoint = "search_artists")]
|
||||||
pub async fn search_artists(query: String, limit: i64) -> Result<Vec<Artist>, ServerFnError> {
|
pub async fn search_artists(query: String, limit: i64) -> Result<Vec<(Artist, f32)>, ServerFnError> {
|
||||||
use crate::schema::artists::dsl::*;
|
use crate::schema::artists::dsl::*;
|
||||||
|
|
||||||
Ok(artists
|
Ok(artists
|
||||||
|
.select((artists::all_columns(), trgm_distance(name, query.clone())))
|
||||||
.filter(trgm_similar(name, query.clone()))
|
.filter(trgm_similar(name, query.clone()))
|
||||||
.order_by(trgm_distance(name, query))
|
.order_by(trgm_distance(name, query))
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
@ -77,10 +79,11 @@ pub async fn search_artists(query: String, limit: i64) -> Result<Vec<Artist>, Se
|
|||||||
/// # Returns
|
/// # Returns
|
||||||
/// A Result containing a vector of songs if the search was successful, or an error if the search failed
|
/// A Result containing a vector of songs if the search was successful, or an error if the search failed
|
||||||
#[server(endpoint = "search_songs")]
|
#[server(endpoint = "search_songs")]
|
||||||
pub async fn search_songs(query: String, limit: i64) -> Result<Vec<Song>, ServerFnError> {
|
pub async fn search_songs(query: String, limit: i64) -> Result<Vec<(Song, f32)>, ServerFnError> {
|
||||||
use crate::schema::songs::dsl::*;
|
use crate::schema::songs::dsl::*;
|
||||||
|
|
||||||
Ok(songs
|
Ok(songs
|
||||||
|
.select((songs::all_columns(), trgm_distance(title, query.clone())))
|
||||||
.filter(trgm_similar(title, query.clone()))
|
.filter(trgm_similar(title, query.clone()))
|
||||||
.order_by(trgm_distance(title, query))
|
.order_by(trgm_distance(title, query))
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
@ -95,9 +98,10 @@ pub async fn search_songs(query: String, limit: i64) -> Result<Vec<Song>, Server
|
|||||||
/// `limit` - The maximum number of results to return for each type
|
/// `limit` - The maximum number of results to return for each type
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A Result containing a tuple of vectors of albums, artists, and songs if the search was successful,
|
/// A Result containing a tuple of vectors of albums, artists, and songs,
|
||||||
|
/// along with respective similarity scores, if the search was successful.
|
||||||
#[server(endpoint = "search")]
|
#[server(endpoint = "search")]
|
||||||
pub async fn search(query: String, limit: i64) -> Result<(Vec<Album>, Vec<Artist>, Vec<Song>), ServerFnError> {
|
pub async fn search(query: String, limit: i64) -> Result<(Vec<(Album, f32)>, Vec<(Artist, f32)>, Vec<(Song, f32)>), ServerFnError> {
|
||||||
let albums = search_albums(query.clone(), limit);
|
let albums = search_albums(query.clone(), limit);
|
||||||
let artists = search_artists(query.clone(), limit);
|
let artists = search_artists(query.clone(), limit);
|
||||||
let songs = search_songs(query.clone(), limit);
|
let songs = search_songs(query.clone(), limit);
|
||||||
|
@ -3,9 +3,9 @@ use leptos::*;
|
|||||||
#[component]
|
#[component]
|
||||||
pub fn Song(song_image_path: String, song_title: String, song_artist: String) -> impl IntoView {
|
pub fn Song(song_image_path: String, song_title: String, song_artist: String) -> impl IntoView {
|
||||||
view!{
|
view!{
|
||||||
<div class="queue-song">
|
<div class="song">
|
||||||
<img src={song_image_path} alt={song_title.clone()} />
|
<img src={song_image_path} alt={song_title.clone()} />
|
||||||
<div class="queue-song-info">
|
<div class="song-info">
|
||||||
<h3>{song_title}</h3>
|
<h3>{song_title}</h3>
|
||||||
<p>{song_artist}</p>
|
<p>{song_artist}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,10 +5,12 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
height: calc(100% - 78.9px); /* Adjust height to fit the queue */
|
height: calc(100% - 87px); /* Adjust height to fit the queue */
|
||||||
background-color: #424242; /* Queue background color */
|
background-color: #424242; /* Queue background color */
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||||
overflow-y: auto; /* Add scroll bar when queue is too long */
|
overflow-y: auto; /* Add scroll bar when queue is too long */
|
||||||
|
margin: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
.queue-header {
|
.queue-header {
|
||||||
background-color: #333; /* Header background color */
|
background-color: #333; /* Header background color */
|
||||||
@ -29,31 +31,6 @@
|
|||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
padding-bottom: 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 {
|
button {
|
||||||
background: none;
|
background: none;
|
||||||
|
@ -1,4 +1,92 @@
|
|||||||
@import "theme.scss";
|
@import "theme.scss";
|
||||||
|
|
||||||
.search-container {
|
.search-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
input[type="search"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
border: 1px solid $search-border;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: $accent-color;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-results {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
.search-result-list {
|
||||||
|
flex: 1;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.search-result {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid $search-border;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: $search-background;
|
||||||
|
transition: box-shadow 0.2s;
|
||||||
|
margin: 0.2rem;
|
||||||
|
min-height: 6rem;
|
||||||
|
|
||||||
|
.result-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.search-result-album,
|
||||||
|
.search-result-artist {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-item-type {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: $text-controls-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-side-result {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
.search-result-options {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.search-result-options-icon {
|
||||||
|
fill: $text-controls-color;
|
||||||
|
transition: fill 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
fill: $controls-hover-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
fill: $controls-click-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
22
style/song.scss
Normal file
22
style/song.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
.song {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
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 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.song-info {
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #fff; /* Adjust text color for song */
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
color: #aaa; /* Adjust text color for artist */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,8 @@ $play-bar-background-color: #212121;
|
|||||||
$play-grad-start: #0a0533;
|
$play-grad-start: #0a0533;
|
||||||
$play-grad-end: $accent-color;
|
$play-grad-end: $accent-color;
|
||||||
$queue-background-color: $play-bar-background-color;
|
$queue-background-color: $play-bar-background-color;
|
||||||
|
$search-background: $play-bar-background-color;
|
||||||
|
$search-border: #3f3f3f;
|
||||||
|
|
||||||
$auth-inputs: #796dd4;
|
$auth-inputs: #796dd4;
|
||||||
$auth-containers: white;
|
$auth-containers: white;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user