Compare commits
8 Commits
b47f43f6e0
...
db18298832
| Author | SHA1 | Date | |
|---|---|---|---|
|
db18298832
|
|||
|
5acdaf9d55
|
|||
|
28effd4024
|
|||
|
636c811e24
|
|||
|
c5654fc9f7
|
|||
|
dd13cdd2cc
|
|||
|
ff8f2ecfca
|
|||
|
067ce69c4b
|
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "libretunes"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
build = "src/build.rs"
|
||||
|
||||
[profile.dev]
|
||||
|
||||
@@ -201,7 +201,7 @@ pub async fn create_playlist(
|
||||
user: backend::User,
|
||||
db_conn: &mut PgPooledConn,
|
||||
) -> BackendResult<()> {
|
||||
use image_convert::{to_webp, ImageResource, WEBPConfig};
|
||||
use image_convert::{ImageResource, WEBPConfig, to_webp};
|
||||
|
||||
// Safe to unwrap - "On the server side, this always returns Some(_). On the client side, always returns None."
|
||||
let mut data = data.into_inner().unwrap();
|
||||
@@ -278,7 +278,7 @@ pub async fn edit_playlist_image(
|
||||
state: BackendState,
|
||||
db_conn: &mut PgPooledConn,
|
||||
) -> BackendResult<()> {
|
||||
use image_convert::{to_webp, ImageResource, WEBPConfig};
|
||||
use image_convert::{ImageResource, WEBPConfig, to_webp};
|
||||
|
||||
// Safe to unwrap - "On the server side, this always returns Some(_). On the client side, always returns None."
|
||||
let mut data = data.into_inner().unwrap();
|
||||
|
||||
@@ -37,7 +37,7 @@ pub async fn upload_picture(
|
||||
}
|
||||
|
||||
// Read the image, and convert it to webp
|
||||
use image_convert::{to_webp, ImageResource, WEBPConfig};
|
||||
use image_convert::{ImageResource, WEBPConfig, to_webp};
|
||||
|
||||
let bytes = field
|
||||
.bytes()
|
||||
|
||||
@@ -196,6 +196,91 @@ pub async fn get_song_by_id(
|
||||
}
|
||||
}
|
||||
|
||||
#[api_fn(endpoint = "songs/get_many")]
|
||||
pub async fn get_songs_by_id(
|
||||
song_ids: Vec<i32>,
|
||||
user: backend::User,
|
||||
db_conn: &mut PgPooledConn,
|
||||
) -> BackendResult<Vec<Option<frontend::Song>>> {
|
||||
let song_parts: Vec<(
|
||||
backend::Song,
|
||||
Option<backend::Album>,
|
||||
Option<backend::Artist>,
|
||||
Option<(i32, i32)>,
|
||||
Option<(i32, i32)>,
|
||||
)> = songs::table
|
||||
.filter(songs::id.eq_any(song_ids.clone()))
|
||||
.left_join(albums::table.on(songs::album_id.eq(albums::id.nullable())))
|
||||
.left_join(
|
||||
song_artists::table
|
||||
.inner_join(artists::table)
|
||||
.on(songs::id.eq(song_artists::song_id)),
|
||||
)
|
||||
.left_join(
|
||||
song_likes::table.on(songs::id
|
||||
.eq(song_likes::song_id)
|
||||
.and(song_likes::user_id.eq(user.id))),
|
||||
)
|
||||
.left_join(
|
||||
song_dislikes::table.on(songs::id
|
||||
.eq(song_dislikes::song_id)
|
||||
.and(song_dislikes::user_id.eq(user.id))),
|
||||
)
|
||||
.select((
|
||||
songs::all_columns,
|
||||
albums::all_columns.nullable(),
|
||||
artists::all_columns.nullable(),
|
||||
song_likes::all_columns.nullable(),
|
||||
song_dislikes::all_columns.nullable(),
|
||||
))
|
||||
.load(db_conn)
|
||||
.context("Error loading song from database")?;
|
||||
|
||||
let mut songs: HashMap<i32, frontend::Song> = HashMap::new();
|
||||
|
||||
for (song, album, artist, like, dislike) in song_parts {
|
||||
if let Some(last_song) = songs.get_mut(&song.id) {
|
||||
if let Some(artist) = artist {
|
||||
last_song.artists.push(artist);
|
||||
}
|
||||
} else {
|
||||
let image_path = song.image_web_path_or_placeholder(album.as_ref());
|
||||
|
||||
let song_path = song
|
||||
.storage_path
|
||||
.to_web_path(AssetType::Audio)
|
||||
.context(format!(
|
||||
"Error converting audio path to web path for song {} (id: {})",
|
||||
song.title.clone(),
|
||||
song.id
|
||||
))?;
|
||||
|
||||
let new_song = frontend::Song {
|
||||
id: song.id,
|
||||
title: song.title.clone(),
|
||||
artists: artist.map(|artist| vec![artist]).unwrap_or(vec![]),
|
||||
album: album.clone(),
|
||||
track: song.track,
|
||||
duration: song.duration,
|
||||
release_date: song.release_date,
|
||||
song_path,
|
||||
image_path,
|
||||
like_dislike: Some((like.is_some(), dislike.is_some())),
|
||||
added_date: song.added_date,
|
||||
};
|
||||
|
||||
songs.insert(song.id, new_song);
|
||||
}
|
||||
}
|
||||
|
||||
let songs = song_ids
|
||||
.iter()
|
||||
.map(|song_id| songs.remove(song_id))
|
||||
.collect();
|
||||
|
||||
Ok(songs)
|
||||
}
|
||||
|
||||
#[api_fn(endpoint = "songs/plays")]
|
||||
pub async fn get_song_plays(song_id: i32, db_conn: &mut PgPooledConn) -> BackendResult<i64> {
|
||||
let plays = song_history::table
|
||||
|
||||
@@ -91,7 +91,7 @@ fn HomePage(
|
||||
</div>
|
||||
<Personal />
|
||||
<Queue />
|
||||
<PlayBar />
|
||||
<PlayBar {..} node_ref={GlobalState::playbar_element()} />
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ pub mod queue;
|
||||
pub mod sidebar;
|
||||
pub mod song;
|
||||
pub mod song_list;
|
||||
pub mod songs;
|
||||
pub mod upload;
|
||||
pub mod upload_dropdown;
|
||||
|
||||
@@ -38,6 +39,7 @@ pub mod all {
|
||||
pub use song_list::{
|
||||
SongAlbum, SongArtists, SongImage, SongLikeDislike, SongList, SongListExtra, SongListItem,
|
||||
};
|
||||
pub use songs::all::*;
|
||||
pub use upload::{Album, Artist, Upload, UploadBtn};
|
||||
pub use upload_dropdown::{UploadDropdown, UploadDropdownBtn};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use leptos::html::Div;
|
||||
#[component]
|
||||
pub fn Personal() -> impl IntoView {
|
||||
view! {
|
||||
<div class="home-card">
|
||||
<div class="home-card w-[250px] min-w-[250px]">
|
||||
<Profile />
|
||||
</div>
|
||||
}
|
||||
@@ -29,7 +29,7 @@ pub fn Profile() -> impl IntoView {
|
||||
let user_profile_picture = move || user.get().flatten().map(|user| user.image_path.path());
|
||||
|
||||
view! {
|
||||
<div class="flex w-50 relative">
|
||||
<div class="flex relative">
|
||||
<div class="text-lg self-center">
|
||||
<Suspense
|
||||
fallback=|| view!{
|
||||
|
||||
@@ -9,7 +9,9 @@ fn remove_song_fn(index: usize) {
|
||||
if index == 0 {
|
||||
leptos_log!("Error: Trying to remove currently playing song (index 0) from queue");
|
||||
} else {
|
||||
leptos_log!("Remove Song from Queue: Song is not currently playing, deleting song from queue and not adding to history");
|
||||
leptos_log!(
|
||||
"Remove Song from Queue: Song is not currently playing, deleting song from queue and not adding to history"
|
||||
);
|
||||
GlobalState::play_status().update(|status| {
|
||||
status.queue.remove(index);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use leptos::html::Div;
|
||||
use leptos_router::components::{Form, A};
|
||||
use leptos_router::components::{A, Form};
|
||||
use leptos_router::hooks::use_location;
|
||||
use std::sync::Arc;
|
||||
use web_sys::Response;
|
||||
|
||||
7
src/components/songs/mod.rs
Normal file
7
src/components/songs/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub mod song;
|
||||
pub mod song_list;
|
||||
pub mod song_list_defaults;
|
||||
|
||||
pub mod all {
|
||||
use super::*;
|
||||
}
|
||||
1
src/components/songs/song.rs
Normal file
1
src/components/songs/song.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
src/components/songs/song_list.rs
Normal file
1
src/components/songs/song_list.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
src/components/songs/song_list_defaults.rs
Normal file
1
src/components/songs/song_list_defaults.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::ingest::scan::full_scan;
|
||||
use crate::prelude::*;
|
||||
|
||||
use tokio::task::{spawn, JoinHandle};
|
||||
use tokio::time::{interval_at, Duration, Instant};
|
||||
use tokio::task::{JoinHandle, spawn};
|
||||
use tokio::time::{Duration, Instant, interval_at};
|
||||
|
||||
pub const INITIAL_SCAN_DELAY: Duration = Duration::from_secs(10);
|
||||
pub const SCAN_INTERVAL: Duration = Duration::from_hours(1);
|
||||
|
||||
@@ -6,12 +6,12 @@ extern crate diesel_migrations;
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::{Router, middleware::from_fn};
|
||||
use axum::{body::Body, extract::Request, http::Response, middleware::Next};
|
||||
use axum::{middleware::from_fn, Router};
|
||||
use axum_login::tower_sessions::SessionManagerLayer;
|
||||
use axum_login::AuthManagerLayerBuilder;
|
||||
use axum_login::tower_sessions::SessionManagerLayer;
|
||||
use http::StatusCode;
|
||||
use leptos_axum::{file_and_error_handler, generate_route_list, LeptosRoutes};
|
||||
use leptos_axum::{LeptosRoutes, file_and_error_handler, generate_route_list};
|
||||
use libretunes::app::*;
|
||||
use libretunes::prelude::*;
|
||||
use libretunes::util::config::load_config;
|
||||
|
||||
@@ -40,10 +40,10 @@ impl PlayStatus {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_audio(&self) -> Option<HtmlAudioElement> {
|
||||
if let Some(audio) = &self.audio_player {
|
||||
if let Some(audio) = audio.get() {
|
||||
return Some(audio);
|
||||
}
|
||||
if let Some(audio) = &self.audio_player
|
||||
&& let Some(audio) = audio.get()
|
||||
{
|
||||
return Some(audio);
|
||||
}
|
||||
|
||||
None
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use leptos::ev::{keydown, KeyboardEvent};
|
||||
use leptos::ev::{KeyboardEvent, keydown};
|
||||
use leptos::html::{Button, Input};
|
||||
use leptos_router::components::Form;
|
||||
use leptos_router::hooks::{use_navigate, use_params_map};
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::util::database::{PgPool, PgPooledConn};
|
||||
|
||||
use axum::extract::FromRequestParts;
|
||||
use diesel::r2d2::ConnectionManager;
|
||||
use http::{request::Parts, StatusCode};
|
||||
use http::{StatusCode, request::Parts};
|
||||
use leptos_axum::extract;
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -93,7 +93,9 @@ impl Config {
|
||||
Some(url) => url.clone(),
|
||||
None => match &self.postgres_config {
|
||||
Some(config) => config.to_url(),
|
||||
None => panic!("Both database_url and postgres_config are missing. This error shouldn't be possible."),
|
||||
None => panic!(
|
||||
"Both database_url and postgres_config are missing. This error shouldn't be possible."
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use diesel::{
|
||||
pg::PgConnection,
|
||||
r2d2::{ConnectionManager, Pool, PooledConnection},
|
||||
};
|
||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
||||
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
|
||||
|
||||
pub type PgPool = Pool<ConnectionManager<PgConnection>>;
|
||||
pub type PgPooledConn = PooledConnection<ConnectionManager<PgConnection>>;
|
||||
|
||||
@@ -18,7 +18,7 @@ pub const MUSIC_PLACEHOLDER_WEB_PATH: &str = "/placeholders/MusicPlaceholder.svg
|
||||
/// Uses 128 bits of randomness encoded in hexadecimal,
|
||||
/// where `XX` is the first byte and `XXXXXXXXXX` is the remaining bytes.
|
||||
fn random_path() -> PathBuf {
|
||||
use rand::{rng, Rng};
|
||||
use rand::{Rng, rng};
|
||||
|
||||
let mut rng = rng();
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use leptos::html::Div;
|
||||
|
||||
/// Global front-end state
|
||||
/// Contains anything frequently needed across multiple components
|
||||
/// Behaves like a singleton, in that `provide_context`/`expect_context` will
|
||||
@@ -16,10 +18,15 @@ pub struct GlobalState {
|
||||
|
||||
/// A resource that fetches the playlists
|
||||
pub playlists: Resource<BackendResult<Vec<frontend::Playlist>>>,
|
||||
|
||||
/// A reference to the playbar
|
||||
pub playbar_element: NodeRef<Div>,
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
pub fn new() -> Self {
|
||||
let playbar_element = NodeRef::<Div>::new();
|
||||
|
||||
let play_status = RwSignal::new(frontend::PlayStatus::default());
|
||||
|
||||
let logged_in_user = Resource::new(
|
||||
@@ -53,6 +60,7 @@ impl GlobalState {
|
||||
logged_in_user,
|
||||
play_status,
|
||||
playlists,
|
||||
playbar_element,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +75,10 @@ impl GlobalState {
|
||||
pub fn playlists() -> Resource<BackendResult<Vec<frontend::Playlist>>> {
|
||||
expect_context::<Self>().playlists
|
||||
}
|
||||
|
||||
pub fn playbar_element() -> NodeRef<Div> {
|
||||
expect_context::<Self>().playbar_element
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GlobalState {
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
@apply text-white;
|
||||
@apply last-of-type:grow;
|
||||
@apply last-of-type:pb-[85px]; /* Hard-coded height of the playbar + 5px */
|
||||
@apply overflow-scroll;
|
||||
@apply overflow-y-scroll;
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
|
||||
Reference in New Issue
Block a user