Add artist artwork

This commit is contained in:
Mikko Ahlroth 2023-10-23 20:23:10 +03:00
parent 2216d4b720
commit 960eaa73f7
9 changed files with 126 additions and 28 deletions

View file

@ -1,3 +1,5 @@
import gleam/option
pub type Artist {
Artist(
name: String,
@ -5,5 +7,6 @@ pub type Artist {
tracks: List(Int),
trashed: Bool,
rating: Int,
artwork_id: option.Option(String),
)
}

View file

@ -10,5 +10,6 @@ pub fn from(artist: APIArtist) {
tracks: artist.tracks,
trashed: artist.trashed,
rating: artist.rating,
artwork_id: artist.artwork_id,
)
}

View file

@ -220,16 +220,19 @@ pub fn view(model: Model) {
library_view.Tracks ->
tracks_view.render(
model.library,
model.settings,
[attribute.id("tracks-view"), start_play.on(StartPlay)],
)
library_view.Artists ->
artists_view.render(
model.library,
model.settings,
[attribute.id("artists-view"), start_play.on(StartPlay)],
)
library_view.Albums ->
albums_view.render(
model.library,
model.settings,
[attribute.id("albums-view"), start_play.on(StartPlay)],
)
},

View file

@ -5,6 +5,7 @@
import gleam/dynamic
import gleam/list
import gleam/map
import gleam/option
import lustre
import lustre/effect
import lustre/element.{text}
@ -15,6 +16,7 @@ import elekf/library.{Library}
import elekf/library/track.{Track}
import elekf/web/events/start_play
import elekf/web/components/search
import elekf/web/common
/// An item in the library with its ID.
pub type LibraryItem(a) =
@ -26,7 +28,8 @@ pub type DataGetter(a) =
/// A view that renders a single item from the library.
pub type ItemView(a) =
fn(Library, List(LibraryItem(a)), Int, LibraryItem(a)) -> element.Element(Msg)
fn(Model(a), List(LibraryItem(a)), Int, LibraryItem(a)) ->
element.Element(Msg)
/// A filter that gets the item and the current search string and must return
/// `True` if the item should be shown and `False` if not.
@ -57,6 +60,7 @@ pub type View {
pub type Msg {
LibraryUpdated(Library)
SettingsUpdated(option.Option(common.Settings))
ShuffleAll
StartPlay(List(LibraryItem(Track)), Int)
Search(search.Msg)
@ -69,6 +73,7 @@ pub type Model(a) {
data_getter: DataGetter(a),
shuffler: Shuffler(a),
search: search.Model,
settings: option.Option(common.Settings),
)
}
@ -93,7 +98,7 @@ pub fn register(
fn() { init(library.empty(), data_getter, shuffler) },
update,
generate_view(item_view, search_filter),
map.from_list([#("library", library_decode)]),
map.from_list([#("library", library_decode), #("settings", settings_decode)]),
)
}
@ -101,18 +106,32 @@ pub fn register(
pub fn render(
name: String,
library: Library,
settings: option.Option(common.Settings),
extra_attrs: List(attribute.Attribute(msg)),
) {
element.element(
name,
list.concat([[attribute.property("library", library)], extra_attrs]),
list.concat([
[
attribute.property("library", library),
attribute.property("settings", settings),
],
extra_attrs,
]),
[],
)
}
fn init(library, data_getter, shuffler) {
#(
Model(library, data_getter(library), data_getter, shuffler, search.init()),
Model(
library,
data_getter(library),
data_getter,
shuffler,
search.init(),
option.None,
),
effect.none(),
)
}
@ -123,6 +142,10 @@ fn update(model, msg) {
Model(..model, library: library, data: model.data_getter(library)),
effect.none(),
)
SettingsUpdated(settings) -> #(
Model(..model, settings: settings),
effect.none(),
)
ShuffleAll -> #(model, shuffle_all(model))
StartPlay(tracks, position) -> #(model, start_play.emit(tracks, position))
Search(search_msg) -> {
@ -155,10 +178,7 @@ fn generate_view(item_view: ItemView(a), search_filter: SearchFilter(a)) {
[h3([attribute.class("library-item-title")], [text("Shuffle all")])],
),
],
list.index_map(
items,
fn(i, item) { item_view(model.library, items, i, item) },
),
list.index_map(items, fn(i, item) { item_view(model, items, i, item) }),
),
)
}
@ -173,3 +193,8 @@ fn library_decode(data: dynamic.Dynamic) {
let library: Library = dynamic.unsafe_coerce(data)
Ok(LibraryUpdated(library))
}
fn settings_decode(data: dynamic.Dynamic) {
let settings: option.Option(common.Settings) = dynamic.unsafe_coerce(data)
Ok(SettingsUpdated(settings))
}

View file

@ -4,13 +4,15 @@ import gleam/string
import gleam/int
import gleam/list
import gleam/map
import gleam/option
import lustre/element/html.{div, h3, p}
import lustre/element.{text}
import lustre/attribute
import lustre/event
import elekf/library.{Library}
import elekf/library/album.{Album}
import elekf/web/components/library_view.{LibraryItem, StartPlay}
import elekf/web/components/library_view.{LibraryItem, Model, StartPlay}
import elekf/web/common
const component_name = "albums-view"
@ -26,8 +28,12 @@ pub fn register() {
}
/// Render the albums view.
pub fn render(library: Library, extra_attrs: List(attribute.Attribute(msg))) {
library_view.render(component_name, library, extra_attrs)
pub fn render(
library: Library,
settings: option.Option(common.Settings),
extra_attrs: List(attribute.Attribute(msg)),
) {
library_view.render(component_name, library, settings, extra_attrs)
}
fn data_getter(library: Library) {
@ -46,16 +52,16 @@ fn search_filter(item: Album, search_text: String) {
}
fn item_view(
library: Library,
model: Model(Album),
_items: List(LibraryItem(Album)),
_index: Int,
item: LibraryItem(Album),
) {
let #(album_id, album) = item
let tracks = get_tracks(library, album)
let tracks = get_tracks(model.library, album)
let artist_name = case album.artist_id {
0 -> "Unknown artist"
id -> library.assert_artist(library, id).name
id -> library.assert_artist(model.library, id).name
}
div(

View file

@ -4,13 +4,16 @@ import gleam/string
import gleam/int
import gleam/list
import gleam/map
import lustre/element/html.{div, h3, p}
import gleam/option
import lustre/element/html.{div, h3, img, p}
import lustre/element.{text}
import lustre/attribute
import lustre/event
import ibroadcast/artwork
import elekf/library.{Library}
import elekf/library/artist.{Artist}
import elekf/web/components/library_view.{LibraryItem, StartPlay}
import elekf/web/components/library_view.{LibraryItem, Model, StartPlay}
import elekf/web/common
const component_name = "artists-view"
@ -26,8 +29,12 @@ pub fn register() {
}
/// Render the artists view.
pub fn render(library: Library, extra_attrs: List(attribute.Attribute(msg))) {
library_view.render(component_name, library, extra_attrs)
pub fn render(
library: Library,
settings: option.Option(common.Settings),
extra_attrs: List(attribute.Attribute(msg)),
) {
library_view.render(component_name, library, settings, extra_attrs)
}
fn data_getter(library: Library) {
@ -55,13 +62,13 @@ fn search_filter(item: Artist, search_text: String) {
}
fn item_view(
library: Library,
model: Model(Artist),
_items: List(LibraryItem(Artist)),
_index: Int,
item: LibraryItem(Artist),
) {
let #(artist_id, artist) = item
let tracks = get_tracks(library, artist)
let tracks = get_tracks(model.library, artist)
div(
[
attribute.id("artist-list-" <> int.to_string(artist_id)),
@ -70,6 +77,20 @@ fn item_view(
attribute.attribute("role", "button"),
],
[
case model.settings, artist.artwork_id {
option.Some(s), option.Some(id) ->
img([
attribute.class("artist-image"),
attribute.alt("artist.name"),
attribute.src(artwork.url(s.artwork_server, id, artwork.S300)),
attribute.attribute("loading", "lazy"),
])
_, _ ->
div(
[attribute.class("artist-image-placeholder")],
[text(artist.name)],
)
},
h3([attribute.class("artist-title")], [text(artist.name)]),
p(
[attribute.class("artist-tracks")],

View file

@ -4,13 +4,15 @@ import gleam/string
import gleam/int
import gleam/list
import gleam/map
import gleam/option
import lustre/element/html.{div, h3, p}
import lustre/element.{text}
import lustre/attribute
import lustre/event
import elekf/library.{Library}
import elekf/library/track.{Track}
import elekf/web/components/library_view.{LibraryItem, StartPlay}
import elekf/web/components/library_view.{LibraryItem, Model, StartPlay}
import elekf/web/common
const component_name = "tracks-view"
@ -26,8 +28,12 @@ pub fn register() {
}
/// Render the tracks view.
pub fn render(library: Library, extra_attrs: List(attribute.Attribute(msg))) {
library_view.render(component_name, library, extra_attrs)
pub fn render(
library: Library,
settings: option.Option(common.Settings),
extra_attrs: List(attribute.Attribute(msg)),
) {
library_view.render(component_name, library, settings, extra_attrs)
}
fn data_getter(library: Library) {
@ -43,14 +49,14 @@ fn search_filter(item: Track, search_text: String) {
}
fn item_view(
library: Library,
model: Model(Track),
items: List(LibraryItem(Track)),
index: Int,
item: LibraryItem(Track),
) {
let #(track_id, track) = item
let album = library.assert_album(library, track.album_id)
let artist = library.assert_artist(library, track.artist_id)
let album = library.assert_album(model.library, track.album_id)
let artist = library.assert_artist(model.library, track.artist_id)
div(
[
attribute.id("track-list-" <> int.to_string(track_id)),

View file

@ -0,0 +1,22 @@
//// Artwork is fetched from the artwork API server, whose address can be found
//// in the library response.
/// The desired size of the image in pixels square.
pub type Size {
S150
S300
S1000
}
/// Get the URL to an artwork.
pub fn url(artwork_server: String, artwork_id: String, size: Size) {
artwork_server <> "/artwork/" <> artwork_id <> "-" <> size_arg(size)
}
fn size_arg(size: Size) {
case size {
S150 -> "150"
S300 -> "300"
S1000 -> "1000"
}
}

View file

@ -3,6 +3,7 @@ import gleam/json
import gleam/map.{Map}
import gleam/dynamic
import gleam/result
import gleam/option
import ibroadcast/servers
import ibroadcast/request.{DecodeFailed}
import ibroadcast/authed_request.{RequestConfig}
@ -23,7 +24,13 @@ pub type Album {
}
pub type Artist {
Artist(name: String, tracks: List(Int), trashed: Bool, rating: Int)
Artist(
name: String,
tracks: List(Int),
trashed: Bool,
rating: Int,
artwork_id: option.Option(String),
)
}
pub type Track {
@ -132,12 +139,16 @@ fn artists_decoder() {
}
fn artist_decoder() {
dynamic.decode4(
dynamic.decode5(
Artist,
dynamic.element(0, dynamic.string),
dynamic.element(1, dynamic.list(dynamic.int)),
dynamic.element(2, dynamic.bool),
dynamic.element(3, dynamic.int),
dynamic.any([
dynamic.element(4, dynamic.optional(dynamic.string)),
fn(_) { Ok(option.None) },
]),
)
}