Compare commits

..

4 Commits

Author SHA1 Message Date
2ddf210a5f Color scheme for dark mode 2024-06-16 20:17:18 -05:00
bef20ccc5a Styles for homepage 2024-06-16 20:02:20 -05:00
c713fcaae8 Changes to colors 2024-06-16 19:15:53 -05:00
32d131db67 Remove daisy, rewrite login UI 2024-06-16 18:13:03 -05:00
9 changed files with 144 additions and 85 deletions

View File

@ -9,10 +9,10 @@
<title>Solid App</title> <title>Solid App</title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,100..900;1,100..900&family=Inter:wght@100..900&display=swap" rel="stylesheet">
</head> </head>
<body data-theme="black"> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root"></div>

View File

@ -0,0 +1,11 @@
export function ArrowClockwiseIcon(props: {
fill: string,
class?: string,
size?: number,
}) {
return (
<svg class={props.class} width={props.size ?? 32} height={props.size ?? 32} fill={props.fill} viewBox="0 0 256 256">
<path d="M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1-5.66-13.66l17-17-10.55-9.65-.25-.24a80,80,0,1,0-1.67,114.78,8,8,0,1,1,11,11.63A95.44,95.44,0,0,1,128,224h-1.32A96,96,0,1,1,195.75,60l10.93,10L226.34,50.3A8,8,0,0,1,240,56Z" />
</svg>
);
}

View File

@ -0,0 +1,12 @@
export function PlayIcon(props: {
fill: string,
class?: string,
size?: number,
}) {
return (
<svg class={props.class} width={props.size ?? 32} height={props.size ?? 32} fill={props.fill} viewBox="0 0 256 256">
<path d="M240,128a15.74,15.74,0,0,1-7.6,13.51L88.32,229.65a16,16,0,0,1-16.2.3A15.86,15.86,0,0,1,64,216.13V39.87a15.86,15.86,0,0,1,8.12-13.82,16,16,0,0,1,16.2.3L232.4,114.49A15.74,15.74,0,0,1,240,128Z" />
</svg>
);
}

View File

@ -2,8 +2,48 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
html { /* Dark mode */
font-family: "Inter", sans-serif; :root {
font-optical-sizing: auto; --c-bg: #09090b;
font-variation-settings: "slnt" 0; --c-on-bg: white;
--c-border-1: #a1a1aa;
--c-bg-inversed: rgba(228, 228, 240, 0.75);
--c-primary: #c4b5fd;
--c-on-primary: #09090b;
--c-primary-bg: #1f162c;
--c-primary-bg-2: #332a3f;
--c-on-primary-bg: #f5f3ff;
--c-primary-border: #6b58a1;
}
/* Light mode */
@media (prefers-color-scheme: light) {
:root {
--c-bg: white;
--c-on-bg: #09090b;
--c-border-1: #a1a1aa;
--c-bg-inversed: rgba(39, 39, 42, 0.75);
--c-primary: #7c3aed;
--c-on-primary: white;
--c-primary-bg: #f5f3ff;
--c-primary-bg-2: #ede9fe;
--c-on-primary-bg: #110627;
--c-primary-border: #c4b5fd;
}
}
html {
font-family: "Inter", sans-serif;
font-optical-sizing: auto;
font-variation-settings: "slnt" 0;
background-color: var(--c-bg);
color: var(--c-on-bg);
} }

View File

@ -2,6 +2,8 @@ import { batch, createEffect, createMemo, createResource, createSignal, onMount
import { GetAlbumCover, GetRandomAlbums, TriggerAlbumReload } from "../../wailsjs/go/main/App"; import { GetAlbumCover, GetRandomAlbums, TriggerAlbumReload } from "../../wailsjs/go/main/App";
import { main } from "../../wailsjs/go/models"; import { main } from "../../wailsjs/go/models";
import { createStore } from "solid-js/store"; import { createStore } from "solid-js/store";
import { ArrowClockwiseIcon } from "../icons/ArrowClockwiseIcon";
import { PlayIcon } from "../icons/PlayIcon";
type AlbumsData = { type AlbumsData = {
status: "loading" | "ok", status: "loading" | "ok",
@ -24,7 +26,7 @@ export function Home() {
}); });
// Loads the random albums from Go. // Loads the random albums from Go.
// This function, when called multiple times, returns the same set. // This function returns the same set when called multiple times.
const loadAlbums = async() => { const loadAlbums = async() => {
const response = await GetRandomAlbums(); const response = await GetRandomAlbums();
@ -54,16 +56,16 @@ export function Home() {
return ( return (
<div class={`min-h-screen ${hidden() ? "opacity-0" : "opacity-100"} transition-opacity`}> <div class={`min-h-screen ${hidden() ? "opacity-0" : "opacity-100"} transition-opacity`}>
<h1 class="font-black text-2xl pt-6 pb-4 pl-2"> <h1 class="font-title font-black text-2xl pt-6 pb-4 pl-2">
Random albums <span class="text-c-primary">Random albums</span>
<button <button
class="text-xs font-normal mx-1 p-1 rounded underline hover:bg-zinc-900" class="mx-1 p-1 rounded underline hover:bg-c-primary-bg-2 cursor-pointer"
onClick={triggerAlbumReload} onClick={triggerAlbumReload}
> >
Reload <ArrowClockwiseIcon class="h-4 w-4 inline-block" fill="var(--c-primary)" />
</button> </button>
</h1> </h1>
<div class="pb-4 overflow-scroll whitespace-nowrap"> <div class="pb-1 overflow-x-scroll whitespace-nowrap">
{els} {els}
</div> </div>
</div> </div>
@ -109,22 +111,30 @@ function Album(props: { albums: Array<main.Album | null>, idx: number }) {
const imgOpacity = createMemo(() => (imgReady() && !isNull() ? "opacity-100" : "opacity-0")); const imgOpacity = createMemo(() => (imgReady() && !isNull() ? "opacity-100" : "opacity-0"));
return ( return (
<div class="inline-block mx-2 p-1 w-32 rounded bg-zinc-900"> <div class="inline-block mx-1 p-1 w-32 rounded bg-c-primary-bg text-c-on-primary-bg
<div class="w-30 h-30" > hover:shadow hover:bg-c-primary-bg-2 transition-shadow group"
>
<div class="w-30 h-30 relative" >
<img <img
class={`inline-block rounded w-30 h-30 transition-opacity ${imgOpacity()}`} class={`inline-block rounded w-30 h-30 transition-opacity ${imgOpacity()}`}
src={imageBase64()} src={imageBase64()}
alt="" alt=""
/> />
<button
class="inline-block absolute bottom-1 right-1 bg-[var(--c-bg-inversed)] z-10 cursor-pointer h-8 w-8 rounded-full
hover:bg-c-primary transition-all opacity-0 group-hover:opacity-100"
>
<PlayIcon class="inline-block p-2 h-full w-full" fill="var(--c-primary-bg)" />
</button>
</div> </div>
<div class={`text-sm overflow-hidden overflow-ellipsis pt-1 ${opacity()}`} title={albumName()}> <div class={`font-title font-medium text-sm overflow-hidden overflow-ellipsis pt-1 ${opacity()}`} title={albumName()}>
{albumName()} {albumName()}
</div> </div>
<div class={`text-xs overflow-hidden overflow-ellipsis ${opacity()}`} title={artistName()}> <div class={`text-xs overflow-hidden overflow-ellipsis ${opacity()}`} title={artistName()}>
{artistName()} {artistName()}
</div> </div>
</div> </div>
); );
} }

View File

@ -1,14 +1,26 @@
import { Show, createSignal } from "solid-js"; import { Show, createSignal, onMount } from "solid-js";
import { Login as GoLogin } from "../../wailsjs/go/main/App"; import { Login as GoLogin } from "../../wailsjs/go/main/App";
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
function runAndDelay(ms: number, fn: () => void): Promise<void> {
fn();
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
const delay = (ms: number, fn: () => void) => new Promise((resolve) => setTimeout(() => {
fn();
resolve(null);
}, ms));
export function Login() { export function Login() {
const [loading, setLoading] = createSignal(false); const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal(""); const [error, setError] = createSignal("");
const [server, setServer] = createSignal(""); const [server, setServer] = createSignal("");
const [username, setUsername] = createSignal(""); const [username, setUsername] = createSignal("");
const [password, setPassword] = createSignal(""); const [password, setPassword] = createSignal("");
const [fade, setFade] = createSignal(false); const [fadeUI, setFadeUI] = createSignal(false);
const navigate = useNavigate(); const navigate = useNavigate();
const login = async(ev: Event) => { const login = async(ev: Event) => {
@ -17,54 +29,55 @@ export function Login() {
setError(""); setError("");
GoLogin(server(), username(), password()) GoLogin(server(), username(), password())
.then(() => { .then(() => runAndDelay(150, () => setFadeUI(true)))
setFade(true); .then(() => navigate("/home"))
setTimeout(() => {
navigate("/home");
}, 150);
})
.catch((err) => { .catch((err) => {
setError(err); setError(err);
setLoading(false); setLoading(false);
}); });
}; };
onMount(() => {
setFadeUI(true);
delay(150, () => setFadeUI(false));
});
return ( return (
<div class={`w-screen h-screen flex items-center justify-center ${fade() ? "opacity-0" : "opacity-100"} transition-opacity`}> <div class={`w-screen h-screen flex items-center justify-center ${fadeUI() ? "opacity-0" : "opacity-100"} transition-opacity`}>
<div class="w-80"> <div class="max-w-80 px-1 overflow-hidden">
<form onSubmit={login} > <form onSubmit={login}>
<h1 class="text-center font-black text-xl">Login</h1> <h1 class="text-center font-title font-black text-xl text-c-primary mb-4">Login to Navidrome</h1>
<label class="form-control" for="login-server"> <label for="login-server">
<div class="label"> <div class="label">
<span class="label-text text-xs">Server URL</span> <span class="font-title label-text text-xs">Server URL:</span>
</div> </div>
<input id="login-server" type="text" <input id="login-server" type="text"
onInput={(e) => setServer(e.target.value)} onInput={(e) => setServer(e.target.value)}
class="input input-bordered" class="border border-c-primary-border rounded bg-c-primary-bg inline-block w-full p-1"
placeholder="https://" placeholder="https://"
required required
disabled={loading()} disabled={loading()}
/> />
</label> </label>
<label class="form-control" for="login-username"> <label for="login-username">
<div class="label"> <div class="label">
<span class="label-text text-xs">Username</span> <span class="font-title label-text text-xs">Username</span>
</div> </div>
<input id="login-username" type="text" <input id="login-username" type="text"
onInput={(e) => setUsername(e.target.value)} onInput={(e) => setUsername(e.target.value)}
class="input input-bordered" class="border border-c-primary-border rounded bg-c-primary-bg inline-block w-full p-1"
placeholder="username" placeholder="username"
required required
disabled={loading()} disabled={loading()}
/> />
</label> </label>
<label class="form-control" for="login-password"> <label for="login-password">
<div class="label"> <div class="label">
<span class="label-text text-xs">Password</span> <span class="font-title label-text text-xs">Password</span>
</div> </div>
<input id="login-password" type="password" <input id="login-password" type="password"
onInput={(e) => setPassword(e.target.value)} onInput={(e) => setPassword(e.target.value)}
class="input input-bordered" class="border border-c-primary-border rounded bg-c-primary-bg inline-block w-full p-1"
placeholder="********" placeholder="********"
required required
disabled={loading()} disabled={loading()}
@ -72,7 +85,9 @@ export function Login() {
</label> </label>
<br /> <br />
<div class="text-center"> <div class="text-center">
<button type="submit" class={`btn btn-primary ${loading() ? "animate-pulse" : ""}`} <button
type="submit"
class={`rounded font-title bg-c-primary text-c-on-primary py-2 px-4 mt-6 ${loading() ? "animate-pulse" : ""}`}
disabled={loading()} disabled={loading()}
> >
Login Login

View File

@ -1,5 +1,3 @@
import daisyui from "daisyui";
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: [ content: [
@ -10,45 +8,20 @@ export default {
spacing: { spacing: {
"30": "7.5rem", "30": "7.5rem",
}, },
fontFamily: {
"title": ["'Inter Tight'", "Inter", "sans-serif"],
},
colors: {
"c-bg": "var(--c-bg)",
"c-on-bg": "var(--c-on-bg)",
"c-border-1": "var(--c-border-1)",
"c-primary": "var(--c-primary)",
"c-on-primary": "var(--c-on-primary)",
"c-primary-bg": "var(--c-primary-bg)",
"c-primary-bg-2": "var(--c-primary-bg-2)",
"c-on-primary-bg": "var(--c-on-primary-bg)",
"c-primary-border": "var(--c-primary-border)",
},
}, },
}, },
plugins: [
daisyui,
],
daisyui: {
themes: [
"light",
"dark",
"cupcake",
"bumblebee",
"emerald",
"corporate",
"synthwave",
"retro",
"cyberpunk",
"valentine",
"halloween",
"garden",
"forest",
"aqua",
"lofi",
"pastel",
"fantasy",
"wireframe",
"black",
"luxury",
"dracula",
"cmyk",
"autumn",
"business",
"acid",
"lemonade",
"night",
"coffee",
"winter",
"dim",
"nord",
"sunset",
],
},
}; };

View File

@ -26,10 +26,8 @@ func main() {
Title: "my-wails-solid", Title: "my-wails-solid",
Width: 1024, Width: 1024,
Height: 768, Height: 768,
MinWidth: 1024, MinWidth: 250,
MinHeight: 768, MinHeight: 400,
MaxWidth: 1280,
MaxHeight: 800,
DisableResize: false, DisableResize: false,
Fullscreen: false, Fullscreen: false,
Frameless: false, Frameless: false,

View File

@ -23,12 +23,12 @@ var randomAlbums []Album
var serverUrl = "" var serverUrl = ""
var client = resty.New() var client = resty.New()
// (Tries to) login to a remote navidrome server // Attempts to login to a remote navidrome server
func (a *App) Login(server, username, password string) (bool, error) { func (a *App) Login(server, username, password string) (bool, error) {
log.Print("begin Login to server") log.Print("begin Login to server")
// client := resty.New() // client := resty.New()
// TODO: check server for leading https and trailing /, normalize // TODO: check server string for leading https and trailing /, normalize
successData := AuthSuccess{} successData := AuthSuccess{}
errorData := AuthError{} errorData := AuthError{}