Beginnings of refactoring to component based model
This commit is contained in:
parent
f8310f11e6
commit
8ea17ecd82
6 changed files with 256 additions and 53 deletions
|
@ -13,6 +13,11 @@ pub type Library {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets an empty library for use as a default value.
|
||||||
|
pub fn empty() {
|
||||||
|
Library(albums: map.new(), artists: map.new(), tracks: map.new())
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets an album from the library based on ID.
|
/// Gets an album from the library based on ID.
|
||||||
pub fn get_album(library: Library, id: Int) {
|
pub fn get_album(library: Library, id: Int) {
|
||||||
map.get(library.albums, id)
|
map.get(library.albums, id)
|
||||||
|
|
|
@ -23,6 +23,7 @@ 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/search
|
import elekf/web/components/search
|
||||||
|
import elekf/web/components/library_views/tracks_view
|
||||||
import elekf/web/utils
|
import elekf/web/utils
|
||||||
|
|
||||||
pub type PlayQueue =
|
pub type PlayQueue =
|
||||||
|
@ -201,60 +202,60 @@ pub fn view(model: Model) {
|
||||||
[
|
[
|
||||||
case model.library {
|
case model.library {
|
||||||
option.None -> p([], [text("Loading library…")])
|
option.None -> p([], [text("Loading library…")])
|
||||||
option.Some(lib) ->
|
option.Some(lib) -> tracks_view.render(lib)
|
||||||
div(
|
|
||||||
[attribute.class("track-list")],
|
|
||||||
list.append(
|
|
||||||
[
|
|
||||||
div(
|
|
||||||
[
|
|
||||||
attribute.class("track-list-shuffle-all"),
|
|
||||||
event.on_click(ShuffleAll),
|
|
||||||
],
|
|
||||||
[h3([attribute.class("track-title")], [text("Shuffle all")])],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
{
|
|
||||||
let tracks =
|
|
||||||
map.to_list(lib.tracks)
|
|
||||||
|> list.filter(fn(track) {
|
|
||||||
search_text == "" || string.contains(
|
|
||||||
{ track.1 }.title_lower,
|
|
||||||
search_text,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
list.index_map(
|
|
||||||
tracks,
|
|
||||||
fn(i, item) {
|
|
||||||
let #(track_id, track) = item
|
|
||||||
let album = library.assert_album(lib, track.album_id)
|
|
||||||
let artist = library.assert_artist(lib, track.artist_id)
|
|
||||||
div(
|
|
||||||
[
|
|
||||||
attribute.id("track-list-" <> int.to_string(track_id)),
|
|
||||||
attribute.type_("button"),
|
|
||||||
event.on_click(StartPlay(tracks, i)),
|
|
||||||
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)]),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
|
// div(
|
||||||
|
// [attribute.class("track-list")],
|
||||||
|
// list.append(
|
||||||
|
// [
|
||||||
|
// div(
|
||||||
|
// [
|
||||||
|
// attribute.class("track-list-shuffle-all"),
|
||||||
|
// event.on_click(ShuffleAll),
|
||||||
|
// ],
|
||||||
|
// [h3([attribute.class("track-title")], [text("Shuffle all")])],
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// {
|
||||||
|
// let tracks =
|
||||||
|
// map.to_list(lib.tracks)
|
||||||
|
// |> list.filter(fn(track) {
|
||||||
|
// search_text == "" || string.contains(
|
||||||
|
// { track.1 }.title_lower,
|
||||||
|
// search_text,
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
|
||||||
|
// list.index_map(
|
||||||
|
// tracks,
|
||||||
|
// fn(i, item) {
|
||||||
|
// let #(track_id, track) = item
|
||||||
|
// let album = library.assert_album(lib, track.album_id)
|
||||||
|
// let artist = library.assert_artist(lib, track.artist_id)
|
||||||
|
// div(
|
||||||
|
// [
|
||||||
|
// attribute.id("track-list-" <> int.to_string(track_id)),
|
||||||
|
// attribute.type_("button"),
|
||||||
|
// event.on_click(StartPlay(tracks, i)),
|
||||||
|
// 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)]),
|
||||||
|
// ],
|
||||||
|
// )
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
div(
|
div(
|
||||||
[attribute.id("search-positioner")],
|
[attribute.id("search-positioner")],
|
||||||
[
|
[
|
||||||
|
|
125
src/elekf/web/components/library_view.gleam
Normal file
125
src/elekf/web/components/library_view.gleam
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
//// A view to the library, presenting artists, albums, or tracks.
|
||||||
|
|
||||||
|
import gleam/dynamic
|
||||||
|
import gleam/list
|
||||||
|
import gleam/map
|
||||||
|
import lustre
|
||||||
|
import lustre/effect
|
||||||
|
import lustre/element.{text}
|
||||||
|
import lustre/element/html.{div, h3}
|
||||||
|
import lustre/attribute
|
||||||
|
import lustre/event
|
||||||
|
import elekf/library.{Library}
|
||||||
|
import elekf/library/album.{Album}
|
||||||
|
import elekf/library/artist.{Artist}
|
||||||
|
import elekf/library/track.{Track}
|
||||||
|
import elekf/web/events/start_play
|
||||||
|
|
||||||
|
pub const start_play_event = "start-play"
|
||||||
|
|
||||||
|
pub type LibraryItem(a) =
|
||||||
|
#(Int, a)
|
||||||
|
|
||||||
|
pub type DataGetter(a) =
|
||||||
|
fn(Library) -> List(LibraryItem(a))
|
||||||
|
|
||||||
|
pub type ItemView(a) =
|
||||||
|
fn(Library, List(LibraryItem(a)), Int, LibraryItem(a)) -> element.Element(Msg)
|
||||||
|
|
||||||
|
pub type Shuffler(a) =
|
||||||
|
fn(List(LibraryItem(a))) -> List(LibraryItem(Track))
|
||||||
|
|
||||||
|
pub type View {
|
||||||
|
/// All albums.
|
||||||
|
Albums
|
||||||
|
/// Tracks of a single album.
|
||||||
|
SingleAlbum(Int, Album)
|
||||||
|
/// All artists.
|
||||||
|
Artists
|
||||||
|
/// Albums of a single artist.
|
||||||
|
SingleArtist(Int, Artist)
|
||||||
|
/// Tracks of a single artist.
|
||||||
|
SingleArtistTracks(Int, Artist)
|
||||||
|
/// All tracks.
|
||||||
|
Tracks
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Msg {
|
||||||
|
LibraryUpdated(Library)
|
||||||
|
ShuffleAll
|
||||||
|
StartPlay(List(LibraryItem(Track)), Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Model(a) {
|
||||||
|
Model(
|
||||||
|
library: Library,
|
||||||
|
data: List(LibraryItem(a)),
|
||||||
|
item_view: ItemView(a),
|
||||||
|
shuffler: Shuffler(a),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(
|
||||||
|
name: String,
|
||||||
|
data_getter: DataGetter(a),
|
||||||
|
item_view: ItemView(a),
|
||||||
|
shuffler: Shuffler(a),
|
||||||
|
) {
|
||||||
|
lustre.component(
|
||||||
|
name,
|
||||||
|
fn() { init(library.empty(), data_getter, item_view, shuffler) },
|
||||||
|
update,
|
||||||
|
view,
|
||||||
|
map.from_list([#("library", library_decode)]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(name: String, library: Library) {
|
||||||
|
element.element(name, [attribute.property("library", library)], [])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(library, data_getter, item_view, shuffler) {
|
||||||
|
#(Model(library, data_getter(library), item_view, shuffler), effect.none())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(model, msg) {
|
||||||
|
case msg {
|
||||||
|
LibraryUpdated(library) -> #(
|
||||||
|
Model(..model, library: library),
|
||||||
|
effect.none(),
|
||||||
|
)
|
||||||
|
ShuffleAll -> #(model, shuffle_all(model))
|
||||||
|
StartPlay(tracks, position) -> #(model, start_play.emit(tracks, position))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(model: Model(a)) {
|
||||||
|
div(
|
||||||
|
[attribute.class("library-list")],
|
||||||
|
list.append(
|
||||||
|
[
|
||||||
|
div(
|
||||||
|
[
|
||||||
|
attribute.class("library-item library-list-shuffle-all"),
|
||||||
|
event.on_click(ShuffleAll),
|
||||||
|
],
|
||||||
|
[h3([attribute.class("library-item-title")], [text("Shuffle all")])],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
list.index_map(
|
||||||
|
model.data,
|
||||||
|
fn(i, item) { model.item_view(model.library, model.data, i, item) },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shuffle_all(model: Model(a)) {
|
||||||
|
let tracks = model.shuffler(model.data)
|
||||||
|
start_play.emit(tracks, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn library_decode(data: dynamic.Dynamic) {
|
||||||
|
let library: Library = dynamic.unsafe_coerce(data)
|
||||||
|
Ok(LibraryUpdated(library))
|
||||||
|
}
|
52
src/elekf/web/components/library_views/tracks_view.gleam
Normal file
52
src/elekf/web/components/library_views/tracks_view.gleam
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import gleam/int
|
||||||
|
import gleam/list
|
||||||
|
import gleam/map
|
||||||
|
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}
|
||||||
|
|
||||||
|
const component_name = "tracks-view"
|
||||||
|
|
||||||
|
pub fn register() {
|
||||||
|
library_view.register(component_name, data_getter, item_view, shuffler)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(library: Library) {
|
||||||
|
library_view.render(component_name, library)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_getter(library: Library) {
|
||||||
|
map.to_list(library.tracks)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shuffler(items) {
|
||||||
|
list.shuffle(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn item_view(
|
||||||
|
library: Library,
|
||||||
|
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)
|
||||||
|
div(
|
||||||
|
[
|
||||||
|
attribute.id("track-list-" <> int.to_string(track_id)),
|
||||||
|
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)]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
17
src/elekf/web/events/start_play.gleam
Normal file
17
src/elekf/web/events/start_play.gleam
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import gleam/dynamic
|
||||||
|
import lustre/event
|
||||||
|
import elekf/library/track.{Track}
|
||||||
|
|
||||||
|
pub const event_name = "start-play"
|
||||||
|
|
||||||
|
pub type EventData {
|
||||||
|
EventData(tracks: List(#(Int, Track)), position: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit(tracks: List(#(Int, Track)), position: Int) {
|
||||||
|
event.emit(event_name, EventData(tracks, position))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decoder(data) -> EventData {
|
||||||
|
dynamic.unsafe_coerce(data)
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import elekf/web/login_view
|
||||||
import elekf/web/authed_view
|
import elekf/web/authed_view
|
||||||
import elekf/web/view
|
import elekf/web/view
|
||||||
import elekf/web/utils
|
import elekf/web/utils
|
||||||
|
import elekf/web/components/library_views/tracks_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
|
||||||
|
@ -33,6 +34,8 @@ type Msg {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
|
let assert Ok(_) = tracks_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