Implement grid expanding
This commit is contained in:
parent
93683dbb9f
commit
16214369f5
12 changed files with 326 additions and 108 deletions
|
@ -13,13 +13,13 @@ gleam = ">= 0.32.0"
|
|||
|
||||
[dependencies]
|
||||
gleam_stdlib = "~> 0.31"
|
||||
lustre = "~> 3.0"
|
||||
gleam_json = "~> 0.6"
|
||||
gleam_http = "~> 3.5"
|
||||
gleam_javascript = "~> 0.6"
|
||||
gleam_fetch = "~> 0.2"
|
||||
plinth = "~> 0.1"
|
||||
varasto = "~> 1.0"
|
||||
lustre = "~> 3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
gleeunit = "~> 0.10"
|
||||
|
|
|
@ -2,16 +2,16 @@
|
|||
# You typically do not need to edit this file
|
||||
|
||||
packages = [
|
||||
{ name = "gleam_fetch", version = "0.2.1", build_tools = ["gleam"], requirements = ["gleam_javascript", "gleam_http", "gleam_stdlib"], otp_app = "gleam_fetch", source = "hex", outer_checksum = "F64E93C754D948B2D37ABC4ADD5482FE0FAED4B99C79E66012DDE96BEDC40544" },
|
||||
{ name = "gleam_fetch", version = "0.2.1", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_javascript", "gleam_stdlib"], otp_app = "gleam_fetch", source = "hex", outer_checksum = "F64E93C754D948B2D37ABC4ADD5482FE0FAED4B99C79E66012DDE96BEDC40544" },
|
||||
{ name = "gleam_http", version = "3.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "0B09AAE8EB547C4F2F2D3F8917A0A4D2EF75DFF0232389332BAFE19DBBFDB92B" },
|
||||
{ name = "gleam_javascript", version = "0.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "BFEBB63ABE4A1694E07DEFD19B160C2980304B5D775A89D4B02E7DE7C9D8008B" },
|
||||
{ name = "gleam_json", version = "0.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "C6CC5BEECA525117E97D0905013AB3F8836537455645DDDD10FE31A511B195EF" },
|
||||
{ name = "gleam_stdlib", version = "0.32.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "07D64C26D014CF570F8ACADCE602761EA2E74C842D26F2FD49B0D61973D9966F" },
|
||||
{ name = "gleeunit", version = "0.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "1397E5C4AC4108769EE979939AC39BF7870659C5AFB714630DEEEE16B8272AD5" },
|
||||
{ name = "lustre", version = "3.0.10", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "DBA4A223D93BDDD43B9D8EEB52AE130A6995C8078559F32DB58BE89899307FED" },
|
||||
{ name = "lustre", version = "3.0.11", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "4B46341D9A3426023CFA21A6CFF4ED0171652A01B8905D1878099308DA2E92D2" },
|
||||
{ name = "plinth", version = "0.1.4", build_tools = ["gleam"], requirements = ["gleam_javascript", "gleam_stdlib"], otp_app = "plinth", source = "hex", outer_checksum = "43307A0271B8AC9B646B3C39F80D22C41DB4022CDAD67ABFAA555BB3F1321E2E" },
|
||||
{ name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" },
|
||||
{ name = "varasto", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib", "plinth"], otp_app = "varasto", source = "hex", outer_checksum = "0621E5BFD0B9B7F7D19B8FC6369C6E2EAC5C1F3858A1E5E51342F5BCE10C3728" },
|
||||
{ name = "varasto", version = "1.0.0", build_tools = ["gleam"], requirements = ["plinth", "gleam_stdlib", "gleam_json"], otp_app = "varasto", source = "hex", outer_checksum = "0621E5BFD0B9B7F7D19B8FC6369C6E2EAC5C1F3858A1E5E51342F5BCE10C3728" },
|
||||
]
|
||||
|
||||
[requirements]
|
||||
|
|
10
src/elekf/utils/set.gleam
Normal file
10
src/elekf/utils/set.gleam
Normal file
|
@ -0,0 +1,10 @@
|
|||
import gleam/set.{type Set}
|
||||
|
||||
/// Toggle an item in a set. In other words, insert the item if it didn't
|
||||
/// already exist, and remove it if it did.
|
||||
pub fn toggle(set: Set(a), item: a) -> Set(a) {
|
||||
case set.contains(set, item) {
|
||||
True -> set.delete(set, item)
|
||||
False -> set.insert(set, item)
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ import lustre/event
|
|||
import elekf/library.{type Library}
|
||||
import elekf/library/track.{type Track}
|
||||
import elekf/library/artist.{type Artist}
|
||||
import elekf/library/album.{type Album}
|
||||
import elekf/web/events/start_play
|
||||
import elekf/web/events/show_artist
|
||||
import elekf/web/components/search
|
||||
|
@ -26,9 +27,13 @@ pub type DataGetter(a) =
|
|||
fn(Library) -> List(LibraryItem(a))
|
||||
|
||||
/// A view that renders a single item from the library.
|
||||
///
|
||||
/// It should return a list of elements, which can be used for expanding items:
|
||||
/// the first element for the item itself and subsequent elements for the
|
||||
/// expanded contents.
|
||||
pub type ItemView(a) =
|
||||
fn(Model(a), List(LibraryItem(a)), Int, LibraryItem(a)) ->
|
||||
element.Element(Msg)
|
||||
List(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.
|
||||
|
@ -64,6 +69,7 @@ pub type Msg {
|
|||
StartPlay(List(LibraryItem(Track)), Int)
|
||||
ShowArtist(LibraryItem(Artist))
|
||||
Search(search.Msg)
|
||||
AlbumExpandToggled(LibraryItem(Album))
|
||||
}
|
||||
|
||||
pub type Model(a) {
|
||||
|
@ -159,6 +165,9 @@ pub fn update(model, msg) {
|
|||
let search_model = search.update(model.search, search_msg)
|
||||
#(Model(..model, search: search_model), effect.none())
|
||||
}
|
||||
|
||||
// Base case, this should be handled in an implementing view
|
||||
AlbumExpandToggled(_album) -> #(model, effect.none())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,7 +197,8 @@ pub fn library_view(
|
|||
[h3([attribute.class("library-item-title")], [text("Shuffle all")])],
|
||||
),
|
||||
],
|
||||
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) })
|
||||
|> list.flatten(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,61 +3,118 @@
|
|||
import gleam/list
|
||||
import gleam/option
|
||||
import gleam/int
|
||||
import lustre/element/html.{div, h3, img, p}
|
||||
import lustre/element/html.{div, h3, p}
|
||||
import lustre/element.{text}
|
||||
import lustre/attribute
|
||||
import lustre/event
|
||||
import ibroadcast/artwork
|
||||
import elekf/library
|
||||
import elekf/library.{type Library}
|
||||
import elekf/library/album.{type Album}
|
||||
import elekf/library/track.{type Track}
|
||||
import elekf/library/album_utils
|
||||
import elekf/web/components/library_view.{type Model, StartPlay}
|
||||
import elekf/web/components/library_view.{AlbumExpandToggled}
|
||||
import elekf/web/components/library_item.{type LibraryItem}
|
||||
import elekf/web/components/library_views/track_item
|
||||
import elekf/web/components/library_views/thumbnail
|
||||
import elekf/web/common.{type Settings}
|
||||
|
||||
const base_classes = "library-item album-item"
|
||||
|
||||
const showing_tracks_class = "library-item-expanded"
|
||||
|
||||
pub fn view(
|
||||
model: Model(Album),
|
||||
_items: List(LibraryItem(Album)),
|
||||
_index: Int,
|
||||
library: Library,
|
||||
settings: option.Option(Settings),
|
||||
item: LibraryItem(Album),
|
||||
show_tracks: Bool,
|
||||
) {
|
||||
let #(album_id, album) = item
|
||||
let tracks = album_utils.get_tracks(model.library, album)
|
||||
let tracks = album_utils.get_tracks(library, album)
|
||||
let artist_name = case album.artist_id {
|
||||
0 -> "Unknown artist"
|
||||
id -> library.assert_artist(model.library, id).name
|
||||
id -> library.assert_artist(library, id).name
|
||||
}
|
||||
let assert Ok(first_track) = list.first(tracks)
|
||||
|
||||
let class = case show_tracks {
|
||||
True -> base_classes <> " " <> showing_tracks_class
|
||||
False -> base_classes
|
||||
}
|
||||
let expanded = case show_tracks {
|
||||
True -> "true"
|
||||
False -> "false"
|
||||
}
|
||||
|
||||
list.append(
|
||||
[
|
||||
div(
|
||||
[
|
||||
attribute.id("album-list-" <> int.to_string(album_id)),
|
||||
attribute.class("library-item album-item"),
|
||||
event.on_click(StartPlay(tracks, 0)),
|
||||
attribute.attribute("role", "button"),
|
||||
attribute.class(class),
|
||||
],
|
||||
[
|
||||
case model.settings, { first_track.1 }.artwork_id {
|
||||
div(
|
||||
[
|
||||
attribute.class("album-item-expander"),
|
||||
event.on_click(AlbumExpandToggled(item)),
|
||||
event.on_keydown(fn(_) { AlbumExpandToggled(item) }),
|
||||
attribute.attribute("role", "button"),
|
||||
attribute.attribute("aria-expanded", expanded),
|
||||
attribute.attribute("tabindex", "0"),
|
||||
],
|
||||
[
|
||||
case 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),
|
||||
])
|
||||
thumbnail.view(s, int.to_string(id))
|
||||
_, _ ->
|
||||
div([attribute.class("artist-image-placeholder")], [text(album.name)])
|
||||
div(
|
||||
[
|
||||
attribute.class(
|
||||
"artist-image-placeholder album-item-image",
|
||||
),
|
||||
],
|
||||
[text(album.name)],
|
||||
)
|
||||
},
|
||||
h3([attribute.class("album-title")], [text(album.name)]),
|
||||
p([attribute.class("album-artist")], [text(artist_name)]),
|
||||
h3([attribute.class("album-item-title")], [text(album.name)]),
|
||||
p([attribute.class("album-item-artist")], [text(artist_name)]),
|
||||
p(
|
||||
[attribute.class("album-tracks")],
|
||||
[attribute.class("album-item-tracks-meta")],
|
||||
[text(int.to_string(list.length(album.tracks)) <> " tracks")],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
case show_tracks {
|
||||
True -> [view_tracks(library, tracks)]
|
||||
False -> []
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn view_tracks(library: Library, tracks: List(LibraryItem(Track))) {
|
||||
div(
|
||||
[
|
||||
attribute.class(
|
||||
"library-item-expanded-contents album-item-expanded-tracks",
|
||||
),
|
||||
],
|
||||
list.flatten([
|
||||
[h3([attribute.class("library-item-title")], [text("Shuffle all")])],
|
||||
list.index_map(
|
||||
tracks,
|
||||
fn(index, track_item) {
|
||||
track_item.view(
|
||||
library,
|
||||
tracks,
|
||||
track_item,
|
||||
index,
|
||||
"album-tracks-list",
|
||||
)
|
||||
},
|
||||
)
|
||||
|> list.flatten(),
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,26 +3,48 @@
|
|||
import gleam/string
|
||||
import gleam/list
|
||||
import gleam/map
|
||||
import gleam/set
|
||||
import gleam/option
|
||||
import gleam/dynamic
|
||||
import gleam/result
|
||||
import lustre
|
||||
import lustre/effect
|
||||
import lustre/attribute
|
||||
import lustre/element
|
||||
import elekf/utils/set as set_utils
|
||||
import elekf/library.{type Library}
|
||||
import elekf/library/album.{type Album}
|
||||
import elekf/library/album_utils
|
||||
import elekf/web/components/library_view
|
||||
import elekf/web/components/library_view.{AlbumExpandToggled}
|
||||
import elekf/web/components/library_item.{type LibraryItem}
|
||||
import elekf/web/components/library_views/album_item
|
||||
import elekf/web/common
|
||||
|
||||
const component_name = "albums-view"
|
||||
|
||||
type Model {
|
||||
Model(library_view: library_view.Model(Album), expanded_albums: set.Set(Int))
|
||||
}
|
||||
|
||||
type Msg {
|
||||
LibraryViewMsg(library_view.Msg)
|
||||
}
|
||||
|
||||
/// Register the albums view as a custom element.
|
||||
pub fn register() {
|
||||
library_view.register(
|
||||
lustre.component(
|
||||
component_name,
|
||||
data_getter,
|
||||
album_item.view,
|
||||
shuffler,
|
||||
search_filter,
|
||||
init,
|
||||
update,
|
||||
generate_view(search_filter),
|
||||
library_view.generic_attributes()
|
||||
|> map.map_values(fn(_key, decoder) {
|
||||
fn(data: dynamic.Dynamic) {
|
||||
data
|
||||
|> decoder()
|
||||
|> result.map(LibraryViewMsg)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -35,6 +57,34 @@ pub fn render(
|
|||
library_view.render(component_name, library, settings, extra_attrs)
|
||||
}
|
||||
|
||||
fn init() {
|
||||
let #(lib_m, lib_e) =
|
||||
library_view.init(library.empty(), data_getter, shuffler)
|
||||
|
||||
#(
|
||||
Model(library_view: lib_m, expanded_albums: set.new()),
|
||||
effect.map(lib_e, LibraryViewMsg),
|
||||
)
|
||||
}
|
||||
|
||||
fn update(model: Model, msg) {
|
||||
case msg {
|
||||
LibraryViewMsg(AlbumExpandToggled(album)) -> {
|
||||
#(
|
||||
Model(
|
||||
..model,
|
||||
expanded_albums: set_utils.toggle(model.expanded_albums, album.0),
|
||||
),
|
||||
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) {
|
||||
map.to_list(library.albums)
|
||||
}
|
||||
|
@ -49,3 +99,23 @@ fn shuffler(library, items: List(LibraryItem(Album))) {
|
|||
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) {
|
||||
library_view.library_view(
|
||||
model.library_view,
|
||||
fn(_library_model, _items, _index, item) { view(model, item) },
|
||||
search_filter,
|
||||
)
|
||||
|> element.map(LibraryViewMsg)
|
||||
}
|
||||
}
|
||||
|
||||
fn view(model: Model, item: LibraryItem(Album)) {
|
||||
album_item.view(
|
||||
model.library_view.library,
|
||||
model.library_view.settings,
|
||||
item,
|
||||
set.contains(model.expanded_albums, item.0),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,15 +5,15 @@ import gleam/int
|
|||
import gleam/list
|
||||
import gleam/map
|
||||
import gleam/option
|
||||
import lustre/element/html.{div, h3, img, p}
|
||||
import lustre/element/html.{div, h3, p}
|
||||
import lustre/element.{text}
|
||||
import lustre/attribute
|
||||
import lustre/event
|
||||
import ibroadcast/artwork
|
||||
import elekf/library.{type Library}
|
||||
import elekf/library/artist.{type Artist}
|
||||
import elekf/web/components/library_view.{type Model, ShowArtist}
|
||||
import elekf/web/components/library_item.{type LibraryItem}
|
||||
import elekf/web/components/library_views/thumbnail
|
||||
import elekf/web/common
|
||||
|
||||
const component_name = "artists-view"
|
||||
|
@ -69,6 +69,7 @@ fn item_view(
|
|||
item: LibraryItem(Artist),
|
||||
) {
|
||||
let #(artist_id, artist) = item
|
||||
[
|
||||
div(
|
||||
[
|
||||
attribute.id("artist-list-" <> int.to_string(artist_id)),
|
||||
|
@ -79,14 +80,7 @@ fn item_view(
|
|||
],
|
||||
[
|
||||
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"),
|
||||
attribute.width(300),
|
||||
])
|
||||
option.Some(s), option.Some(id) -> thumbnail.view(s, id)
|
||||
_, _ ->
|
||||
div(
|
||||
[attribute.class("artist-image-placeholder")],
|
||||
|
@ -99,7 +93,8 @@ fn item_view(
|
|||
[text(int.to_string(list.length(artist.tracks)) <> " tracks")],
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn get_tracks(library: Library, artist: Artist) {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import gleam/string
|
||||
import gleam/list
|
||||
import gleam/map
|
||||
import gleam/set
|
||||
import gleam/option
|
||||
import gleam/dynamic
|
||||
import gleam/result
|
||||
|
@ -11,11 +12,12 @@ import lustre/element/html.{div, header}
|
|||
import lustre/element.{text}
|
||||
import lustre/attribute
|
||||
import lustre/effect
|
||||
import elekf/utils/set as set_utils
|
||||
import elekf/library.{type Library}
|
||||
import elekf/library/album.{type Album}
|
||||
import elekf/library/album_utils
|
||||
import elekf/library/artist.{type Artist}
|
||||
import elekf/web/components/library_view
|
||||
import elekf/web/components/library_view.{AlbumExpandToggled}
|
||||
import elekf/web/components/library_item.{type LibraryItem}
|
||||
import elekf/web/components/library_views/album_item
|
||||
import elekf/web/common
|
||||
|
@ -26,6 +28,7 @@ type Model {
|
|||
Model(
|
||||
library_view: library_view.Model(Album),
|
||||
artist: option.Option(LibraryItem(Artist)),
|
||||
expanded_albums: set.Set(Int),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -82,7 +85,7 @@ fn init() {
|
|||
)
|
||||
|
||||
#(
|
||||
Model(artist: option.None, library_view: lib_m),
|
||||
Model(artist: option.None, expanded_albums: set.new(), library_view: lib_m),
|
||||
effect.map(lib_e, LibraryViewMsg),
|
||||
)
|
||||
}
|
||||
|
@ -92,6 +95,7 @@ fn update(model: Model, msg) {
|
|||
ArtistUpdated(option.Some(artist)) -> #(
|
||||
Model(
|
||||
artist: option.Some(artist),
|
||||
expanded_albums: set.new(),
|
||||
library_view: library_view.Model(
|
||||
..model.library_view,
|
||||
data: data_getter(model.library_view.library, artist),
|
||||
|
@ -102,10 +106,18 @@ fn update(model: Model, msg) {
|
|||
ArtistUpdated(option.None) -> #(
|
||||
Model(
|
||||
artist: option.None,
|
||||
expanded_albums: set.new(),
|
||||
library_view: library_view.Model(..model.library_view, data: []),
|
||||
),
|
||||
effect.none(),
|
||||
)
|
||||
LibraryViewMsg(AlbumExpandToggled(album)) -> #(
|
||||
Model(
|
||||
..model,
|
||||
expanded_albums: set_utils.toggle(model.expanded_albums, album.0),
|
||||
),
|
||||
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))
|
||||
|
@ -161,7 +173,7 @@ fn view(
|
|||
header([attribute.class("library-header")], [text({ artist.1 }.name)]),
|
||||
library_view.library_view(
|
||||
model.library_view,
|
||||
album_item.view,
|
||||
fn(_library_model, _items, _index, item) { album_view(model, item) },
|
||||
search_filter,
|
||||
)
|
||||
|> element.map(LibraryViewMsg),
|
||||
|
@ -169,6 +181,15 @@ fn view(
|
|||
)
|
||||
}
|
||||
|
||||
fn album_view(model: Model, item: LibraryItem(Album)) {
|
||||
album_item.view(
|
||||
model.library_view.library,
|
||||
model.library_view.settings,
|
||||
item,
|
||||
set.contains(model.expanded_albums, item.0),
|
||||
)
|
||||
}
|
||||
|
||||
fn artist_decode(data: dynamic.Dynamic) {
|
||||
let artist: option.Option(LibraryItem(Artist)) = dynamic.unsafe_coerce(data)
|
||||
Ok(ArtistUpdated(artist))
|
||||
|
|
20
src/elekf/web/components/library_views/thumbnail.gleam
Normal file
20
src/elekf/web/components/library_views/thumbnail.gleam
Normal file
|
@ -0,0 +1,20 @@
|
|||
//// A thumbnail element (artist, album...)
|
||||
|
||||
import lustre/element/html.{div, img}
|
||||
import lustre/attribute
|
||||
import ibroadcast/artwork
|
||||
import elekf/web/common.{type Settings}
|
||||
|
||||
pub fn view(s: Settings, id: String) {
|
||||
div(
|
||||
[attribute.class("library-item-thumbnail")],
|
||||
[
|
||||
img([
|
||||
attribute.alt(""),
|
||||
attribute.src(artwork.url(s.artwork_server, id, artwork.S300)),
|
||||
attribute.attribute("loading", "lazy"),
|
||||
attribute.width(300),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
37
src/elekf/web/components/library_views/track_item.gleam
Normal file
37
src/elekf/web/components/library_views/track_item.gleam
Normal file
|
@ -0,0 +1,37 @@
|
|||
import gleam/int
|
||||
import lustre/attribute
|
||||
import lustre/event
|
||||
import lustre/element.{text}
|
||||
import lustre/element/html.{div, h3, p}
|
||||
import elekf/library.{type Library}
|
||||
import elekf/library/track.{type Track}
|
||||
import elekf/web/components/library_item.{type LibraryItem}
|
||||
import elekf/web/components/library_view.{StartPlay}
|
||||
|
||||
pub fn view(
|
||||
library: Library,
|
||||
items: List(LibraryItem(Track)),
|
||||
item: LibraryItem(Track),
|
||||
index: Int,
|
||||
id_prefix: String,
|
||||
) {
|
||||
let #(track_id, track) = item
|
||||
let album = library.assert_album(library, track.album_id)
|
||||
let artist = library.assert_artist(library, track.artist_id)
|
||||
[
|
||||
div(
|
||||
[
|
||||
attribute.id(id_prefix <> "-" <> int.to_string(track_id)),
|
||||
attribute.class("library-item track-item"),
|
||||
attribute.type_("button"),
|
||||
event.on_click(StartPlay(items, index)),
|
||||
attribute.attribute("role", "button"),
|
||||
],
|
||||
[
|
||||
h3([attribute.class("track-title")], [text(track.title)]),
|
||||
p([attribute.class("track-artist")], [text(artist.name)]),
|
||||
p([attribute.class("track-album")], [text(album.name)]),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
|
@ -1,18 +1,15 @@
|
|||
//// A library view to all of the tracks in the library.
|
||||
|
||||
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.{type Library}
|
||||
import elekf/library/track.{type Track}
|
||||
import elekf/web/components/library_view.{type Model, StartPlay}
|
||||
import elekf/web/components/library_view.{type Model}
|
||||
import elekf/web/components/library_item.{type LibraryItem}
|
||||
import elekf/web/components/library_views/track_item
|
||||
import elekf/web/common
|
||||
|
||||
const component_name = "tracks-view"
|
||||
|
@ -55,21 +52,5 @@ fn item_view(
|
|||
index: Int,
|
||||
item: LibraryItem(Track),
|
||||
) {
|
||||
let #(track_id, track) = item
|
||||
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)),
|
||||
attribute.class("library-item track-item"),
|
||||
attribute.type_("button"),
|
||||
event.on_click(StartPlay(items, index)),
|
||||
attribute.attribute("role", "button"),
|
||||
],
|
||||
[
|
||||
h3([attribute.class("track-title")], [text(track.title)]),
|
||||
p([attribute.class("track-artist")], [text(artist.name)]),
|
||||
p([attribute.class("track-album")], [text(album.name)]),
|
||||
],
|
||||
)
|
||||
track_item.view(model.library, items, item, index, "track-list")
|
||||
}
|
||||
|
|
19
style.css
19
style.css
|
@ -66,12 +66,29 @@ main {
|
|||
}
|
||||
|
||||
#artists-view .library-list,
|
||||
#albums-view .library-list {
|
||||
#albums-view .library-list,
|
||||
#single-artist-view .library-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
grid-auto-flow: dense;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.library-list-shuffle-all {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.library-item-thumbnail {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
.library-item-expanded {
|
||||
}
|
||||
|
||||
.library-item-expanded-contents {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.library-item img {
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue