diff --git a/src/elekf/web/authed_view.gleam b/src/elekf/web/authed_view.gleam index 6a6a291..c5a8e6b 100644 --- a/src/elekf/web/authed_view.gleam +++ b/src/elekf/web/authed_view.gleam @@ -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) + } +} diff --git a/src/elekf/web/components/link.gleam b/src/elekf/web/components/link.gleam new file mode 100644 index 0000000..04422a5 --- /dev/null +++ b/src/elekf/web/components/link.gleam @@ -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, + ) +} diff --git a/src/elekf/web/router.gleam b/src/elekf/web/router.gleam new file mode 100644 index 0000000..e3574c6 --- /dev/null +++ b/src/elekf/web/router.gleam @@ -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, "/") +} diff --git a/style.css b/style.css index 400625d..f76de8f 100644 --- a/style.css +++ b/style.css @@ -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 {