Merge pull request 'Create component for displaying music on dashboard' (#105) from 33-create-component-for-displaying-music-on-dashboard into main
Reviewed-on: LibreTunes/LibreTunes#105
This commit is contained in:
commit
36e7a5827b
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
|
39
src/albumdata.rs
Normal file
39
src/albumdata.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use crate::models::Artist;
|
||||
use crate::components::dashboard_tile::DashboardTile;
|
||||
|
||||
use time::Date;
|
||||
|
||||
/// Holds information about an album
|
||||
///
|
||||
/// Intended to be used in the front-end
|
||||
pub struct AlbumData {
|
||||
/// Album id
|
||||
pub id: i32,
|
||||
/// Album title
|
||||
pub title: String,
|
||||
/// Album artists
|
||||
pub artists: Vec<Artist>,
|
||||
/// Album release date
|
||||
pub release_date: Option<Date>,
|
||||
/// Path to album image, relative to the root of the web server.
|
||||
/// For example, `"/assets/images/Album.jpg"`
|
||||
pub image_path: String,
|
||||
}
|
||||
|
||||
impl DashboardTile for AlbumData {
|
||||
fn image_path(&self) -> String {
|
||||
self.image_path.clone()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
self.title.clone()
|
||||
}
|
||||
|
||||
fn link(&self) -> String {
|
||||
format!("/album/{}", self.id)
|
||||
}
|
||||
|
||||
fn description(&self) -> Option<String> {
|
||||
Some(format!("Album • {}", Artist::display_list(&self.artists)))
|
||||
}
|
||||
}
|
32
src/artistdata.rs
Normal file
32
src/artistdata.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use crate::components::dashboard_tile::DashboardTile;
|
||||
|
||||
/// Holds information about an artist
|
||||
///
|
||||
/// Intended to be used in the front-end
|
||||
pub struct ArtistData {
|
||||
/// Artist id
|
||||
pub id: i32,
|
||||
/// Artist name
|
||||
pub name: String,
|
||||
/// Path to artist image, relative to the root of the web server.
|
||||
/// For example, `"/assets/images/Artist.jpg"`
|
||||
pub image_path: String,
|
||||
}
|
||||
|
||||
impl DashboardTile for ArtistData {
|
||||
fn image_path(&self) -> String {
|
||||
self.image_path.clone()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn link(&self) -> String {
|
||||
format!("/artist/{}", self.id)
|
||||
}
|
||||
|
||||
fn description(&self) -> Option<String> {
|
||||
Some("Artist".to_string())
|
||||
}
|
||||
}
|
@ -2,4 +2,6 @@ pub mod sidebar;
|
||||
pub mod dashboard;
|
||||
pub mod search;
|
||||
pub mod personal;
|
||||
pub mod dashboard_tile;
|
||||
pub mod dashboard_row;
|
||||
pub mod upload;
|
||||
|
118
src/components/dashboard_row.rs
Normal file
118
src/components/dashboard_row.rs
Normal file
@ -0,0 +1,118 @@
|
||||
use leptos::html::Ul;
|
||||
use leptos::leptos_dom::*;
|
||||
use leptos::*;
|
||||
use leptos_use::{use_element_size, UseElementSizeReturn, use_scroll, UseScrollReturn};
|
||||
use crate::components::dashboard_tile::DashboardTile;
|
||||
use leptos_icons::*;
|
||||
|
||||
/// A row of dashboard tiles, with a title
|
||||
pub struct DashboardRow {
|
||||
pub title: String,
|
||||
pub tiles: Vec<Box<dyn DashboardTile>>,
|
||||
}
|
||||
|
||||
impl DashboardRow {
|
||||
pub fn new(title: String, tiles: Vec<Box<dyn DashboardTile>>) -> Self {
|
||||
Self {
|
||||
title,
|
||||
tiles,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoView for DashboardRow {
|
||||
fn into_view(self) -> View {
|
||||
let list_ref = create_node_ref::<Ul>();
|
||||
|
||||
// Scroll functions attempt to align the left edge of the scroll area with the left edge of a tile
|
||||
// This is done by scrolling to the nearest multiple of the tile width, plus some for padding
|
||||
|
||||
let scroll_left = move |_| {
|
||||
if let Some(scroll_element) = list_ref.get_untracked() {
|
||||
let client_width = scroll_element.client_width() as f64;
|
||||
let current_pos = scroll_element.scroll_left() as f64;
|
||||
let desired_pos = current_pos - client_width;
|
||||
|
||||
if let Some(first_tile) = scroll_element.first_element_child() {
|
||||
let tile_width = first_tile.client_width() as f64;
|
||||
let scroll_pos = desired_pos + (tile_width - (desired_pos % tile_width));
|
||||
scroll_element.scroll_to_with_x_and_y(scroll_pos, 0.0);
|
||||
} else {
|
||||
warn!("Could not get first tile to scroll left");
|
||||
// Fall back to scrolling by the client width if we can't get the tile width
|
||||
scroll_element.scroll_to_with_x_and_y(desired_pos, 0.0);
|
||||
}
|
||||
} else {
|
||||
warn!("Could not get scroll element to scroll left");
|
||||
}
|
||||
};
|
||||
|
||||
let scroll_right = move |_| {
|
||||
if let Some(scroll_element) = list_ref.get_untracked() {
|
||||
let client_width = scroll_element.client_width() as f64;
|
||||
let current_pos = scroll_element.scroll_left() as f64;
|
||||
let desired_pos = current_pos + client_width;
|
||||
|
||||
if let Some(first_tile) = scroll_element.first_element_child() {
|
||||
let tile_width = first_tile.client_width() as f64;
|
||||
let scroll_pos = desired_pos - (desired_pos % tile_width);
|
||||
scroll_element.scroll_to_with_x_and_y(scroll_pos, 0.0);
|
||||
} else {
|
||||
warn!("Could not get first tile to scroll right");
|
||||
// Fall back to scrolling by the client width if we can't get the tile width
|
||||
scroll_element.scroll_to_with_x_and_y(desired_pos, 0.0);
|
||||
}
|
||||
} else {
|
||||
warn!("Could not get scroll element to scroll right");
|
||||
}
|
||||
};
|
||||
|
||||
let UseElementSizeReturn { width: scroll_element_width, .. } = use_element_size(list_ref);
|
||||
let UseScrollReturn { x: scroll_x, .. } = use_scroll(list_ref);
|
||||
|
||||
let scroll_right_hidden = Signal::derive(move || {
|
||||
if let Some(scroll_element) = list_ref.get() {
|
||||
if scroll_element.scroll_width() as f64 - scroll_element_width.get() <= scroll_x.get() {
|
||||
"visibility: hidden"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
});
|
||||
|
||||
let scroll_left_hidden = Signal::derive(move || {
|
||||
if scroll_x.get() <= 0.0 {
|
||||
"visibility: hidden"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
});
|
||||
|
||||
view! {
|
||||
<div class="dashboard-tile-row">
|
||||
<div class="dashboard-tile-row-title-row">
|
||||
<h2>{self.title}</h2>
|
||||
<div class="dashboard-tile-row-scroll-btn">
|
||||
<button on:click=scroll_left tabindex=-1 style=scroll_left_hidden>
|
||||
<Icon class="dashboard-tile-row-scroll" icon=icondata::FiChevronLeft />
|
||||
</button>
|
||||
<button on:click=scroll_right tabindex=-1 style=scroll_right_hidden>
|
||||
<Icon class="dashboard-tile-row-scroll" icon=icondata::FiChevronRight />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul _ref={list_ref}>
|
||||
{self.tiles.into_iter().map(|tile_info| {
|
||||
view! {
|
||||
<li>
|
||||
{ tile_info.into_view() }
|
||||
</li>
|
||||
}
|
||||
}).collect::<Vec<_>>()}
|
||||
</ul>
|
||||
</div>
|
||||
}.into_view()
|
||||
}
|
||||
}
|
27
src/components/dashboard_tile.rs
Normal file
27
src/components/dashboard_tile.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use leptos::leptos_dom::*;
|
||||
use leptos::*;
|
||||
|
||||
pub trait DashboardTile {
|
||||
fn image_path(&self) -> String;
|
||||
fn title(&self) -> String;
|
||||
fn link(&self) -> String;
|
||||
fn description(&self) -> Option<String> { None }
|
||||
}
|
||||
|
||||
impl IntoView for &dyn DashboardTile {
|
||||
fn into_view(self) -> View {
|
||||
let link = self.link();
|
||||
|
||||
view! {
|
||||
<div class="dashboard-tile">
|
||||
<a href={link}>
|
||||
<img src={self.image_path()} alt="dashboard-tile" />
|
||||
<p class="dashboard-tile-title">{self.title()}</p>
|
||||
<p class="dashboard-tile-description">
|
||||
{self.description().unwrap_or_default()}
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
}.into_view()
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
pub mod app;
|
||||
pub mod auth;
|
||||
pub mod songdata;
|
||||
pub mod albumdata;
|
||||
pub mod artistdata;
|
||||
pub mod playstatus;
|
||||
pub mod playbar;
|
||||
pub mod database;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::models::{Album, Artist, Song};
|
||||
use crate::components::dashboard_tile::DashboardTile;
|
||||
|
||||
use time::Date;
|
||||
|
||||
@ -60,3 +61,21 @@ impl TryInto<Song> for SongData {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DashboardTile for SongData {
|
||||
fn image_path(&self) -> String {
|
||||
self.image_path.clone()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
self.title.clone()
|
||||
}
|
||||
|
||||
fn link(&self) -> String {
|
||||
format!("/song/{}", self.id)
|
||||
}
|
||||
|
||||
fn description(&self) -> Option<String> {
|
||||
Some(format!("Song • {}", Artist::display_list(&self.artists)))
|
||||
}
|
||||
}
|
||||
|
45
style/dashboard_row.scss
Normal file
45
style/dashboard_row.scss
Normal file
@ -0,0 +1,45 @@
|
||||
.dashboard-tile-row {
|
||||
.dashboard-tile-row-title-row {
|
||||
display: flex;
|
||||
|
||||
.dashboard-tile-row-scroll-btn {
|
||||
margin-left: auto;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
|
||||
button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
|
||||
.dashboard-tile-row-scroll {
|
||||
color: $text-controls-color;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.dashboard-tile-row-scroll:hover {
|
||||
color: $controls-hover-color;
|
||||
}
|
||||
|
||||
.dashboard-tile-row-scroll:active {
|
||||
color: $controls-click-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
overflow-x: hidden;
|
||||
scroll-behavior: smooth;
|
||||
margin-left: 40px;
|
||||
padding-inline-start: 0;
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
-webkit-mask-image: linear-gradient(90deg, #000000 95%, transparent);
|
||||
mask-image: linear-gradient(90deg, #000000 95%, transparent);
|
||||
}
|
||||
}
|
28
style/dashboard_tile.scss
Normal file
28
style/dashboard_tile.scss
Normal file
@ -0,0 +1,28 @@
|
||||
.dashboard-tile {
|
||||
img {
|
||||
width: $dashboard-tile-size;
|
||||
height: $dashboard-tile-size;
|
||||
border-radius: 7px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $text-controls-color;
|
||||
}
|
||||
|
||||
p.dashboard-tile-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
p.dashboard-tile-description {
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
margin-right: 10px;
|
||||
}
|
@ -8,6 +8,8 @@
|
||||
@import 'home.scss';
|
||||
@import 'search.scss';
|
||||
@import 'personal.scss';
|
||||
@import 'dashboard_tile.scss';
|
||||
@import 'dashboard_row.scss';
|
||||
@import 'upload.scss';
|
||||
|
||||
body {
|
||||
|
@ -14,3 +14,5 @@ $queue-background-color: $play-bar-background-color;
|
||||
|
||||
$auth-inputs: #796dd4;
|
||||
$auth-containers: white;
|
||||
|
||||
$dashboard-tile-size: 200px;
|
||||
|
Loading…
x
Reference in New Issue
Block a user