Add artist artwork
This commit is contained in:
parent
2216d4b720
commit
960eaa73f7
9 changed files with 126 additions and 28 deletions
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -10,5 +10,6 @@ pub fn from(artist: APIArtist) {
|
|||
tracks: artist.tracks,
|
||||
trashed: artist.trashed,
|
||||
rating: artist.rating,
|
||||
artwork_id: artist.artwork_id,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)],
|
||||
)
|
||||
},
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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")],
|
||||
|
|
|
@ -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)),
|
||||
|
|
22
src/ibroadcast/artwork.gleam
Normal file
22
src/ibroadcast/artwork.gleam
Normal 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"
|
||||
}
|
||||
}
|
|
@ -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) },
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue