Prevent crash when library is not yet loaded
This commit is contained in:
parent
33f8cd92f2
commit
4f869df09f
8 changed files with 143 additions and 99 deletions
|
@ -287,34 +287,44 @@ pub fn view(model: Model) {
|
|||
]),
|
||||
case model.view {
|
||||
library_view.Tracks ->
|
||||
tracks_view.render(model.library, model.settings, [
|
||||
tracks_view.render(library_if_loaded(model), model.settings, [
|
||||
attribute.id("tracks-view"),
|
||||
attribute.class("glass-bg"),
|
||||
start_play.on(StartPlay),
|
||||
])
|
||||
library_view.Artists ->
|
||||
artists_view.render(model.library, model.settings, [
|
||||
artists_view.render(library_if_loaded(model), model.settings, [
|
||||
attribute.id("artists-view"),
|
||||
attribute.class("glass-bg"),
|
||||
])
|
||||
library_view.Albums ->
|
||||
albums_view.render(model.library, model.settings, [
|
||||
albums_view.render(library_if_loaded(model), model.settings, [
|
||||
attribute.id("albums-view"),
|
||||
attribute.class("glass-bg"),
|
||||
start_play.on(StartPlay),
|
||||
])
|
||||
library_view.SingleArtist(id) ->
|
||||
single_artist_view.render(model.library, id, model.settings, [
|
||||
attribute.id("single-artist-view"),
|
||||
attribute.class("glass-bg"),
|
||||
start_play.on(StartPlay),
|
||||
])
|
||||
single_artist_view.render(
|
||||
library_if_loaded(model),
|
||||
id,
|
||||
model.settings,
|
||||
[
|
||||
attribute.id("single-artist-view"),
|
||||
attribute.class("glass-bg"),
|
||||
start_play.on(StartPlay),
|
||||
],
|
||||
)
|
||||
library_view.SingleAlbum(id) ->
|
||||
single_album_view.render(model.library, id, model.settings, [
|
||||
attribute.id("single-album-view"),
|
||||
attribute.class("glass-bg"),
|
||||
start_play.on(StartPlay),
|
||||
])
|
||||
single_album_view.render(
|
||||
library_if_loaded(model),
|
||||
id,
|
||||
model.settings,
|
||||
[
|
||||
attribute.id("single-album-view"),
|
||||
attribute.class("glass-bg"),
|
||||
start_play.on(StartPlay),
|
||||
],
|
||||
)
|
||||
},
|
||||
]),
|
||||
case model.play_status {
|
||||
|
@ -374,6 +384,13 @@ fn if_player(
|
|||
}
|
||||
}
|
||||
|
||||
fn library_if_loaded(model: Model) {
|
||||
case model.loading_library {
|
||||
True -> option.None
|
||||
False -> option.Some(model.library)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_library(model: Model) {
|
||||
use dispatch <- effect.from()
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import gleam/string
|
|||
import gleam/result
|
||||
import lustre
|
||||
import lustre/effect
|
||||
import lustre/element
|
||||
import lustre/element.{text}
|
||||
import lustre/element/html.{div}
|
||||
import lustre/attribute
|
||||
import lustre/event
|
||||
|
@ -36,9 +36,26 @@ pub type DataGetter(a, filter) =
|
|||
/// the first element for the item itself and subsequent elements for the
|
||||
/// expanded contents.
|
||||
pub type ItemView(a, filter) =
|
||||
fn(Model(a, filter), List(LibraryItem(a)), Int, LibraryItem(a)) ->
|
||||
fn(Model(a, filter), Library, List(LibraryItem(a)), Int, LibraryItem(a)) ->
|
||||
List(element.Element(Msg(filter)))
|
||||
|
||||
/// A filter to restrict the items to be shown.
|
||||
pub type Filter(filter) {
|
||||
/// The filter has not yet been obtained as the component is initializing. No
|
||||
/// data fetches should be done yet.
|
||||
FilterNotLoaded
|
||||
Filter(filter)
|
||||
}
|
||||
|
||||
pub type LibraryAcquired
|
||||
|
||||
pub type LibraryNotAcquired
|
||||
|
||||
pub opaque type LibraryStatus {
|
||||
HaveLibrary(library: Library)
|
||||
NoLibrary
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
|
@ -80,9 +97,8 @@ pub type Msg(filter) {
|
|||
pub type Model(a, filter) {
|
||||
Model(
|
||||
id: String,
|
||||
filter: filter,
|
||||
library: Library,
|
||||
library_loading: Bool,
|
||||
filter: Filter(filter),
|
||||
library_status: LibraryStatus,
|
||||
data: List(LibraryItem(a)),
|
||||
data_getter: DataGetter(a, filter),
|
||||
shuffler: Shuffler(a),
|
||||
|
@ -105,7 +121,6 @@ pub type Model(a, filter) {
|
|||
/// and shuffle them for play.
|
||||
pub fn register(
|
||||
name: String,
|
||||
default_filter: filter,
|
||||
data_getter: DataGetter(a, filter),
|
||||
item_view: ItemView(a, filter),
|
||||
shuffler: Shuffler(a),
|
||||
|
@ -115,17 +130,7 @@ pub fn register(
|
|||
) {
|
||||
lustre.component(
|
||||
name,
|
||||
fn() {
|
||||
init(
|
||||
name,
|
||||
default_filter,
|
||||
library.empty(),
|
||||
True,
|
||||
data_getter,
|
||||
shuffler,
|
||||
sorter,
|
||||
)
|
||||
},
|
||||
fn() { init(name, FilterNotLoaded, data_getter, shuffler, sorter) },
|
||||
update,
|
||||
generate_view(item_view, search_filter),
|
||||
dict.merge(generic_attributes(), extra_attrs),
|
||||
|
@ -141,7 +146,7 @@ pub fn generic_attributes() {
|
|||
/// Render the component using a custom element.
|
||||
pub fn render(
|
||||
name: String,
|
||||
library: Library,
|
||||
library: option.Option(Library),
|
||||
settings: option.Option(common.Settings),
|
||||
extra_attrs: List(attribute.Attribute(msg)),
|
||||
) {
|
||||
|
@ -159,7 +164,7 @@ pub fn render(
|
|||
@external(javascript, "../../../library_view_ffi.mjs", "requestScroll")
|
||||
pub fn request_scroll(pos: Float) -> Nil
|
||||
|
||||
pub fn init(id, filter, library, library_loading, data_getter, shuffler, sorter) {
|
||||
pub fn init(id, filter, data_getter, shuffler, sorter) {
|
||||
let scrollend_effect =
|
||||
effect.from(fn(dispatch) {
|
||||
lustre_utils.after_next_render(fn() {
|
||||
|
@ -183,9 +188,8 @@ pub fn init(id, filter, library, library_loading, data_getter, shuffler, sorter)
|
|||
Model(
|
||||
id,
|
||||
filter,
|
||||
library,
|
||||
library_loading,
|
||||
data_getter(library, filter),
|
||||
NoLibrary,
|
||||
[],
|
||||
data_getter,
|
||||
shuffler,
|
||||
sorter,
|
||||
|
@ -198,11 +202,10 @@ pub fn init(id, filter, library, library_loading, data_getter, shuffler, sorter)
|
|||
)
|
||||
}
|
||||
|
||||
pub fn update(model, msg) {
|
||||
pub fn update(model: Model(a, filter), msg) {
|
||||
case msg {
|
||||
LibraryUpdated(library) -> #(
|
||||
Model(..model, library: library, library_loading: False)
|
||||
|> update_data(library),
|
||||
update_data(Model(..model, library_status: new_library(library))),
|
||||
effect.none(),
|
||||
)
|
||||
SettingsUpdated(settings) -> #(
|
||||
|
@ -217,8 +220,8 @@ pub fn update(model, msg) {
|
|||
}
|
||||
|
||||
FilterUpdated(filter) -> {
|
||||
let new_model = Model(..model, filter: filter)
|
||||
#(update_data(new_model, new_model.library), effect.none())
|
||||
let new_model = Model(..model, filter: Filter(filter))
|
||||
#(update_data(new_model), effect.none())
|
||||
}
|
||||
|
||||
ListScrolled(pos) -> {
|
||||
|
@ -245,27 +248,38 @@ pub fn library_view(
|
|||
item_view: ItemView(a, filter),
|
||||
search_filter: SearchFilter(a),
|
||||
) {
|
||||
let items = case model.search.search_text {
|
||||
"" -> model.data
|
||||
txt -> {
|
||||
let search_txt = string.lowercase(txt)
|
||||
model.data
|
||||
|> list.filter(fn(item) { search_filter(item.1, search_txt) })
|
||||
}
|
||||
}
|
||||
case model.library_status, model.filter == FilterNotLoaded {
|
||||
HaveLibrary(lib), False -> {
|
||||
let items = case model.search.search_text {
|
||||
"" -> model.data
|
||||
txt -> {
|
||||
let search_txt = string.lowercase(txt)
|
||||
model.data
|
||||
|> list.filter(fn(item) { search_filter(item.1, search_txt) })
|
||||
}
|
||||
}
|
||||
|
||||
div(
|
||||
[attribute.id("library-list"), scroll_to.on(ScrollRequested)],
|
||||
list.append(
|
||||
[
|
||||
search.view(model.search)
|
||||
|> element.map(Search),
|
||||
shuffle_all.view([event.on_click(ShuffleAll)]),
|
||||
],
|
||||
list.index_map(items, fn(item, i) { item_view(model, items, i, item) })
|
||||
|> list.flatten(),
|
||||
),
|
||||
)
|
||||
div(
|
||||
[attribute.id("library-list"), scroll_to.on(ScrollRequested)],
|
||||
list.append(
|
||||
[
|
||||
search.view(model.search)
|
||||
|> element.map(Search),
|
||||
shuffle_all.view([event.on_click(ShuffleAll)]),
|
||||
],
|
||||
list.index_map(items, fn(item, i) {
|
||||
item_view(model, lib, items, i, item)
|
||||
})
|
||||
|> list.flatten(),
|
||||
),
|
||||
)
|
||||
}
|
||||
_, _ ->
|
||||
div(
|
||||
[attribute.id("library-list"), attribute.class("library-list-loading")],
|
||||
[text("")],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_view(item_view: ItemView(a, filter), search_filter: SearchFilter(a)) {
|
||||
|
@ -273,13 +287,21 @@ fn generate_view(item_view: ItemView(a, filter), search_filter: SearchFilter(a))
|
|||
}
|
||||
|
||||
fn shuffle_all(model: Model(a, filter)) {
|
||||
let tracks = model.shuffler(model.library, model.data)
|
||||
start_play.emit(tracks, 0)
|
||||
case model.library_status {
|
||||
HaveLibrary(lib) -> {
|
||||
let tracks = model.shuffler(lib, model.data)
|
||||
start_play.emit(tracks, 0)
|
||||
}
|
||||
NoLibrary -> effect.none()
|
||||
}
|
||||
}
|
||||
|
||||
fn library_decode(data: dynamic.Dynamic) {
|
||||
let library: Library = dynamic.unsafe_coerce(data)
|
||||
Ok(LibraryUpdated(library))
|
||||
let library: option.Option(Library) = dynamic.unsafe_coerce(data)
|
||||
case library {
|
||||
option.Some(lib) -> Ok(LibraryUpdated(lib))
|
||||
option.None -> Error([])
|
||||
}
|
||||
}
|
||||
|
||||
fn settings_decode(data: dynamic.Dynamic) {
|
||||
|
@ -287,13 +309,17 @@ fn settings_decode(data: dynamic.Dynamic) {
|
|||
Ok(SettingsUpdated(settings))
|
||||
}
|
||||
|
||||
fn update_data(model: Model(a, filter), library: Library) {
|
||||
Model(
|
||||
..model,
|
||||
data: library
|
||||
|> model.data_getter(model.filter)
|
||||
|> list.sort(fn(a, b) { model.sorter(a.1, b.1) }),
|
||||
)
|
||||
fn update_data(model: Model(a, filter)) {
|
||||
case model.library_status, model.filter {
|
||||
HaveLibrary(l), Filter(f) ->
|
||||
Model(
|
||||
..model,
|
||||
data: l
|
||||
|> model.data_getter(f)
|
||||
|> list.sort(fn(a, b) { model.sorter(a.1, b.1) }),
|
||||
)
|
||||
_, _ -> model
|
||||
}
|
||||
}
|
||||
|
||||
fn get_view_history(history_api) {
|
||||
|
@ -305,3 +331,7 @@ fn add_scrollend_listener(callback: fn(Float) -> Nil) -> Nil
|
|||
|
||||
@external(javascript, "../../../library_view_ffi.mjs", "scrollTo")
|
||||
fn scroll_to(pos: Float) -> Nil
|
||||
|
||||
fn new_library(library: Library) -> LibraryStatus {
|
||||
HaveLibrary(library)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ const component_name = "albums-view"
|
|||
pub fn register() {
|
||||
library_view.register(
|
||||
component_name,
|
||||
Nil,
|
||||
data_getter,
|
||||
item_view,
|
||||
shuffler,
|
||||
|
@ -31,7 +30,7 @@ pub fn register() {
|
|||
|
||||
/// Render the albums view.
|
||||
pub fn render(
|
||||
library: Library,
|
||||
library: option.Option(Library),
|
||||
settings: option.Option(common.Settings),
|
||||
extra_attrs: List(attribute.Attribute(msg)),
|
||||
) {
|
||||
|
@ -55,9 +54,10 @@ fn search_filter(item: Album, search_text: String) {
|
|||
|
||||
fn item_view(
|
||||
model: Model(Album, Nil),
|
||||
library: Library,
|
||||
_items: List(LibraryItem(Album)),
|
||||
_index: Int,
|
||||
item: LibraryItem(Album),
|
||||
) {
|
||||
album_item.view(model.library, model.settings, item)
|
||||
album_item.view(library, model.settings, item)
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ const component_name = "artists-view"
|
|||
pub fn register() {
|
||||
library_view.register(
|
||||
component_name,
|
||||
Nil,
|
||||
data_getter,
|
||||
item_view,
|
||||
shuffler,
|
||||
|
@ -36,7 +35,7 @@ pub fn register() {
|
|||
|
||||
/// Render the artists view.
|
||||
pub fn render(
|
||||
library: Library,
|
||||
library: option.Option(Library),
|
||||
settings: option.Option(common.Settings),
|
||||
extra_attrs: List(attribute.Attribute(msg)),
|
||||
) {
|
||||
|
@ -66,6 +65,7 @@ fn search_filter(item: Artist, search_text: String) {
|
|||
|
||||
fn item_view(
|
||||
model: Model(Artist, Nil),
|
||||
_library: Library,
|
||||
_items: List(LibraryItem(Artist)),
|
||||
_index: Int,
|
||||
item: LibraryItem(Artist),
|
||||
|
|
|
@ -19,7 +19,6 @@ const component_name = "play-queue-view"
|
|||
pub fn register() {
|
||||
library_view.register(
|
||||
component_name,
|
||||
Nil,
|
||||
data_getter,
|
||||
item_view,
|
||||
shuffler,
|
||||
|
@ -31,7 +30,7 @@ pub fn register() {
|
|||
|
||||
/// Render the play queue view.
|
||||
pub fn render(
|
||||
library: Library,
|
||||
library: option.Option(Library),
|
||||
settings: option.Option(common.Settings),
|
||||
extra_attrs: List(attribute.Attribute(msg)),
|
||||
) {
|
||||
|
@ -51,10 +50,11 @@ fn search_filter(item: Track, search_text: String) {
|
|||
}
|
||||
|
||||
fn item_view(
|
||||
model: Model(Track, Nil),
|
||||
_model: Model(Track, Nil),
|
||||
library: Library,
|
||||
items: List(LibraryItem(Track)),
|
||||
index: Int,
|
||||
item: LibraryItem(Track),
|
||||
) {
|
||||
track_item.view(model.library, items, index, item, "track-list")
|
||||
track_item.view(library, items, index, item, "track-list")
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ const component_name = "single-album-view"
|
|||
pub fn register() {
|
||||
library_view.register(
|
||||
component_name,
|
||||
library.invalid_id,
|
||||
data_getter,
|
||||
item_view,
|
||||
shuffler,
|
||||
|
@ -32,7 +31,7 @@ pub fn register() {
|
|||
|
||||
/// Render the single album view.
|
||||
pub fn render(
|
||||
library: Library,
|
||||
library: option.Option(Library),
|
||||
album_id: Int,
|
||||
settings: option.Option(common.Settings),
|
||||
extra_attrs: List(attribute.Attribute(msg)),
|
||||
|
@ -63,12 +62,13 @@ fn search_filter(item: Track, search_text: String) {
|
|||
}
|
||||
|
||||
fn item_view(
|
||||
model: library_view.Model(Track, Int),
|
||||
_model: library_view.Model(Track, Int),
|
||||
library: Library,
|
||||
items: List(LibraryItem(Track)),
|
||||
index: Int,
|
||||
item: LibraryItem(Track),
|
||||
) {
|
||||
track_item.view(model.library, items, index, item, "album-tracks-list")
|
||||
track_item.view(library, items, index, item, "album-tracks-list")
|
||||
}
|
||||
|
||||
fn id_decode(data: dynamic.Dynamic) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//// A library view to a single artist's albums.
|
||||
//// A library view to a single artist's albums and non-album tracks.
|
||||
|
||||
import gleam/string
|
||||
import gleam/list
|
||||
|
@ -20,7 +20,6 @@ const component_name = "single-artist-view"
|
|||
pub fn register() {
|
||||
library_view.register(
|
||||
component_name,
|
||||
library.invalid_id,
|
||||
data_getter,
|
||||
item_view,
|
||||
shuffler,
|
||||
|
@ -32,7 +31,7 @@ pub fn register() {
|
|||
|
||||
/// Render the single artist view.
|
||||
pub fn render(
|
||||
library: Library,
|
||||
library: option.Option(Library),
|
||||
artist_id: Int,
|
||||
settings: option.Option(common.Settings),
|
||||
extra_attrs: List(attribute.Attribute(msg)),
|
||||
|
@ -44,12 +43,9 @@ pub fn render(
|
|||
}
|
||||
|
||||
fn data_getter(library: Library, artist_id: Int) {
|
||||
library.tracks
|
||||
|> dict.fold([], fn(acc, key, val) {
|
||||
case val.artist_id == artist_id && val.album_id == 0 {
|
||||
True -> [#(key, val), ..acc]
|
||||
False -> acc
|
||||
}
|
||||
let artist = library.assert_artist(library, artist_id)
|
||||
list.map(artist.tracks, fn(track_id) {
|
||||
#(track_id, library.assert_track(library, track_id))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -63,12 +59,13 @@ fn search_filter(item: Track, search_text: String) {
|
|||
}
|
||||
|
||||
fn item_view(
|
||||
model: library_view.Model(Track, Int),
|
||||
_model: library_view.Model(Track, Int),
|
||||
library: Library,
|
||||
items: List(LibraryItem(Track)),
|
||||
index: Int,
|
||||
item: LibraryItem(Track),
|
||||
) {
|
||||
track_item.view(model.library, items, index, item, "artist-tracks-list")
|
||||
track_item.view(library, items, index, item, "artist-tracks-list")
|
||||
}
|
||||
|
||||
fn id_decode(data: dynamic.Dynamic) {
|
||||
|
|
|
@ -19,7 +19,6 @@ const component_name = "tracks-view"
|
|||
pub fn register() {
|
||||
library_view.register(
|
||||
component_name,
|
||||
Nil,
|
||||
data_getter,
|
||||
item_view,
|
||||
shuffler,
|
||||
|
@ -31,7 +30,7 @@ pub fn register() {
|
|||
|
||||
/// Render the tracks view.
|
||||
pub fn render(
|
||||
library: Library,
|
||||
library: option.Option(Library),
|
||||
settings: option.Option(common.Settings),
|
||||
extra_attrs: List(attribute.Attribute(msg)),
|
||||
) {
|
||||
|
@ -51,10 +50,11 @@ fn search_filter(item: Track, search_text: String) {
|
|||
}
|
||||
|
||||
fn item_view(
|
||||
model: Model(Track, Nil),
|
||||
_model: Model(Track, Nil),
|
||||
library: Library,
|
||||
items: List(LibraryItem(Track)),
|
||||
index: Int,
|
||||
item: LibraryItem(Track),
|
||||
) {
|
||||
track_item.view(model.library, items, index, item, "track-list")
|
||||
track_item.view(library, items, index, item, "track-list")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue