Compare commits

...

2 Commits

Author SHA1 Message Date
15ea565601 Minimal UI for albums 2024-05-29 10:49:03 -05:00
89158497e5 Load random album list from backend 2024-05-29 10:48:50 -05:00
8 changed files with 156 additions and 15 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
build/bin build/bin
frontend/dist frontend/dist
frontend/node_modules frontend/node_modules
frontend/wailsjs/go

View File

@ -1,15 +1,44 @@
import { createSignal, onMount } from "solid-js"; import { For, createResource, createSignal, onMount } from "solid-js";
import { GetRandomAlbums } from "../../wailsjs/go/main/App";
import {main} from "../../wailsjs/go/models";
export function Home() { export function Home() {
const [hidden, setHidden] = createSignal(true); const [hidden, setHidden] = createSignal(true);
const [albums] = createResource(GetRandomAlbums);
onMount(() => { onMount(() => {
// Fade in the UI
setTimeout(() => setHidden(false) , 150); setTimeout(() => setHidden(false) , 150);
}); });
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`}>
Home :D <div class="py-10 h-64 overflow-scroll whitespace-nowrap">
<For each={albums()}>
{(album) => <Album album={album} />}
</For>
</div>
</div>
);
}
function Album(props: {album: main.Album}) {
return (
<div class="inline-block mx-2 p-1 w-32 rounded bg-zinc-900">
<img
class="inline-block rounded w-30 h-30 min-w-30 min-h-30"
src={props.album.mediumImageUrl}
alt=""
/>
<br />
<div class="text-sm overflow-hidden overflow-ellipsis pt-1">
{props.album.name}
</div>
<div class="text-xs overflow-hidden overflow-ellipsis opacity-50">
{props.album.albumArtist}
</div>
</div> </div>
); );
} }

View File

@ -72,7 +72,11 @@ export function Login() {
</label> </label>
<br /> <br />
<div class="text-center"> <div class="text-center">
<button type="submit" class="btn btn-primary">Login</button> <button type="submit" class={`btn btn-primary ${loading() ? "animate-pulse" : ""}`}
disabled={loading()}
>
Login
</button>
</div> </div>
<Show when={error() !== ""}> <Show when={error() !== ""}>

View File

@ -5,6 +5,13 @@ export default {
content: [ content: [
"./src/**/*.{js,jsx,ts,tsx}", "./src/**/*.{js,jsx,ts,tsx}",
], ],
theme: {
extend: {
spacing: {
"30": "7.5rem",
},
},
},
plugins: [ plugins: [
daisyui, daisyui,
], ],

View File

@ -1,5 +1,8 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT // This file is automatically generated. DO NOT EDIT
import {main} from '../models';
export function GetRandomAlbums():Promise<Array<main.Album>>;
export function Greet(arg1:string):Promise<string>; export function Greet(arg1:string):Promise<string>;

View File

@ -2,6 +2,10 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT // This file is automatically generated. DO NOT EDIT
export function GetRandomAlbums() {
return window['go']['main']['App']['GetRandomAlbums']();
}
export function Greet(arg1) { export function Greet(arg1) {
return window['go']['main']['App']['Greet'](arg1); return window['go']['main']['App']['Greet'](arg1);
} }

View File

@ -4,29 +4,25 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"sync"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
) )
type AuthSuccess struct {
Id string `json:"id"`
IsAdmin bool `json:"isAdmin"`
Name string `json:"name"`
SubsonicSalt string `json:"subsonicSalt"`
SubsonicToken string `json:"subsonicToken"`
Token string `json:"token"`
Username string `json:"username"`
}
type AuthError struct { type AuthError struct {
Error string `json:"error"` Error string `json:"error"`
} }
var LoggedUser AuthSuccess
var randomAlbumWaitGroup sync.WaitGroup
var randomAlbums []Album
// (Tries to) login to a remote navidrome server // (Tries 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")
client := resty.New() client := resty.New()
// TODO: check server for leading https and ending /, normalize // TODO: check server for leading https and trailing /, normalize
successData := AuthSuccess{} successData := AuthSuccess{}
errorData := AuthError{} errorData := AuthError{}
@ -45,6 +41,13 @@ func (a *App) Login(server, username, password string) (bool, error) {
if response.IsSuccess() { if response.IsSuccess() {
log.Printf("%+v", successData) log.Printf("%+v", successData)
// Set global session
LoggedUser = successData
// Begin to load the list of albums on the background
randomAlbumWaitGroup.Add(1)
go loadAlbums(server)
return true, nil return true, nil
} else if response.IsError() { } else if response.IsError() {
log.Printf("%+v", errorData) log.Printf("%+v", errorData)
@ -54,3 +57,39 @@ func (a *App) Login(server, username, password string) (bool, error) {
return false, errors.New("invalid state") return false, errors.New("invalid state")
} }
} }
// Waits for the random albums to be loaded, and returns them.
// This function assumes that the random albums are being loaded in the background.
func (a *App) GetRandomAlbums() ([]Album, error) {
log.Printf("Waiting for loading group...")
randomAlbumWaitGroup.Wait()
log.Printf("Waiting group finished")
return randomAlbums, nil
}
// Loads a list of random albums from the server.
func loadAlbums(serverUrl string) {
defer randomAlbumWaitGroup.Done()
log.Print("begin loadAlbums")
client := resty.New()
var errorData AuthError
response, err := client.R().
SetHeader("x-nd-authorization", fmt.Sprintf("Bearer %s", LoggedUser.Token)).
SetResult(&randomAlbums).
SetError(&errorData).
Get(fmt.Sprintf("%s/api/album?_end=20&_order=DESC&_sort=random&_start=0", serverUrl))
if err != nil {
log.Print("Get albums error")
}
if response.IsSuccess() {
log.Print("Get albums success")
// TODO: Begin to load album artwork in the background
// Album artwork comes from the url /rest/getCoverArt.view
}
// TODO: Do the loading
}

54
types.go Normal file
View File

@ -0,0 +1,54 @@
package main
import "time"
// The result of a Login
type AuthSuccess struct {
Id string `json:"id"`
IsAdmin bool `json:"isAdmin"`
Name string `json:"name"`
SubsonicSalt string `json:"subsonicSalt"`
SubsonicToken string `json:"subsonicToken"`
Token string `json:"token"`
Username string `json:"username"`
}
// An album as defined by the server
type Album struct {
PlayCount int `json:"playCount"`
PlayDate time.Time `json:"playDate"`
Rating int `json:"rating"`
Starred bool `json:"starred"`
StarredAt time.Time `json:"starredAt"`
ID string `json:"id"`
Name string `json:"name"`
EmbedArtPath string `json:"embedArtPath"`
ArtistID string `json:"artistId"`
Artist string `json:"artist"`
AlbumArtistID string `json:"albumArtistId"`
AlbumArtist string `json:"albumArtist"`
AllArtistIds string `json:"allArtistIds"`
MaxYear int `json:"maxYear"`
MinYear int `json:"minYear"`
Compilation bool `json:"compilation"`
SongCount int `json:"songCount"`
Duration float64 `json:"duration"`
Size int `json:"size"`
Genre string `json:"genre"`
Genres []struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"genres"`
FullText string `json:"fullText"`
OrderAlbumName string `json:"orderAlbumName"`
OrderAlbumArtistName string `json:"orderAlbumArtistName"`
ImageFiles string `json:"imageFiles"`
Paths string `json:"paths"`
SmallImageURL string `json:"smallImageUrl"`
MediumImageURL string `json:"mediumImageUrl"`
LargeImageURL string `json:"largeImageUrl"`
ExternalURL string `json:"externalUrl"`
ExternalInfoUpdatedAt time.Time `json:"externalInfoUpdatedAt"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}