diff --git a/index.html b/index.html
new file mode 100644
index 0000000..1276eb1
--- /dev/null
+++ b/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+ Music App
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/mod.rs b/src/components/mod.rs
new file mode 100644
index 0000000..d365263
--- /dev/null
+++ b/src/components/mod.rs
@@ -0,0 +1,3 @@
+pub use separator::separator;
+
+mod separator;
\ No newline at end of file
diff --git a/src/components/separator/mod.rs b/src/components/separator/mod.rs
new file mode 100644
index 0000000..d7ea1fd
--- /dev/null
+++ b/src/components/separator/mod.rs
@@ -0,0 +1,26 @@
+use sycamore::prelude::*;
+
+#[derive(Props)]
+pub struct SeparatorProps {
+ width: u8
+}
+
+/*
+ w-[1%] w-[2%] w-[3%] w-[4%] w-[5%] w-[6%] w-[7%] w-[8%] w-[9%] w-[10%]
+ w-[11%] w-[12%] w-[13%] w-[14%] w-[15%] w-[16%] w-[17%] w-[18%] w-[19%] w-[20%]
+ w-[21%] w-[22%] w-[23%] w-[24%] w-[25%] w-[26%] w-[27%] w-[28%] w-[29%] w-[30%]
+ w-[31%] w-[32%] w-[33%] w-[34%] w-[35%] w-[36%] w-[37%] w-[38%] w-[39%] w-[40%]
+ w-[41%] w-[42%] w-[43%] w-[44%] w-[45%] w-[46%] w-[47%] w-[48%] w-[49%] w-[50%]
+ w-[51%] w-[52%] w-[53%] w-[54%] w-[55%] w-[56%] w-[57%] w-[58%] w-[59%] w-[60%]
+ w-[61%] w-[62%] w-[63%] w-[64%] w-[65%] w-[66%] w-[67%] w-[68%] w-[69%] w-[70%]
+ w-[71%] w-[72%] w-[73%] w-[74%] w-[75%] w-[76%] w-[77%] w-[78%] w-[79%] w-[80%]
+ w-[81%] w-[82%] w-[83%] w-[84%] w-[85%] w-[86%] w-[87%] w-[88%] w-[89%] w-[90%]
+ w-[91%] w-[92%] w-[93%] w-[94%] w-[95%] w-[96%] w-[97%] w-[98%] w-[99%] w-[100%]
+*/
+
+pub fn separator(props: SeparatorProps) -> View {
+ let class_string = format!("place-self-center w-[{}%] border border-1 border-black", props.width);
+ view! {
+ p(class=class_string) {}
+ }
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..b2252be
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,201 @@
+mod components;
+
+use std::env;
+use sycamore::prelude::*;
+use sycamore::futures::spawn_local_scoped;
+use serde::Deserialize;
+use log::{info, warn, error, Level, debug};
+
+fn main() {
+ console_log::init_with_level(Level::Debug).expect("Something went horribly wrong while setting the Logging Level");
+
+ sycamore::render(|| view! {
+ div(class="flex flex-col h-full") {
+ div(class="min-w-full p-1 debug") {
+ h1 { "Navbar" }
+ }
+ div(class="flex-1 grid grid-cols-5 debug") {
+ div(class="col-span-1 debug") {
+ h1 { "Left Sidebar" }
+ }
+ div(class="col-span-3 debug") {
+ h1 { "Center Module" }
+ }
+ div(class="col-span-1 flex flex-col gap-1 p-2 debug") {
+ div(class="flex flex-row w-full gap-2 items-center") {
+ h1(class="flex flex-1") { "Current Queue" }
+ p { "Fetch" }
+ p { "Upload" }
+ }
+ components::separator(width = 100)
+ crate::render_playlist {}
+ }
+ }
+ div(class="min-h-24 h-24 debug") {
+ h3 { "Player Module" }
+ }
+ }
+ });
+}
+
+#[derive(Deserialize)]
+struct PlayQueueApi {
+ #[serde(alias="subsonic-response")]
+ subsonic_response: SubsonicResponse
+}
+
+#[derive(Deserialize)]
+struct SubsonicResponse {
+ status: String,
+ version: String,
+ r#type: String,
+ #[serde(alias="serverVersion")]
+ server_version: String,
+ #[serde(alias="openSubsonic")]
+ open_subsonic: bool,
+ #[serde(alias="playQueue")]
+ play_queue: PlayQueue,
+}
+
+#[derive(Debug, Deserialize)]
+struct PlayQueue {
+ entry: Vec,
+ current: Option,
+ position: Option,
+ username: String,
+ changed: String,
+ #[serde(alias="changedBy")]
+ changed_by: String
+}
+
+#[derive(Clone, Debug, Deserialize)]
+struct Song {
+ id: String,
+ parent: String,
+ #[serde(alias="isDir")]
+ is_dir: bool,
+ title: String,
+ album: String,
+ artist: String,
+ track: usize,
+ year: usize,
+ genre: String,
+ genres: Vec,
+ #[serde(alias="coverArt")]
+ cover_art: String,
+ size: usize,
+ #[serde(alias="contentType")]
+ content_type: String,
+ suffix: String,
+ duration: usize,
+ #[serde(alias="bitRate")]
+ bit_rate: usize,
+ path: String,
+ #[serde(alias="playCount")]
+ play_count: usize,
+ played: String,
+ #[serde(alias="discNumber")]
+ disc_number: usize,
+ created: String,
+ #[serde(alias="albumId")]
+ album_id: String,
+ #[serde(alias="artistId")]
+ artist_id: String,
+ r#type: String,
+ #[serde(alias="userRating")]
+ user_rating: Option,
+ #[serde(alias="isVideo")]
+ is_video: bool,
+ bpm: usize,
+ comment: String
+}
+
+#[derive(Debug, Clone, Deserialize)]
+struct Genre {
+ name: String
+}
+
+async fn test_api() -> Vec {
+ const API_BASE: &str = "https://music.neshweb.net/rest";
+
+
+ let salt = "testsalt1234"; // TODO: random salt please
+
+ let salted_password = format!("{password}{salt}");
+
+ let hash = md5::compute(salted_password.as_bytes());
+
+ let res = match reqwest::get(format!("{API_BASE}/getPlayQueue.view?u={user}&t={:x}&s={salt}&v=1.16.1&c={}&f=json", hash, env!("CARGO_PKG_NAME"))).await {
+ Ok(data) => data,
+ Err(e) => {
+ error!("Error: {e}");
+ return vec![]
+ },
+ };
+
+ debug!("Still alive!");
+
+ let data = match res.json::().await {
+ Ok(data) => data,
+ Err(e) => {
+ error!("Error: {e}");
+ return vec![]
+ },
+ };
+
+
+
+ data.subsonic_response.play_queue.entry.iter().map(|elem| {
+ elem.clone()
+ }).collect()
+
+ /*
+ let res = reqwest::blocking::get(format!("{api_base}/getPlayQueue.view?u={user}&t={:x}&s={salt}&v=1.16.1&c=myapp&f=json", hash)).unwrap();
+
+ let text = res.text().unwrap();
+
+ println!("{text}");
+
+ let song_id = "81983bfae2fdddfa5d3dc181d61d987c";
+ let song_pos = 1;
+
+ let res_save = reqwest::blocking::get(format!("{api_base}/savePlayQueue.view?u={user}&t={:x}&s={salt}&v=1.16.1&c={}&f=json&id={song_id}&pos={song_pos}", hash, env!("CARGO_PKG_NAME"))).unwrap();
+
+ let text_save = res_save.text().unwrap();
+
+ println!("{text_save}");*/
+}
+
+#[component]
+fn render_playlist() -> View {
+ let list: Signal> = create_signal(vec![]);
+
+ info!("Info Test");
+ debug!("Debug Test");
+
+ spawn_local_scoped(async move {
+ debug!("Moving into spawned task");
+ let res = test_api().await;
+ debug!("Data from spawned task returned");
+ list.set(res);
+ });
+
+ create_effect(move || {
+ debug!("List is: {:#?}", list.get_clone());
+ });
+
+ view! {
+ (View::new_fragment(list.get_clone().iter().map(|elem: &Song| {
+ let song = elem.clone();
+ view! {
+ p { (song.artist) " - " (song.title) " (" (seconds_to_duration(song.duration)) ")" }
+ }
+ }).collect()))
+ }
+}
+
+fn seconds_to_duration(raw_seconds: usize) -> String {
+ let minutes = raw_seconds / 60;
+ let seconds = raw_seconds % 60;
+ format!("{minutes}:{seconds}")
+}
diff --git a/styles.css b/styles.css
new file mode 100644
index 0000000..2426beb
--- /dev/null
+++ b/styles.css
@@ -0,0 +1,24 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ h1 {
+ @apply text-2xl;
+ @apply font-bold;
+ }
+ h2 {
+ @apply text-xl;
+ @apply font-bold;
+ }
+ h3 {
+ @apply text-lg;
+ @apply font-bold;
+ }
+}
+
+.debug {
+ @apply border;
+ @apply border-2;
+ @apply border-black;
+}