Track list is rendered

This commit is contained in:
Mikko Ahlroth 2023-10-14 22:47:05 +03:00
parent 09f85315e4
commit 2672078e2c
8 changed files with 96 additions and 113 deletions

3
src/custom_event_ffi.mjs Normal file
View file

@ -0,0 +1,3 @@
export function getDetail(e) {
return e.detail;
}

View file

@ -0,0 +1,4 @@
pub type CustomEvent
@external(javascript, "../../custom_event_ffi.mjs", "getDetail")
pub fn get_detail(e: CustomEvent) -> a

View file

@ -13,7 +13,7 @@ import lustre/element/html.{div, h3, p}
import lustre/attribute
import lustre/event
import lustre/effect
import elekf/web/common
import elekf/web/common.{PlayQueue}
import ibroadcast/library/library as library_api
import ibroadcast/authed_request.{RequestConfig}
import elekf/api/base_request_config.{base_request_config}
@ -24,11 +24,9 @@ import elekf/transfer/library as library_transfer
import elekf/web/components/player
import elekf/web/components/search
import elekf/web/components/library_views/tracks_view
import elekf/web/events/start_play
import elekf/web/utils
pub type PlayQueue =
List(#(Int, Track))
pub type PlayInfo {
PlayInfo(
track_id: Int,
@ -61,7 +59,6 @@ pub type Msg {
PlayerMsg(player.Msg)
Search(search.Msg)
StartPlay(PlayQueue, Int)
ShuffleAll
}
pub fn init(auth_data: common.AuthData) {
@ -119,6 +116,7 @@ pub fn update(model: Model, msg) {
#(
Model(
..model,
loading_library: False,
library: library_transfer.from(data.library),
settings: option.Some(settings),
),
@ -128,10 +126,10 @@ pub fn update(model: Model, msg) {
LibraryResult(Error(error)) -> {
io.println_error("Library load failed:")
io.debug(error)
#(model, effect.none())
#(Model(..model, loading_library: False), effect.none())
}
StartPlay(tracks, position) -> {
let #(status, effect) = start_play(model, tracks, position)
let #(status, effect) = handle_start_play(model, tracks, position)
#(Model(..model, play_status: status), effect)
}
PlayerMsg(player.NextTrack) -> {
@ -142,7 +140,7 @@ pub fn update(model: Model, msg) {
case list.at(info.play_queue, next_index) {
Ok(_) -> {
let #(status, effect) =
start_play(model, info.play_queue, next_index)
handle_start_play(model, info.play_queue, next_index)
#(Model(..model, play_status: status), effect)
}
Error(_) -> #(model, effect.none())
@ -172,23 +170,6 @@ pub fn update(model: Model, msg) {
#(Model(..model, search: search_model), effect.none())
}
ShuffleAll -> {
let tracks =
model.library.tracks
|> map.to_list()
|> list.shuffle()
let #(model, effect) = case list.length(tracks) > 0 {
True -> {
let #(play_status, e) = start_play(model, tracks, 0)
#(Model(..model, play_status: play_status), e)
}
False -> #(model, effect.none())
}
#(model, effect)
}
}
}
@ -200,60 +181,12 @@ pub fn view(model: Model) {
[
case model.loading_library {
True -> p([], [text("Loading library…")])
False -> div([], [tracks_view.render(model.library)])
False -> text("")
},
// 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)]),
// ],
// )
// },
// )
// },
// ),
// )
tracks_view.render(
model.library,
[attribute.id("tracks-view"), start_play.on(StartPlay)],
),
div(
[attribute.id("search-positioner")],
[
@ -276,7 +209,7 @@ pub fn view(model: Model) {
)
}
fn start_play(model: Model, queue: PlayQueue, position: Int) {
fn handle_start_play(model: Model, queue: PlayQueue, position: Int) {
let assert Ok(#(track_id, track)) = list.at(queue, position)
let player_model = case model.play_status {

View file

@ -1,5 +1,10 @@
import elekf/library/track.{Track}
import elekf/api/auth/models as auth_models
/// A queue of tracks to play, with their IDs.
pub type PlayQueue =
List(#(Int, Track))
/// Authentication data for the user.
pub type AuthData {
AuthData(user: auth_models.User, device: auth_models.Device)

View file

@ -59,12 +59,20 @@ pub type Model(a) {
Model(
library: Library,
data: List(LibraryItem(a)),
item_view: ItemView(a),
data_getter: DataGetter(a),
shuffler: Shuffler(a),
)
}
/// Register the component as a custom element in the app.
///
/// This must be implemented by the client component, passing suitable values.
///
/// `data_getter` must be a callback that gets the library and must return the
/// items to show.
/// `item_view` is the renderer for individual items.
/// `shuffler` is the function used to find all the tracks in the current view
/// and shuffle them for play.
pub fn register(
name: String,
data_getter: DataGetter(a),
@ -73,26 +81,34 @@ pub fn register(
) {
lustre.component(
name,
fn() { init(library.empty(), data_getter, item_view, shuffler) },
fn() { init(library.empty(), data_getter, shuffler) },
update,
view,
generate_view(item_view),
map.from_list([#("library", library_decode)]),
)
}
/// Render the component using a custom element.
pub fn render(name: String, library: Library) {
element.element(name, [attribute.property("library", library)], [])
pub fn render(
name: String,
library: Library,
extra_attrs: List(attribute.Attribute(msg)),
) {
element.element(
name,
list.concat([[attribute.property("library", library)], extra_attrs]),
[],
)
}
fn init(library, data_getter, item_view, shuffler) {
#(Model(library, data_getter(library), item_view, shuffler), effect.none())
fn init(library, data_getter, shuffler) {
#(Model(library, data_getter(library), data_getter, shuffler), effect.none())
}
fn update(model, msg) {
case msg {
LibraryUpdated(library) -> #(
Model(..model, library: library),
Model(..model, library: library, data: model.data_getter(library)),
effect.none(),
)
ShuffleAll -> #(model, shuffle_all(model))
@ -100,7 +116,8 @@ fn update(model, msg) {
}
}
fn view(model: Model(a)) {
fn generate_view(item_view: ItemView(a)) {
fn(model: Model(a)) {
div(
[attribute.class("library-list")],
list.append(
@ -115,10 +132,11 @@ fn view(model: Model(a)) {
],
list.index_map(
model.data,
fn(i, item) { model.item_view(model.library, model.data, i, item) },
fn(i, item) { item_view(model.library, model.data, i, item) },
),
),
)
}
}
fn shuffle_all(model: Model(a)) {

View file

@ -19,8 +19,8 @@ pub fn register() {
}
/// Render the tracks view.
pub fn render(library: Library) {
library_view.render(component_name, library)
pub fn render(library: Library, extra_attrs: List(attribute.Attribute(msg))) {
library_view.render(component_name, library, extra_attrs)
}
fn data_getter(library: Library) {

View file

@ -1,17 +1,37 @@
import gleam/result
import gleam/dynamic
import lustre/event
import elekf/library/track.{Track}
import elekf/web/common.{PlayQueue}
import elekf/utils/custom_event.{CustomEvent}
pub const event_name = "start-play"
const event_name = "start-play"
pub type EventData {
EventData(tracks: List(#(Int, Track)), position: Int)
EventData(tracks: PlayQueue, position: Int)
}
pub fn emit(tracks: List(#(Int, Track)), position: Int) {
pub fn emit(tracks: PlayQueue, position: Int) {
event.emit(event_name, EventData(tracks, position))
}
pub fn decoder(data) -> EventData {
dynamic.unsafe_coerce(data)
pub fn on(msg: fn(PlayQueue, Int) -> b) {
event.on(
event_name,
fn(data) {
data
|> decoder
|> result.map(fn(e) { msg(e.tracks, e.position) })
},
)
}
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)
}

View file

@ -55,7 +55,7 @@ main {
width: 100%;
}
.track-list {
tracks-view {
flex: 1 1;
overflow-y: auto;
}