Initial real routing work

This commit is contained in:
Mikko Ahlroth 2024-02-25 22:00:15 +02:00
parent 22f8574973
commit 7b3e06beb3
4 changed files with 155 additions and 57 deletions

View file

@ -6,12 +6,12 @@ import gleam/list
import gleam/option
import gleam/float
import gleam/int
import gleam/result
import gleam/javascript/promise
import lustre/element.{text}
import lustre/element/html.{div, nav, p}
import lustre/attribute
import lustre/effect
import lustre/event
import birl
import ibroadcast/library/library as library_api
import ibroadcast/authed_request.{type RequestConfig, RequestConfig}
@ -23,6 +23,7 @@ import elekf/library.{type Library}
import elekf/library/artist.{type Artist}
import elekf/library/track.{type Track}
import elekf/transfer/library as library_transfer
import elekf/web/router
import elekf/web/components/player
import elekf/web/components/library_item.{type LibraryItem}
import elekf/web/components/library_view
@ -31,7 +32,7 @@ import elekf/web/components/library_views/artists_view
import elekf/web/components/library_views/albums_view
import elekf/web/components/library_views/single_artist_view
import elekf/web/components/button_group
import elekf/web/components/button
import elekf/web/components/link
import elekf/web/events/start_play
import elekf/web/events/show_artist
import elekf/web/utils
@ -69,7 +70,7 @@ pub type Model {
settings: option.Option(common.Settings),
request_config: RequestConfig,
play_status: PlayStatus,
view_stack: List(library_view.View),
view: library_view.View,
)
}
@ -78,8 +79,7 @@ pub type Msg {
LibraryResult(Result(library_api.ResponseData, http.ResponseError))
PlayerMsg(player.Msg)
StartPlay(PlayQueue, Int)
ShowArtist(LibraryItem(Artist))
ChangeView(library_view.View)
Router(router.Msg)
}
pub fn init(auth_data: common.AuthData) {
@ -90,10 +90,14 @@ pub fn init(auth_data: common.AuthData) {
settings: option.None,
request_config: form_request_config(auth_data),
play_status: NoTracks,
view_stack: [library_view.Tracks],
view: library_view.Tracks,
)
#(model, load_library(model))
let router_effect =
effect.from(fn(dispatch) { router.init(dispatch) })
|> effect.map(Router)
#(model, effect.batch([load_library(model), router_effect]))
}
pub fn update(model: Model, msg) {
@ -144,15 +148,6 @@ pub fn update(model: Model, msg) {
let #(status, effect) = handle_start_play(model, tracks, position)
#(Model(..model, play_status: status), effect)
}
ShowArtist(artist) -> #(
Model(
..model,
view_stack: list.append(model.view_stack, [
library_view.SingleArtist(artist),
]),
),
effect.none(),
)
PlayerMsg(player.NextTrack) | PlayerMsg(player.PrevTrack) -> {
if_player(model, fn(info) {
let next_index = case msg {
@ -187,8 +182,9 @@ pub fn update(model: Model, msg) {
)
})
}
ChangeView(view) -> {
#(Model(..model, view_stack: [view]), effect.none())
Router(router.RouteChanged(route)) -> {
let view = result.unwrap(route_to_view(route), library_view.Tracks)
#(Model(..model, view: view), effect.none())
}
}
}
@ -209,50 +205,42 @@ pub fn view(model: Model) {
div([attribute.id("authed-view-library")], [
nav([attribute.id("library-top-nav")], [
button_group.view("", [], [
button.view("", [event.on_click(ChangeView(library_view.Tracks))], [
link.view(router.to_hash(router.TrackList), "", [], [
icon("music-note-beamed", Alt("Tracks")),
]),
button.view("", [event.on_click(ChangeView(library_view.Artists))], [
link.view(router.to_hash(router.ArtistList), "", [], [
icon("file-person", Alt("Artists")),
]),
button.view("", [event.on_click(ChangeView(library_view.Albums))], [
link.view(router.to_hash(router.AlbumList), "", [], [
icon("disc", Alt("Albums")),
]),
]),
]),
..list.map(model.view_stack, fn(view) {
case view {
library_view.Tracks ->
tracks_view.render(model.library, model.settings, [
attribute.id("tracks-view"),
attribute.class("glass-bg"),
start_play.on(StartPlay),
])
library_view.Artists ->
artists_view.render(model.library, model.settings, [
attribute.id("artists-view"),
attribute.class("glass-bg"),
show_artist.on(ShowArtist),
])
library_view.Albums ->
albums_view.render(model.library, model.settings, [
attribute.id("albums-view"),
attribute.class("glass-bg"),
start_play.on(StartPlay),
])
library_view.SingleArtist(artist_info) ->
single_artist_view.render(
model.library,
artist_info,
model.settings,
[
attribute.id("single-artist-view"),
attribute.class("glass-bg"),
start_play.on(StartPlay),
],
)
}
})
case model.view {
library_view.Tracks ->
tracks_view.render(model.library, model.settings, [
attribute.id("tracks-view"),
attribute.class("glass-bg"),
start_play.on(StartPlay),
])
library_view.Artists ->
artists_view.render(model.library, model.settings, [
attribute.id("artists-view"),
attribute.class("glass-bg"),
])
library_view.Albums ->
albums_view.render(model.library, model.settings, [
attribute.id("albums-view"),
attribute.class("glass-bg"),
start_play.on(StartPlay),
])
library_view.SingleArtist(artist_info) ->
single_artist_view.render(model.library, artist_info, model.settings, [
attribute.id("single-artist-view"),
attribute.class("glass-bg"),
start_play.on(StartPlay),
])
},
]),
div(
[
@ -388,3 +376,11 @@ fn maybe_send_history_status(play_info: PlayInfo, request_config: RequestConfig)
HistorySent -> play_info
}
}
fn route_to_view(route: router.Route) {
case route {
router.TrackList -> Ok(library_view.Tracks)
router.ArtistList -> Ok(library_view.Artists)
router.AlbumList -> Ok(library_view.Albums)
}
}

View file

@ -0,0 +1,19 @@
import lustre/attribute
import lustre/element
import lustre/element/html.{a}
pub fn view(
href: String,
class: String,
extra_attributes: List(attribute.Attribute(a)),
content: List(element.Element(a)),
) {
a(
[
attribute.href(href),
attribute.class("glass-button " <> class),
..extra_attributes
],
content,
)
}

View file

@ -0,0 +1,76 @@
import gleam/int
import gleam/string
import gleam/result
import plinth/browser/window
const artists = "artists"
const albums = "albums"
const settings = "settings"
pub type Msg {
RouteChanged(route: Route)
}
pub type Route {
TrackList
ArtistList
AlbumList
Artist(id: Int)
Album(id: Int)
Settings
}
pub fn init(dispatch: fn(Msg) -> Nil) {
window.add_event_listener("hashchange", fn(_e) {
let _ = run(dispatch)
Nil
})
}
pub fn run(dispatch: fn(Msg) -> Nil) {
use hash <- result.try(window.get_hash())
use route <- result.try(parse(hash))
Ok(dispatch(RouteChanged(route)))
}
pub fn to_hash(route: Route) {
"#" <> to_string(route)
}
fn parse(route: String) {
let parts =
route
|> string.drop_left(1)
|> string.split("/")
case parts {
[""] -> Ok(TrackList)
[a] if a == artists -> Ok(ArtistList)
[a] if a == albums -> Ok(AlbumList)
[a, id] -> {
case a, int.parse(id) {
a, Ok(id) if a == artists -> Ok(Artist(id))
a, Ok(id) if a == albums -> Ok(Album(id))
_, _ -> Error(Nil)
}
}
[s] if s == settings -> Ok(Settings)
_ -> Error(Nil)
}
}
fn to_string(route: Route) {
let parts = case route {
TrackList -> []
ArtistList -> [artists]
AlbumList -> [albums]
Artist(id) -> [artists, int.to_string(id)]
Album(id) -> [albums, int.to_string(id)]
Settings -> [settings]
}
"/" <> string.join(parts, "/")
}

View file

@ -70,6 +70,10 @@
.glass-button {
border-radius: var(--button-border-radius);
text-align: center;
color: var(--text-color);
text-decoration: none;
}
.button-small,
@ -89,6 +93,10 @@
align-items: stretch;
}
.button-group > * {
flex: 1;
}
.button-group .glass-button {
border-radius: 0;
border-right: none;
@ -296,10 +304,9 @@ single-artist-view {
height: 100%;
}
#library-top-nav button {
display: block;
width: 100%;
#library-top-nav .glass-button {
font-size: 2rem;
padding: 0;
}
#search-bar {