Implement single artist view
This commit is contained in:
parent
eb8b6df527
commit
899d63bb36
11 changed files with 369 additions and 104 deletions
11
src/elekf/library/album_utils.gleam
Normal file
11
src/elekf/library/album_utils.gleam
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import gleam/list
|
||||||
|
import elekf/library.{Library}
|
||||||
|
import elekf/library/album.{Album}
|
||||||
|
|
||||||
|
/// Get the individual tracks in an album.
|
||||||
|
pub fn get_tracks(library: Library, album: Album) {
|
||||||
|
list.map(
|
||||||
|
album.tracks,
|
||||||
|
fn(track_id) { #(track_id, library.assert_track(library, track_id)) },
|
||||||
|
)
|
||||||
|
}
|
|
@ -16,14 +16,18 @@ import ibroadcast/authed_request.{RequestConfig}
|
||||||
import elekf/api/base_request_config.{base_request_config}
|
import elekf/api/base_request_config.{base_request_config}
|
||||||
import elekf/utils/http
|
import elekf/utils/http
|
||||||
import elekf/library.{Library}
|
import elekf/library.{Library}
|
||||||
|
import elekf/library/artist.{Artist}
|
||||||
import elekf/library/track.{Track}
|
import elekf/library/track.{Track}
|
||||||
import elekf/transfer/library as library_transfer
|
import elekf/transfer/library as library_transfer
|
||||||
import elekf/web/components/player
|
import elekf/web/components/player
|
||||||
|
import elekf/web/components/library_item.{LibraryItem}
|
||||||
import elekf/web/components/library_view
|
import elekf/web/components/library_view
|
||||||
import elekf/web/components/library_views/tracks_view
|
import elekf/web/components/library_views/tracks_view
|
||||||
import elekf/web/components/library_views/artists_view
|
import elekf/web/components/library_views/artists_view
|
||||||
import elekf/web/components/library_views/albums_view
|
import elekf/web/components/library_views/albums_view
|
||||||
|
import elekf/web/components/library_views/single_artist_view
|
||||||
import elekf/web/events/start_play
|
import elekf/web/events/start_play
|
||||||
|
import elekf/web/events/show_artist
|
||||||
import elekf/web/utils
|
import elekf/web/utils
|
||||||
import elekf/web/components/icon.{Alt, icon}
|
import elekf/web/components/icon.{Alt, icon}
|
||||||
|
|
||||||
|
@ -58,6 +62,7 @@ pub type Msg {
|
||||||
LibraryResult(Result(library_api.ResponseData, http.ResponseError))
|
LibraryResult(Result(library_api.ResponseData, http.ResponseError))
|
||||||
PlayerMsg(player.Msg)
|
PlayerMsg(player.Msg)
|
||||||
StartPlay(PlayQueue, Int)
|
StartPlay(PlayQueue, Int)
|
||||||
|
ShowArtist(LibraryItem(Artist))
|
||||||
ChangeView(library_view.View)
|
ChangeView(library_view.View)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,6 +137,10 @@ pub fn update(model: Model, msg) {
|
||||||
let #(status, effect) = handle_start_play(model, tracks, position)
|
let #(status, effect) = handle_start_play(model, tracks, position)
|
||||||
#(Model(..model, play_status: status), effect)
|
#(Model(..model, play_status: status), effect)
|
||||||
}
|
}
|
||||||
|
ShowArtist(artist) -> #(
|
||||||
|
Model(..model, library_view: library_view.SingleArtist(artist)),
|
||||||
|
effect.none(),
|
||||||
|
)
|
||||||
PlayerMsg(player.NextTrack) | PlayerMsg(player.PrevTrack) -> {
|
PlayerMsg(player.NextTrack) | PlayerMsg(player.PrevTrack) -> {
|
||||||
if_player(
|
if_player(
|
||||||
model,
|
model,
|
||||||
|
@ -227,7 +236,7 @@ pub fn view(model: Model) {
|
||||||
artists_view.render(
|
artists_view.render(
|
||||||
model.library,
|
model.library,
|
||||||
model.settings,
|
model.settings,
|
||||||
[attribute.id("artists-view"), start_play.on(StartPlay)],
|
[attribute.id("artists-view"), show_artist.on(ShowArtist)],
|
||||||
)
|
)
|
||||||
library_view.Albums ->
|
library_view.Albums ->
|
||||||
albums_view.render(
|
albums_view.render(
|
||||||
|
@ -235,6 +244,13 @@ pub fn view(model: Model) {
|
||||||
model.settings,
|
model.settings,
|
||||||
[attribute.id("albums-view"), start_play.on(StartPlay)],
|
[attribute.id("albums-view"), start_play.on(StartPlay)],
|
||||||
)
|
)
|
||||||
|
library_view.SingleArtist(artist_info) ->
|
||||||
|
single_artist_view.render(
|
||||||
|
model.library,
|
||||||
|
artist_info,
|
||||||
|
model.settings,
|
||||||
|
[attribute.id("single-artist-view"), start_play.on(StartPlay)],
|
||||||
|
)
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
3
src/elekf/web/components/library_item.gleam
Normal file
3
src/elekf/web/components/library_item.gleam
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/// An item in the library with its ID.
|
||||||
|
pub type LibraryItem(a) =
|
||||||
|
#(Int, a)
|
|
@ -14,14 +14,13 @@ import lustre/attribute
|
||||||
import lustre/event
|
import lustre/event
|
||||||
import elekf/library.{Library}
|
import elekf/library.{Library}
|
||||||
import elekf/library/track.{Track}
|
import elekf/library/track.{Track}
|
||||||
|
import elekf/library/artist.{Artist}
|
||||||
import elekf/web/events/start_play
|
import elekf/web/events/start_play
|
||||||
|
import elekf/web/events/show_artist
|
||||||
import elekf/web/components/search
|
import elekf/web/components/search
|
||||||
|
import elekf/web/components/library_item.{LibraryItem}
|
||||||
import elekf/web/common
|
import elekf/web/common
|
||||||
|
|
||||||
/// An item in the library with its ID.
|
|
||||||
pub type LibraryItem(a) =
|
|
||||||
#(Int, a)
|
|
||||||
|
|
||||||
/// Function to get the data of the view from the library.
|
/// Function to get the data of the view from the library.
|
||||||
pub type DataGetter(a) =
|
pub type DataGetter(a) =
|
||||||
fn(Library) -> List(LibraryItem(a))
|
fn(Library) -> List(LibraryItem(a))
|
||||||
|
@ -51,7 +50,7 @@ pub type View {
|
||||||
/// All artists.
|
/// All artists.
|
||||||
Artists
|
Artists
|
||||||
/// Albums of a single artist.
|
/// Albums of a single artist.
|
||||||
/// SingleArtist(Int, Artist)
|
SingleArtist(LibraryItem(Artist))
|
||||||
/// Tracks of a single artist.
|
/// Tracks of a single artist.
|
||||||
/// SingleArtistTracks(Int, Artist)
|
/// SingleArtistTracks(Int, Artist)
|
||||||
/// All tracks.
|
/// All tracks.
|
||||||
|
@ -63,6 +62,7 @@ pub type Msg {
|
||||||
SettingsUpdated(option.Option(common.Settings))
|
SettingsUpdated(option.Option(common.Settings))
|
||||||
ShuffleAll
|
ShuffleAll
|
||||||
StartPlay(List(LibraryItem(Track)), Int)
|
StartPlay(List(LibraryItem(Track)), Int)
|
||||||
|
ShowArtist(LibraryItem(Artist))
|
||||||
Search(search.Msg)
|
Search(search.Msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,10 +98,16 @@ pub fn register(
|
||||||
fn() { init(library.empty(), data_getter, shuffler) },
|
fn() { init(library.empty(), data_getter, shuffler) },
|
||||||
update,
|
update,
|
||||||
generate_view(item_view, search_filter),
|
generate_view(item_view, search_filter),
|
||||||
map.from_list([#("library", library_decode), #("settings", settings_decode)]),
|
generic_attributes(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the generic properties common to all library views, that can be input
|
||||||
|
/// into the Lustre component attribute change
|
||||||
|
pub fn generic_attributes() {
|
||||||
|
map.from_list([#("library", library_decode), #("settings", settings_decode)])
|
||||||
|
}
|
||||||
|
|
||||||
/// Render the component using a custom element.
|
/// Render the component using a custom element.
|
||||||
pub fn render(
|
pub fn render(
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -111,7 +117,7 @@ pub fn render(
|
||||||
) {
|
) {
|
||||||
element.element(
|
element.element(
|
||||||
name,
|
name,
|
||||||
list.concat([
|
list.flatten([
|
||||||
[
|
[
|
||||||
attribute.property("library", library),
|
attribute.property("library", library),
|
||||||
attribute.property("settings", settings),
|
attribute.property("settings", settings),
|
||||||
|
@ -122,7 +128,7 @@ pub fn render(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(library, data_getter, shuffler) {
|
pub fn init(library, data_getter, shuffler) {
|
||||||
#(
|
#(
|
||||||
Model(
|
Model(
|
||||||
library,
|
library,
|
||||||
|
@ -136,7 +142,7 @@ fn init(library, data_getter, shuffler) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(model, msg) {
|
pub fn update(model, msg) {
|
||||||
case msg {
|
case msg {
|
||||||
LibraryUpdated(library) -> #(
|
LibraryUpdated(library) -> #(
|
||||||
Model(..model, library: library, data: model.data_getter(library)),
|
Model(..model, library: library, data: model.data_getter(library)),
|
||||||
|
@ -148,6 +154,7 @@ fn update(model, msg) {
|
||||||
)
|
)
|
||||||
ShuffleAll -> #(model, shuffle_all(model))
|
ShuffleAll -> #(model, shuffle_all(model))
|
||||||
StartPlay(tracks, position) -> #(model, start_play.emit(tracks, position))
|
StartPlay(tracks, position) -> #(model, start_play.emit(tracks, position))
|
||||||
|
ShowArtist(artist) -> #(model, show_artist.emit(artist))
|
||||||
Search(search_msg) -> {
|
Search(search_msg) -> {
|
||||||
let search_model = search.update(model.search, search_msg)
|
let search_model = search.update(model.search, search_msg)
|
||||||
#(Model(..model, search: search_model), effect.none())
|
#(Model(..model, search: search_model), effect.none())
|
||||||
|
@ -155,8 +162,11 @@ fn update(model, msg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_view(item_view: ItemView(a), search_filter: SearchFilter(a)) {
|
pub fn library_view(
|
||||||
fn(model: Model(a)) {
|
model: Model(a),
|
||||||
|
item_view: ItemView(a),
|
||||||
|
search_filter: SearchFilter(a),
|
||||||
|
) {
|
||||||
let items = case model.search.search_text {
|
let items = case model.search.search_text {
|
||||||
"" -> model.data
|
"" -> model.data
|
||||||
txt ->
|
txt ->
|
||||||
|
@ -181,7 +191,10 @@ fn generate_view(item_view: ItemView(a), search_filter: SearchFilter(a)) {
|
||||||
list.index_map(items, fn(i, item) { item_view(model, items, i, item) }),
|
list.index_map(items, fn(i, item) { item_view(model, items, i, item) }),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_view(item_view: ItemView(a), search_filter: SearchFilter(a)) {
|
||||||
|
library_view(_, item_view, search_filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shuffle_all(model: Model(a)) {
|
fn shuffle_all(model: Model(a)) {
|
||||||
|
|
64
src/elekf/web/components/library_views/album_item.gleam
Normal file
64
src/elekf/web/components/library_views/album_item.gleam
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
//// Library item view for a single album.
|
||||||
|
|
||||||
|
import gleam/list
|
||||||
|
import gleam/option
|
||||||
|
import gleam/int
|
||||||
|
import lustre/element/html.{div, h3, img, p}
|
||||||
|
import lustre/element.{text}
|
||||||
|
import lustre/attribute
|
||||||
|
import lustre/event
|
||||||
|
import ibroadcast/artwork
|
||||||
|
import elekf/library
|
||||||
|
import elekf/library/album.{Album}
|
||||||
|
import elekf/library/album_utils
|
||||||
|
import elekf/web/components/library_view.{Model, StartPlay}
|
||||||
|
import elekf/web/components/library_item.{LibraryItem}
|
||||||
|
|
||||||
|
pub fn view(
|
||||||
|
model: Model(Album),
|
||||||
|
_items: List(LibraryItem(Album)),
|
||||||
|
_index: Int,
|
||||||
|
item: LibraryItem(Album),
|
||||||
|
) {
|
||||||
|
let #(album_id, album) = item
|
||||||
|
let tracks = album_utils.get_tracks(model.library, album)
|
||||||
|
let artist_name = case album.artist_id {
|
||||||
|
0 -> "Unknown artist"
|
||||||
|
id -> library.assert_artist(model.library, id).name
|
||||||
|
}
|
||||||
|
let assert Ok(first_track) = list.first(tracks)
|
||||||
|
|
||||||
|
div(
|
||||||
|
[
|
||||||
|
attribute.id("album-list-" <> int.to_string(album_id)),
|
||||||
|
attribute.class("library-item album-item"),
|
||||||
|
attribute.type_("button"),
|
||||||
|
event.on_click(StartPlay(tracks, 0)),
|
||||||
|
attribute.attribute("role", "button"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
case model.settings, { first_track.1 }.artwork_id {
|
||||||
|
option.Some(s), id if id != 0 ->
|
||||||
|
img([
|
||||||
|
attribute.class("artist-image"),
|
||||||
|
attribute.alt("artist.name"),
|
||||||
|
attribute.src(artwork.url(
|
||||||
|
s.artwork_server,
|
||||||
|
int.to_string(id),
|
||||||
|
artwork.S300,
|
||||||
|
)),
|
||||||
|
attribute.attribute("loading", "lazy"),
|
||||||
|
attribute.width(300),
|
||||||
|
])
|
||||||
|
_, _ ->
|
||||||
|
div([attribute.class("artist-image-placeholder")], [text(album.name)])
|
||||||
|
},
|
||||||
|
h3([attribute.class("album-title")], [text(album.name)]),
|
||||||
|
p([attribute.class("album-artist")], [text(artist_name)]),
|
||||||
|
p(
|
||||||
|
[attribute.class("album-tracks")],
|
||||||
|
[text(int.to_string(list.length(album.tracks)) <> " tracks")],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,18 +1,16 @@
|
||||||
//// A library view to all of the albums in the library.
|
//// A library view to all of the albums in the library.
|
||||||
|
|
||||||
import gleam/string
|
import gleam/string
|
||||||
import gleam/int
|
|
||||||
import gleam/list
|
import gleam/list
|
||||||
import gleam/map
|
import gleam/map
|
||||||
import gleam/option
|
import gleam/option
|
||||||
import lustre/element/html.{div, h3, img, p}
|
|
||||||
import lustre/element.{text}
|
|
||||||
import lustre/attribute
|
import lustre/attribute
|
||||||
import lustre/event
|
|
||||||
import ibroadcast/artwork
|
|
||||||
import elekf/library.{Library}
|
import elekf/library.{Library}
|
||||||
import elekf/library/album.{Album}
|
import elekf/library/album.{Album}
|
||||||
import elekf/web/components/library_view.{LibraryItem, Model, StartPlay}
|
import elekf/library/album_utils
|
||||||
|
import elekf/web/components/library_view
|
||||||
|
import elekf/web/components/library_item.{LibraryItem}
|
||||||
|
import elekf/web/components/library_views/album_item
|
||||||
import elekf/web/common
|
import elekf/web/common
|
||||||
|
|
||||||
const component_name = "albums-view"
|
const component_name = "albums-view"
|
||||||
|
@ -22,7 +20,7 @@ pub fn register() {
|
||||||
library_view.register(
|
library_view.register(
|
||||||
component_name,
|
component_name,
|
||||||
data_getter,
|
data_getter,
|
||||||
item_view,
|
album_item.view,
|
||||||
shuffler,
|
shuffler,
|
||||||
search_filter,
|
search_filter,
|
||||||
)
|
)
|
||||||
|
@ -44,66 +42,10 @@ fn data_getter(library: Library) {
|
||||||
fn shuffler(library, items: List(LibraryItem(Album))) {
|
fn shuffler(library, items: List(LibraryItem(Album))) {
|
||||||
items
|
items
|
||||||
|> list.shuffle()
|
|> list.shuffle()
|
||||||
|> list.map(fn(album) { get_tracks(library, album.1) })
|
|> list.map(fn(album) { album_utils.get_tracks(library, album.1) })
|
||||||
|> list.flatten()
|
|> list.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_filter(item: Album, search_text: String) {
|
fn search_filter(item: Album, search_text: String) {
|
||||||
string.contains(item.name_lower, search_text)
|
string.contains(item.name_lower, search_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn item_view(
|
|
||||||
model: Model(Album),
|
|
||||||
_items: List(LibraryItem(Album)),
|
|
||||||
_index: Int,
|
|
||||||
item: LibraryItem(Album),
|
|
||||||
) {
|
|
||||||
let #(album_id, album) = item
|
|
||||||
let tracks = get_tracks(model.library, album)
|
|
||||||
let artist_name = case album.artist_id {
|
|
||||||
0 -> "Unknown artist"
|
|
||||||
id -> library.assert_artist(model.library, id).name
|
|
||||||
}
|
|
||||||
let assert Ok(first_track) = list.first(tracks)
|
|
||||||
|
|
||||||
div(
|
|
||||||
[
|
|
||||||
attribute.id("album-list-" <> int.to_string(album_id)),
|
|
||||||
attribute.class("library-item album-item"),
|
|
||||||
attribute.type_("button"),
|
|
||||||
event.on_click(StartPlay(tracks, 0)),
|
|
||||||
attribute.attribute("role", "button"),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
case model.settings, { first_track.1 }.artwork_id {
|
|
||||||
option.Some(s), id if id != 0 ->
|
|
||||||
img([
|
|
||||||
attribute.class("artist-image"),
|
|
||||||
attribute.alt("artist.name"),
|
|
||||||
attribute.src(artwork.url(
|
|
||||||
s.artwork_server,
|
|
||||||
int.to_string(id),
|
|
||||||
artwork.S300,
|
|
||||||
)),
|
|
||||||
attribute.attribute("loading", "lazy"),
|
|
||||||
attribute.width(300),
|
|
||||||
])
|
|
||||||
_, _ ->
|
|
||||||
div([attribute.class("artist-image-placeholder")], [text(album.name)])
|
|
||||||
},
|
|
||||||
h3([attribute.class("album-title")], [text(album.name)]),
|
|
||||||
p([attribute.class("album-artist")], [text(artist_name)]),
|
|
||||||
p(
|
|
||||||
[attribute.class("album-tracks")],
|
|
||||||
[text(int.to_string(list.length(album.tracks)) <> " tracks")],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_tracks(library: Library, album: Album) {
|
|
||||||
list.map(
|
|
||||||
album.tracks,
|
|
||||||
fn(track_id) { #(track_id, library.assert_track(library, track_id)) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,7 +12,8 @@ import lustre/event
|
||||||
import ibroadcast/artwork
|
import ibroadcast/artwork
|
||||||
import elekf/library.{Library}
|
import elekf/library.{Library}
|
||||||
import elekf/library/artist.{Artist}
|
import elekf/library/artist.{Artist}
|
||||||
import elekf/web/components/library_view.{LibraryItem, Model, StartPlay}
|
import elekf/web/components/library_view.{Model, ShowArtist}
|
||||||
|
import elekf/web/components/library_item.{LibraryItem}
|
||||||
import elekf/web/common
|
import elekf/web/common
|
||||||
|
|
||||||
const component_name = "artists-view"
|
const component_name = "artists-view"
|
||||||
|
@ -68,13 +69,12 @@ fn item_view(
|
||||||
item: LibraryItem(Artist),
|
item: LibraryItem(Artist),
|
||||||
) {
|
) {
|
||||||
let #(artist_id, artist) = item
|
let #(artist_id, artist) = item
|
||||||
let tracks = get_tracks(model.library, artist)
|
|
||||||
div(
|
div(
|
||||||
[
|
[
|
||||||
attribute.id("artist-list-" <> int.to_string(artist_id)),
|
attribute.id("artist-list-" <> int.to_string(artist_id)),
|
||||||
attribute.class("library-item artist-item"),
|
attribute.class("library-item artist-item"),
|
||||||
attribute.type_("button"),
|
attribute.type_("button"),
|
||||||
event.on_click(StartPlay(tracks, 0)),
|
event.on_click(ShowArtist(item)),
|
||||||
attribute.attribute("role", "button"),
|
attribute.attribute("role", "button"),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|
175
src/elekf/web/components/library_views/single_artist_view.gleam
Normal file
175
src/elekf/web/components/library_views/single_artist_view.gleam
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
//// A library view to a single artist's albums.
|
||||||
|
|
||||||
|
import gleam/string
|
||||||
|
import gleam/list
|
||||||
|
import gleam/map
|
||||||
|
import gleam/option
|
||||||
|
import gleam/dynamic
|
||||||
|
import gleam/result
|
||||||
|
import lustre
|
||||||
|
import lustre/element/html.{div, header}
|
||||||
|
import lustre/element.{text}
|
||||||
|
import lustre/attribute
|
||||||
|
import lustre/effect
|
||||||
|
import elekf/library.{Library}
|
||||||
|
import elekf/library/album.{Album}
|
||||||
|
import elekf/library/album_utils
|
||||||
|
import elekf/library/artist.{Artist}
|
||||||
|
import elekf/web/components/library_view
|
||||||
|
import elekf/web/components/library_item.{LibraryItem}
|
||||||
|
import elekf/web/components/library_views/album_item
|
||||||
|
import elekf/web/common
|
||||||
|
|
||||||
|
const component_name = "single-artist-view"
|
||||||
|
|
||||||
|
type Model {
|
||||||
|
Model(
|
||||||
|
library_view: library_view.Model(Album),
|
||||||
|
artist: option.Option(LibraryItem(Artist)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Msg {
|
||||||
|
LibraryViewMsg(library_view.Msg)
|
||||||
|
ArtistUpdated(option.Option(LibraryItem(Artist)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register the single artist view as a custom element.
|
||||||
|
pub fn register() {
|
||||||
|
lustre.component(
|
||||||
|
component_name,
|
||||||
|
init,
|
||||||
|
update,
|
||||||
|
generate_view(search_filter),
|
||||||
|
map.merge(
|
||||||
|
library_view.generic_attributes()
|
||||||
|
|> map.map_values(fn(_key, decoder) {
|
||||||
|
fn(data: dynamic.Dynamic) {
|
||||||
|
data
|
||||||
|
|> decoder()
|
||||||
|
|> result.map(LibraryViewMsg)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
map.from_list([#("artist", artist_decode)]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render the single artist view.
|
||||||
|
pub fn render(
|
||||||
|
library: Library,
|
||||||
|
artist: LibraryItem(Artist),
|
||||||
|
settings: option.Option(common.Settings),
|
||||||
|
extra_attrs: List(attribute.Attribute(msg)),
|
||||||
|
) {
|
||||||
|
library_view.render(
|
||||||
|
component_name,
|
||||||
|
library,
|
||||||
|
settings,
|
||||||
|
list.flatten([
|
||||||
|
[attribute.property("artist", option.Some(artist))],
|
||||||
|
extra_attrs,
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init() {
|
||||||
|
let #(lib_m, lib_e) =
|
||||||
|
library_view.init(
|
||||||
|
library.empty(),
|
||||||
|
fn(_) -> List(LibraryItem(Album)) { [] },
|
||||||
|
shuffler,
|
||||||
|
)
|
||||||
|
|
||||||
|
#(
|
||||||
|
Model(artist: option.None, library_view: lib_m),
|
||||||
|
effect.map(lib_e, LibraryViewMsg),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(model: Model, msg) {
|
||||||
|
case msg {
|
||||||
|
ArtistUpdated(option.Some(artist)) -> #(
|
||||||
|
Model(
|
||||||
|
artist: option.Some(artist),
|
||||||
|
library_view: library_view.Model(
|
||||||
|
..model.library_view,
|
||||||
|
data: data_getter(model.library_view.library, artist),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
effect.none(),
|
||||||
|
)
|
||||||
|
ArtistUpdated(option.None) -> #(
|
||||||
|
Model(
|
||||||
|
artist: option.None,
|
||||||
|
library_view: library_view.Model(..model.library_view, data: []),
|
||||||
|
),
|
||||||
|
effect.none(),
|
||||||
|
)
|
||||||
|
LibraryViewMsg(lib_msg) -> {
|
||||||
|
let #(lib_m, lib_e) = library_view.update(model.library_view, lib_msg)
|
||||||
|
#(Model(..model, library_view: lib_m), effect.map(lib_e, LibraryViewMsg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_getter(library: Library, artist: LibraryItem(Artist)) {
|
||||||
|
library.albums
|
||||||
|
|> map.fold(
|
||||||
|
[],
|
||||||
|
fn(acc, key, val) {
|
||||||
|
case val.artist_id == artist.0 {
|
||||||
|
True -> [#(key, val), ..acc]
|
||||||
|
False -> acc
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shuffler(library, items: List(LibraryItem(Album))) {
|
||||||
|
items
|
||||||
|
|> list.shuffle()
|
||||||
|
|> list.map(fn(album) { album_utils.get_tracks(library, album.1) })
|
||||||
|
|> list.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_filter(item: Album, search_text: String) {
|
||||||
|
string.contains(item.name_lower, search_text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_view(search_filter: library_view.SearchFilter(Album)) {
|
||||||
|
fn(model: Model) {
|
||||||
|
case model.artist {
|
||||||
|
option.None ->
|
||||||
|
div(
|
||||||
|
[attribute.class("library-view-not-specified")],
|
||||||
|
[text("Artist not specified.")],
|
||||||
|
)
|
||||||
|
option.Some(artist) -> view(model, artist, search_filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(
|
||||||
|
model: Model,
|
||||||
|
artist: LibraryItem(Artist),
|
||||||
|
search_filter: library_view.SearchFilter(Album),
|
||||||
|
) {
|
||||||
|
div(
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
header([attribute.class("library-header")], [text({ artist.1 }.name)]),
|
||||||
|
library_view.library_view(
|
||||||
|
model.library_view,
|
||||||
|
album_item.view,
|
||||||
|
search_filter,
|
||||||
|
)
|
||||||
|
|> element.map(LibraryViewMsg),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn artist_decode(data: dynamic.Dynamic) {
|
||||||
|
let artist: option.Option(LibraryItem(Artist)) = dynamic.unsafe_coerce(data)
|
||||||
|
Ok(ArtistUpdated(artist))
|
||||||
|
}
|
|
@ -11,7 +11,8 @@ import lustre/attribute
|
||||||
import lustre/event
|
import lustre/event
|
||||||
import elekf/library.{Library}
|
import elekf/library.{Library}
|
||||||
import elekf/library/track.{Track}
|
import elekf/library/track.{Track}
|
||||||
import elekf/web/components/library_view.{LibraryItem, Model, StartPlay}
|
import elekf/web/components/library_view.{Model, StartPlay}
|
||||||
|
import elekf/web/components/library_item.{LibraryItem}
|
||||||
import elekf/web/common
|
import elekf/web/common
|
||||||
|
|
||||||
const component_name = "tracks-view"
|
const component_name = "tracks-view"
|
||||||
|
|
38
src/elekf/web/events/show_artist.gleam
Normal file
38
src/elekf/web/events/show_artist.gleam
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import gleam/result
|
||||||
|
import gleam/dynamic
|
||||||
|
import lustre/event
|
||||||
|
import elekf/library/artist.{Artist}
|
||||||
|
import elekf/web/components/library_item.{LibraryItem}
|
||||||
|
import elekf/utils/custom_event.{CustomEvent}
|
||||||
|
|
||||||
|
const event_name = "show-artist"
|
||||||
|
|
||||||
|
pub type EventData {
|
||||||
|
EventData(artist: LibraryItem(Artist))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit(artist) {
|
||||||
|
event.emit(event_name, EventData(artist))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on(msg: fn(LibraryItem(Artist)) -> b) {
|
||||||
|
event.on(
|
||||||
|
event_name,
|
||||||
|
fn(data) {
|
||||||
|
data
|
||||||
|
|> decoder
|
||||||
|
|> result.map(fn(e) { msg(e.artist) })
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decoder(data: dynamic.Dynamic) {
|
||||||
|
let e: CustomEvent = dynamic.unsafe_coerce(data)
|
||||||
|
let detail = custom_event.get_detail(e)
|
||||||
|
let event_data: EventData =
|
||||||
|
detail
|
||||||
|
|> dynamic.from()
|
||||||
|
|> dynamic.unsafe_coerce()
|
||||||
|
|
||||||
|
Ok(event_data)
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ import elekf/web/utils
|
||||||
import elekf/web/components/library_views/tracks_view
|
import elekf/web/components/library_views/tracks_view
|
||||||
import elekf/web/components/library_views/artists_view
|
import elekf/web/components/library_views/artists_view
|
||||||
import elekf/web/components/library_views/albums_view
|
import elekf/web/components/library_views/albums_view
|
||||||
|
import elekf/web/components/library_views/single_artist_view
|
||||||
import elekf/api/auth/storage as auth_storage
|
import elekf/api/auth/storage as auth_storage
|
||||||
import elekf/api/auth/models as auth_models
|
import elekf/api/auth/models as auth_models
|
||||||
import elekf/utils/option as option_utils
|
import elekf/utils/option as option_utils
|
||||||
|
@ -39,6 +40,7 @@ pub fn main() {
|
||||||
let assert Ok(_) = tracks_view.register()
|
let assert Ok(_) = tracks_view.register()
|
||||||
let assert Ok(_) = artists_view.register()
|
let assert Ok(_) = artists_view.register()
|
||||||
let assert Ok(_) = albums_view.register()
|
let assert Ok(_) = albums_view.register()
|
||||||
|
let assert Ok(_) = single_artist_view.register()
|
||||||
|
|
||||||
let app = lustre.application(init, update, view)
|
let app = lustre.application(init, update, view)
|
||||||
let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
|
let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
|
||||||
|
|
Loading…
Reference in a new issue