Implement single artist view
This commit is contained in:
parent
eb8b6df527
commit
899d63bb36
11 changed files with 369 additions and 104 deletions
11
src/elekf/library/album_utils.gleam
Normal file
11
src/elekf/library/album_utils.gleam
Normal file
|
@ -0,0 +1,11 @@
|
|||
import gleam/list
|
||||
import elekf/library.{Library}
|
||||
import elekf/library/album.{Album}
|
||||
|
||||
/// Get the individual tracks in an album.
|
||||
pub fn get_tracks(library: Library, album: Album) {
|
||||
list.map(
|
||||
album.tracks,
|
||||
fn(track_id) { #(track_id, library.assert_track(library, track_id)) },
|
||||
)
|
||||
}
|
|
@ -16,14 +16,18 @@ import ibroadcast/authed_request.{RequestConfig}
|
|||
import elekf/api/base_request_config.{base_request_config}
|
||||
import elekf/utils/http
|
||||
import elekf/library.{Library}
|
||||
import elekf/library/artist.{Artist}
|
||||
import elekf/library/track.{Track}
|
||||
import elekf/transfer/library as library_transfer
|
||||
import elekf/web/components/player
|
||||
import elekf/web/components/library_item.{LibraryItem}
|
||||
import elekf/web/components/library_view
|
||||
import elekf/web/components/library_views/tracks_view
|
||||
import elekf/web/components/library_views/artists_view
|
||||
import elekf/web/components/library_views/albums_view
|
||||
import elekf/web/components/library_views/single_artist_view
|
||||
import elekf/web/events/start_play
|
||||
import elekf/web/events/show_artist
|
||||
import elekf/web/utils
|
||||
import elekf/web/components/icon.{Alt, icon}
|
||||
|
||||
|
@ -58,6 +62,7 @@ pub type Msg {
|
|||
LibraryResult(Result(library_api.ResponseData, http.ResponseError))
|
||||
PlayerMsg(player.Msg)
|
||||
StartPlay(PlayQueue, Int)
|
||||
ShowArtist(LibraryItem(Artist))
|
||||
ChangeView(library_view.View)
|
||||
}
|
||||
|
||||
|
@ -132,6 +137,10 @@ pub fn update(model: Model, msg) {
|
|||
let #(status, effect) = handle_start_play(model, tracks, position)
|
||||
#(Model(..model, play_status: status), effect)
|
||||
}
|
||||
ShowArtist(artist) -> #(
|
||||
Model(..model, library_view: library_view.SingleArtist(artist)),
|
||||
effect.none(),
|
||||
)
|
||||
PlayerMsg(player.NextTrack) | PlayerMsg(player.PrevTrack) -> {
|
||||
if_player(
|
||||
model,
|
||||
|
@ -227,7 +236,7 @@ pub fn view(model: Model) {
|
|||
artists_view.render(
|
||||
model.library,
|
||||
model.settings,
|
||||
[attribute.id("artists-view"), start_play.on(StartPlay)],
|
||||
[attribute.id("artists-view"), show_artist.on(ShowArtist)],
|
||||
)
|
||||
library_view.Albums ->
|
||||
albums_view.render(
|
||||
|
@ -235,6 +244,13 @@ pub fn view(model: Model) {
|
|||
model.settings,
|
||||
[attribute.id("albums-view"), start_play.on(StartPlay)],
|
||||
)
|
||||
library_view.SingleArtist(artist_info) ->
|
||||
single_artist_view.render(
|
||||
model.library,
|
||||
artist_info,
|
||||
model.settings,
|
||||
[attribute.id("single-artist-view"), start_play.on(StartPlay)],
|
||||
)
|
||||
},
|
||||
],
|
||||
),
|
||||
|
|
3
src/elekf/web/components/library_item.gleam
Normal file
3
src/elekf/web/components/library_item.gleam
Normal file
|
@ -0,0 +1,3 @@
|
|||
/// An item in the library with its ID.
|
||||
pub type LibraryItem(a) =
|
||||
#(Int, a)
|
|
@ -14,14 +14,13 @@ import lustre/attribute
|
|||
import lustre/event
|
||||
import elekf/library.{Library}
|
||||
import elekf/library/track.{Track}
|
||||
import elekf/library/artist.{Artist}
|
||||
import elekf/web/events/start_play
|
||||
import elekf/web/events/show_artist
|
||||
import elekf/web/components/search
|
||||
import elekf/web/components/library_item.{LibraryItem}
|
||||
import elekf/web/common
|
||||
|
||||
/// An item in the library with its ID.
|
||||
pub type LibraryItem(a) =
|
||||
#(Int, a)
|
||||
|
||||
/// Function to get the data of the view from the library.
|
||||
pub type DataGetter(a) =
|
||||
fn(Library) -> List(LibraryItem(a))
|
||||
|
@ -51,7 +50,7 @@ pub type View {
|
|||
/// All artists.
|
||||
Artists
|
||||
/// Albums of a single artist.
|
||||
/// SingleArtist(Int, Artist)
|
||||
SingleArtist(LibraryItem(Artist))
|
||||
/// Tracks of a single artist.
|
||||
/// SingleArtistTracks(Int, Artist)
|
||||
/// All tracks.
|
||||
|
@ -63,6 +62,7 @@ pub type Msg {
|
|||
SettingsUpdated(option.Option(common.Settings))
|
||||
ShuffleAll
|
||||
StartPlay(List(LibraryItem(Track)), Int)
|
||||
ShowArtist(LibraryItem(Artist))
|
||||
Search(search.Msg)
|
||||
}
|
||||
|
||||
|
@ -98,10 +98,16 @@ pub fn register(
|
|||
fn() { init(library.empty(), data_getter, shuffler) },
|
||||
update,
|
||||
generate_view(item_view, search_filter),
|
||||
map.from_list([#("library", library_decode), #("settings", settings_decode)]),
|
||||
generic_attributes(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the generic properties common to all library views, that can be input
|
||||
/// into the Lustre component attribute change
|
||||
pub fn generic_attributes() {
|
||||
map.from_list([#("library", library_decode), #("settings", settings_decode)])
|
||||
}
|
||||
|
||||
/// Render the component using a custom element.
|
||||
pub fn render(
|
||||
name: String,
|
||||
|
@ -111,7 +117,7 @@ pub fn render(
|
|||
) {
|
||||
element.element(
|
||||
name,
|
||||
list.concat([
|
||||
list.flatten([
|
||||
[
|
||||
attribute.property("library", library),
|
||||
attribute.property("settings", settings),
|
||||
|
@ -122,7 +128,7 @@ pub fn render(
|
|||
)
|
||||
}
|
||||
|
||||
fn init(library, data_getter, shuffler) {
|
||||
pub fn init(library, data_getter, shuffler) {
|
||||
#(
|
||||
Model(
|
||||
library,
|
||||
|
@ -136,7 +142,7 @@ fn init(library, data_getter, shuffler) {
|
|||
)
|
||||
}
|
||||
|
||||
fn update(model, msg) {
|
||||
pub fn update(model, msg) {
|
||||
case msg {
|
||||
LibraryUpdated(library) -> #(
|
||||
Model(..model, library: library, data: model.data_getter(library)),
|
||||
|
@ -148,6 +154,7 @@ fn update(model, msg) {
|
|||
)
|
||||
ShuffleAll -> #(model, shuffle_all(model))
|
||||
StartPlay(tracks, position) -> #(model, start_play.emit(tracks, position))
|
||||
ShowArtist(artist) -> #(model, show_artist.emit(artist))
|
||||
Search(search_msg) -> {
|
||||
let search_model = search.update(model.search, search_msg)
|
||||
#(Model(..model, search: search_model), effect.none())
|
||||
|
@ -155,8 +162,11 @@ fn update(model, msg) {
|
|||
}
|
||||
}
|
||||
|
||||
fn generate_view(item_view: ItemView(a), search_filter: SearchFilter(a)) {
|
||||
fn(model: Model(a)) {
|
||||
pub fn library_view(
|
||||
model: Model(a),
|
||||
item_view: ItemView(a),
|
||||
search_filter: SearchFilter(a),
|
||||
) {
|
||||
let items = case model.search.search_text {
|
||||
"" -> model.data
|
||||
txt ->
|
||||
|
@ -182,6 +192,9 @@ fn generate_view(item_view: ItemView(a), search_filter: SearchFilter(a)) {
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_view(item_view: ItemView(a), search_filter: SearchFilter(a)) {
|
||||
library_view(_, item_view, search_filter)
|
||||
}
|
||||
|
||||
fn shuffle_all(model: Model(a)) {
|
||||
|
|
64
src/elekf/web/components/library_views/album_item.gleam
Normal file
64
src/elekf/web/components/library_views/album_item.gleam
Normal file
|
@ -0,0 +1,64 @@
|
|||
//// Library item view for a single album.
|
||||
|
||||
import gleam/list
|
||||
import gleam/option
|
||||
import gleam/int
|
||||
import lustre/element/html.{div, h3, img, p}
|
||||
import lustre/element.{text}
|
||||
import lustre/attribute
|
||||
import lustre/event
|
||||
import ibroadcast/artwork
|
||||
import elekf/library
|
||||
import elekf/library/album.{Album}
|
||||
import elekf/library/album_utils
|
||||
import elekf/web/components/library_view.{Model, StartPlay}
|
||||
import elekf/web/components/library_item.{LibraryItem}
|
||||
|
||||
pub fn view(
|
||||
model: Model(Album),
|
||||
_items: List(LibraryItem(Album)),
|
||||
_index: Int,
|
||||
item: LibraryItem(Album),
|
||||
) {
|
||||
let #(album_id, album) = item
|
||||
let tracks = album_utils.get_tracks(model.library, album)
|
||||
let artist_name = case album.artist_id {
|
||||
0 -> "Unknown artist"
|
||||
id -> library.assert_artist(model.library, id).name
|
||||
}
|
||||
let assert Ok(first_track) = list.first(tracks)
|
||||
|
||||
div(
|
||||
[
|
||||
attribute.id("album-list-" <> int.to_string(album_id)),
|
||||
attribute.class("library-item album-item"),
|
||||
attribute.type_("button"),
|
||||
event.on_click(StartPlay(tracks, 0)),
|
||||
attribute.attribute("role", "button"),
|
||||
],
|
||||
[
|
||||
case model.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),
|
||||
])
|
||||
_, _ ->
|
||||
div([attribute.class("artist-image-placeholder")], [text(album.name)])
|
||||
},
|
||||
h3([attribute.class("album-title")], [text(album.name)]),
|
||||
p([attribute.class("album-artist")], [text(artist_name)]),
|
||||
p(
|
||||
[attribute.class("album-tracks")],
|
||||
[text(int.to_string(list.length(album.tracks)) <> " tracks")],
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
|
@ -1,18 +1,16 @@
|
|||
//// A library view to all of the albums in the library.
|
||||
|
||||
import gleam/string
|
||||
import gleam/int
|
||||
import gleam/list
|
||||
import gleam/map
|
||||
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/album.{Album}
|
||||
import elekf/web/components/library_view.{LibraryItem, Model, StartPlay}
|
||||
import elekf/library/album_utils
|
||||
import elekf/web/components/library_view
|
||||
import elekf/web/components/library_item.{LibraryItem}
|
||||
import elekf/web/components/library_views/album_item
|
||||
import elekf/web/common
|
||||
|
||||
const component_name = "albums-view"
|
||||
|
@ -22,7 +20,7 @@ pub fn register() {
|
|||
library_view.register(
|
||||
component_name,
|
||||
data_getter,
|
||||
item_view,
|
||||
album_item.view,
|
||||
shuffler,
|
||||
search_filter,
|
||||
)
|
||||
|
@ -44,66 +42,10 @@ fn data_getter(library: Library) {
|
|||
fn shuffler(library, items: List(LibraryItem(Album))) {
|
||||
items
|
||||
|> list.shuffle()
|
||||
|> list.map(fn(album) { get_tracks(library, album.1) })
|
||||
|> list.map(fn(album) { album_utils.get_tracks(library, album.1) })
|
||||
|> list.flatten()
|
||||
}
|
||||
|
||||
fn search_filter(item: Album, search_text: String) {
|
||||
string.contains(item.name_lower, search_text)
|
||||
}
|
||||
|
||||
fn item_view(
|
||||
model: Model(Album),
|
||||
_items: List(LibraryItem(Album)),
|
||||
_index: Int,
|
||||
item: LibraryItem(Album),
|
||||
) {
|
||||
let #(album_id, album) = item
|
||||
let tracks = get_tracks(model.library, album)
|
||||
let artist_name = case album.artist_id {
|
||||
0 -> "Unknown artist"
|
||||
id -> library.assert_artist(model.library, id).name
|
||||
}
|
||||
let assert Ok(first_track) = list.first(tracks)
|
||||
|
||||
div(
|
||||
[
|
||||
attribute.id("album-list-" <> int.to_string(album_id)),
|
||||
attribute.class("library-item album-item"),
|
||||
attribute.type_("button"),
|
||||
event.on_click(StartPlay(tracks, 0)),
|
||||
attribute.attribute("role", "button"),
|
||||
],
|
||||
[
|
||||
case model.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),
|
||||
])
|
||||
_, _ ->
|
||||
div([attribute.class("artist-image-placeholder")], [text(album.name)])
|
||||
},
|
||||
h3([attribute.class("album-title")], [text(album.name)]),
|
||||
p([attribute.class("album-artist")], [text(artist_name)]),
|
||||
p(
|
||||
[attribute.class("album-tracks")],
|
||||
[text(int.to_string(list.length(album.tracks)) <> " tracks")],
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn get_tracks(library: Library, album: Album) {
|
||||
list.map(
|
||||
album.tracks,
|
||||
fn(track_id) { #(track_id, library.assert_track(library, track_id)) },
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@ import lustre/event
|
|||
import ibroadcast/artwork
|
||||
import elekf/library.{Library}
|
||||
import elekf/library/artist.{Artist}
|
||||
import elekf/web/components/library_view.{LibraryItem, Model, StartPlay}
|
||||
import elekf/web/components/library_view.{Model, ShowArtist}
|
||||
import elekf/web/components/library_item.{LibraryItem}
|
||||
import elekf/web/common
|
||||
|
||||
const component_name = "artists-view"
|
||||
|
@ -68,13 +69,12 @@ fn item_view(
|
|||
item: LibraryItem(Artist),
|
||||
) {
|
||||
let #(artist_id, artist) = item
|
||||
let tracks = get_tracks(model.library, artist)
|
||||
div(
|
||||
[
|
||||
attribute.id("artist-list-" <> int.to_string(artist_id)),
|
||||
attribute.class("library-item artist-item"),
|
||||
attribute.type_("button"),
|
||||
event.on_click(StartPlay(tracks, 0)),
|
||||
event.on_click(ShowArtist(item)),
|
||||
attribute.attribute("role", "button"),
|
||||
],
|
||||
[
|
||||
|
|
175
src/elekf/web/components/library_views/single_artist_view.gleam
Normal file
175
src/elekf/web/components/library_views/single_artist_view.gleam
Normal file
|
@ -0,0 +1,175 @@
|
|||
//// A library view to a single artist's albums.
|
||||
|
||||
import gleam/string
|
||||
import gleam/list
|
||||
import gleam/map
|
||||
import gleam/option
|
||||
import gleam/dynamic
|
||||
import gleam/result
|
||||
import lustre
|
||||
import lustre/element/html.{div, header}
|
||||
import lustre/element.{text}
|
||||
import lustre/attribute
|
||||
import lustre/effect
|
||||
import elekf/library.{Library}
|
||||
import elekf/library/album.{Album}
|
||||
import elekf/library/album_utils
|
||||
import elekf/library/artist.{Artist}
|
||||
import elekf/web/components/library_view
|
||||
import elekf/web/components/library_item.{LibraryItem}
|
||||
import elekf/web/components/library_views/album_item
|
||||
import elekf/web/common
|
||||
|
||||
const component_name = "single-artist-view"
|
||||
|
||||
type Model {
|
||||
Model(
|
||||
library_view: library_view.Model(Album),
|
||||
artist: option.Option(LibraryItem(Artist)),
|
||||
)
|
||||
}
|
||||
|
||||
type Msg {
|
||||
LibraryViewMsg(library_view.Msg)
|
||||
ArtistUpdated(option.Option(LibraryItem(Artist)))
|
||||
}
|
||||
|
||||
/// Register the single artist view as a custom element.
|
||||
pub fn register() {
|
||||
lustre.component(
|
||||
component_name,
|
||||
init,
|
||||
update,
|
||||
generate_view(search_filter),
|
||||
map.merge(
|
||||
library_view.generic_attributes()
|
||||
|> map.map_values(fn(_key, decoder) {
|
||||
fn(data: dynamic.Dynamic) {
|
||||
data
|
||||
|> decoder()
|
||||
|> result.map(LibraryViewMsg)
|
||||
}
|
||||
}),
|
||||
map.from_list([#("artist", artist_decode)]),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// Render the single artist view.
|
||||
pub fn render(
|
||||
library: Library,
|
||||
artist: LibraryItem(Artist),
|
||||
settings: option.Option(common.Settings),
|
||||
extra_attrs: List(attribute.Attribute(msg)),
|
||||
) {
|
||||
library_view.render(
|
||||
component_name,
|
||||
library,
|
||||
settings,
|
||||
list.flatten([
|
||||
[attribute.property("artist", option.Some(artist))],
|
||||
extra_attrs,
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
fn init() {
|
||||
let #(lib_m, lib_e) =
|
||||
library_view.init(
|
||||
library.empty(),
|
||||
fn(_) -> List(LibraryItem(Album)) { [] },
|
||||
shuffler,
|
||||
)
|
||||
|
||||
#(
|
||||
Model(artist: option.None, library_view: lib_m),
|
||||
effect.map(lib_e, LibraryViewMsg),
|
||||
)
|
||||
}
|
||||
|
||||
fn update(model: Model, msg) {
|
||||
case msg {
|
||||
ArtistUpdated(option.Some(artist)) -> #(
|
||||
Model(
|
||||
artist: option.Some(artist),
|
||||
library_view: library_view.Model(
|
||||
..model.library_view,
|
||||
data: data_getter(model.library_view.library, artist),
|
||||
),
|
||||
),
|
||||
effect.none(),
|
||||
)
|
||||
ArtistUpdated(option.None) -> #(
|
||||
Model(
|
||||
artist: option.None,
|
||||
library_view: library_view.Model(..model.library_view, data: []),
|
||||
),
|
||||
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, artist: LibraryItem(Artist)) {
|
||||
library.albums
|
||||
|> map.fold(
|
||||
[],
|
||||
fn(acc, key, val) {
|
||||
case val.artist_id == artist.0 {
|
||||
True -> [#(key, val), ..acc]
|
||||
False -> acc
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn shuffler(library, items: List(LibraryItem(Album))) {
|
||||
items
|
||||
|> list.shuffle()
|
||||
|> list.map(fn(album) { album_utils.get_tracks(library, album.1) })
|
||||
|> list.flatten()
|
||||
}
|
||||
|
||||
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) {
|
||||
case model.artist {
|
||||
option.None ->
|
||||
div(
|
||||
[attribute.class("library-view-not-specified")],
|
||||
[text("Artist not specified.")],
|
||||
)
|
||||
option.Some(artist) -> view(model, artist, search_filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(
|
||||
model: Model,
|
||||
artist: LibraryItem(Artist),
|
||||
search_filter: library_view.SearchFilter(Album),
|
||||
) {
|
||||
div(
|
||||
[],
|
||||
[
|
||||
header([attribute.class("library-header")], [text({ artist.1 }.name)]),
|
||||
library_view.library_view(
|
||||
model.library_view,
|
||||
album_item.view,
|
||||
search_filter,
|
||||
)
|
||||
|> element.map(LibraryViewMsg),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn artist_decode(data: dynamic.Dynamic) {
|
||||
let artist: option.Option(LibraryItem(Artist)) = dynamic.unsafe_coerce(data)
|
||||
Ok(ArtistUpdated(artist))
|
||||
}
|
|
@ -11,7 +11,8 @@ import lustre/attribute
|
|||
import lustre/event
|
||||
import elekf/library.{Library}
|
||||
import elekf/library/track.{Track}
|
||||
import elekf/web/components/library_view.{LibraryItem, Model, StartPlay}
|
||||
import elekf/web/components/library_view.{Model, StartPlay}
|
||||
import elekf/web/components/library_item.{LibraryItem}
|
||||
import elekf/web/common
|
||||
|
||||
const component_name = "tracks-view"
|
||||
|
|
38
src/elekf/web/events/show_artist.gleam
Normal file
38
src/elekf/web/events/show_artist.gleam
Normal file
|
@ -0,0 +1,38 @@
|
|||
import gleam/result
|
||||
import gleam/dynamic
|
||||
import lustre/event
|
||||
import elekf/library/artist.{Artist}
|
||||
import elekf/web/components/library_item.{LibraryItem}
|
||||
import elekf/utils/custom_event.{CustomEvent}
|
||||
|
||||
const event_name = "show-artist"
|
||||
|
||||
pub type EventData {
|
||||
EventData(artist: LibraryItem(Artist))
|
||||
}
|
||||
|
||||
pub fn emit(artist) {
|
||||
event.emit(event_name, EventData(artist))
|
||||
}
|
||||
|
||||
pub fn on(msg: fn(LibraryItem(Artist)) -> b) {
|
||||
event.on(
|
||||
event_name,
|
||||
fn(data) {
|
||||
data
|
||||
|> decoder
|
||||
|> result.map(fn(e) { msg(e.artist) })
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
|
@ -15,6 +15,7 @@ import elekf/web/utils
|
|||
import elekf/web/components/library_views/tracks_view
|
||||
import elekf/web/components/library_views/artists_view
|
||||
import elekf/web/components/library_views/albums_view
|
||||
import elekf/web/components/library_views/single_artist_view
|
||||
import elekf/api/auth/storage as auth_storage
|
||||
import elekf/api/auth/models as auth_models
|
||||
import elekf/utils/option as option_utils
|
||||
|
@ -39,6 +40,7 @@ pub fn main() {
|
|||
let assert Ok(_) = tracks_view.register()
|
||||
let assert Ok(_) = artists_view.register()
|
||||
let assert Ok(_) = albums_view.register()
|
||||
let assert Ok(_) = single_artist_view.register()
|
||||
|
||||
let app = lustre.application(init, update, view)
|
||||
let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
|
||||
|
|
Loading…
Reference in a new issue