Compare commits
4 Commits
60eb0d3ff1
...
2ddf210a5f
Author | SHA1 | Date | |
---|---|---|---|
2ddf210a5f | |||
bef20ccc5a | |||
c713fcaae8 | |||
32d131db67 |
@ -9,10 +9,10 @@
|
||||
<title>Solid App</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<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>
|
||||
|
||||
<body data-theme="black">
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
|
11
frontend/src/icons/ArrowClockwiseIcon.tsx
Normal file
11
frontend/src/icons/ArrowClockwiseIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
12
frontend/src/icons/PlayIcon.tsx
Normal file
12
frontend/src/icons/PlayIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -2,8 +2,48 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Dark mode */
|
||||
:root {
|
||||
--c-bg: #09090b;
|
||||
--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);
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ import { batch, createEffect, createMemo, createResource, createSignal, onMount
|
||||
import { GetAlbumCover, GetRandomAlbums, TriggerAlbumReload } from "../../wailsjs/go/main/App";
|
||||
import { main } from "../../wailsjs/go/models";
|
||||
import { createStore } from "solid-js/store";
|
||||
import { ArrowClockwiseIcon } from "../icons/ArrowClockwiseIcon";
|
||||
import { PlayIcon } from "../icons/PlayIcon";
|
||||
|
||||
type AlbumsData = {
|
||||
status: "loading" | "ok",
|
||||
@ -24,7 +26,7 @@ export function Home() {
|
||||
});
|
||||
|
||||
// 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 response = await GetRandomAlbums();
|
||||
|
||||
@ -54,16 +56,16 @@ export function Home() {
|
||||
|
||||
return (
|
||||
<div class={`min-h-screen ${hidden() ? "opacity-0" : "opacity-100"} transition-opacity`}>
|
||||
<h1 class="font-black text-2xl pt-6 pb-4 pl-2">
|
||||
Random albums
|
||||
<h1 class="font-title font-black text-2xl pt-6 pb-4 pl-2">
|
||||
<span class="text-c-primary">Random albums</span>
|
||||
<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}
|
||||
>
|
||||
Reload
|
||||
<ArrowClockwiseIcon class="h-4 w-4 inline-block" fill="var(--c-primary)" />
|
||||
</button>
|
||||
</h1>
|
||||
<div class="pb-4 overflow-scroll whitespace-nowrap">
|
||||
<div class="pb-1 overflow-x-scroll whitespace-nowrap">
|
||||
{els}
|
||||
</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"));
|
||||
|
||||
return (
|
||||
<div class="inline-block mx-2 p-1 w-32 rounded bg-zinc-900">
|
||||
<div class="w-30 h-30" >
|
||||
<div class="inline-block mx-1 p-1 w-32 rounded bg-c-primary-bg text-c-on-primary-bg
|
||||
hover:shadow hover:bg-c-primary-bg-2 transition-shadow group"
|
||||
>
|
||||
<div class="w-30 h-30 relative" >
|
||||
<img
|
||||
class={`inline-block rounded w-30 h-30 transition-opacity ${imgOpacity()}`}
|
||||
src={imageBase64()}
|
||||
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 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()}
|
||||
</div>
|
||||
<div class={`text-xs overflow-hidden overflow-ellipsis ${opacity()}`} title={artistName()}>
|
||||
{artistName()}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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 { 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() {
|
||||
const [loading, setLoading] = createSignal(false);
|
||||
const [error, setError] = createSignal("");
|
||||
const [server, setServer] = createSignal("");
|
||||
const [username, setUsername] = createSignal("");
|
||||
const [password, setPassword] = createSignal("");
|
||||
const [fade, setFade] = createSignal(false);
|
||||
const [fadeUI, setFadeUI] = createSignal(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const login = async(ev: Event) => {
|
||||
@ -17,54 +29,55 @@ export function Login() {
|
||||
setError("");
|
||||
|
||||
GoLogin(server(), username(), password())
|
||||
.then(() => {
|
||||
setFade(true);
|
||||
setTimeout(() => {
|
||||
navigate("/home");
|
||||
}, 150);
|
||||
})
|
||||
.then(() => runAndDelay(150, () => setFadeUI(true)))
|
||||
.then(() => navigate("/home"))
|
||||
.catch((err) => {
|
||||
setError(err);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
setFadeUI(true);
|
||||
delay(150, () => setFadeUI(false));
|
||||
});
|
||||
|
||||
return (
|
||||
<div class={`w-screen h-screen flex items-center justify-center ${fade() ? "opacity-0" : "opacity-100"} transition-opacity`}>
|
||||
<div class="w-80">
|
||||
<div class={`w-screen h-screen flex items-center justify-center ${fadeUI() ? "opacity-0" : "opacity-100"} transition-opacity`}>
|
||||
<div class="max-w-80 px-1 overflow-hidden">
|
||||
<form onSubmit={login}>
|
||||
<h1 class="text-center font-black text-xl">Login</h1>
|
||||
<label class="form-control" for="login-server">
|
||||
<h1 class="text-center font-title font-black text-xl text-c-primary mb-4">Login to Navidrome</h1>
|
||||
<label for="login-server">
|
||||
<div class="label">
|
||||
<span class="label-text text-xs">Server URL</span>
|
||||
<span class="font-title label-text text-xs">Server URL:</span>
|
||||
</div>
|
||||
<input id="login-server" type="text"
|
||||
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://"
|
||||
required
|
||||
disabled={loading()}
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control" for="login-username">
|
||||
<label for="login-username">
|
||||
<div class="label">
|
||||
<span class="label-text text-xs">Username</span>
|
||||
<span class="font-title label-text text-xs">Username</span>
|
||||
</div>
|
||||
<input id="login-username" type="text"
|
||||
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"
|
||||
required
|
||||
disabled={loading()}
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control" for="login-password">
|
||||
<label for="login-password">
|
||||
<div class="label">
|
||||
<span class="label-text text-xs">Password</span>
|
||||
<span class="font-title label-text text-xs">Password</span>
|
||||
</div>
|
||||
<input id="login-password" type="password"
|
||||
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="********"
|
||||
required
|
||||
disabled={loading()}
|
||||
@ -72,7 +85,9 @@ export function Login() {
|
||||
</label>
|
||||
<br />
|
||||
<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()}
|
||||
>
|
||||
Login
|
||||
|
@ -1,5 +1,3 @@
|
||||
import daisyui from "daisyui";
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
@ -10,45 +8,20 @@ export default {
|
||||
spacing: {
|
||||
"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",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
6
main.go
6
main.go
@ -26,10 +26,8 @@ func main() {
|
||||
Title: "my-wails-solid",
|
||||
Width: 1024,
|
||||
Height: 768,
|
||||
MinWidth: 1024,
|
||||
MinHeight: 768,
|
||||
MaxWidth: 1280,
|
||||
MaxHeight: 800,
|
||||
MinWidth: 250,
|
||||
MinHeight: 400,
|
||||
DisableResize: false,
|
||||
Fullscreen: false,
|
||||
Frameless: false,
|
||||
|
@ -23,12 +23,12 @@ var randomAlbums []Album
|
||||
var serverUrl = ""
|
||||
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) {
|
||||
log.Print("begin Login to server")
|
||||
// client := resty.New()
|
||||
|
||||
// TODO: check server for leading https and trailing /, normalize
|
||||
// TODO: check server string for leading https and trailing /, normalize
|
||||
|
||||
successData := AuthSuccess{}
|
||||
errorData := AuthError{}
|
||||
|
Loading…
Reference in New Issue
Block a user