Add single album view
This commit is contained in:
parent
451b619f5b
commit
830918c344
13 changed files with 421 additions and 197 deletions
|
@ -25,16 +25,15 @@ import elekf/library/track.{type Track}
|
||||||
import elekf/transfer/library as library_transfer
|
import elekf/transfer/library as library_transfer
|
||||||
import elekf/web/router
|
import elekf/web/router
|
||||||
import elekf/web/components/player
|
import elekf/web/components/player
|
||||||
import elekf/web/components/library_item.{type LibraryItem}
|
|
||||||
import elekf/web/components/library_view
|
import elekf/web/components/library_view
|
||||||
import elekf/web/components/library_views/tracks_view
|
import elekf/web/components/library_views/tracks_view
|
||||||
import elekf/web/components/library_views/artists_view
|
import elekf/web/components/library_views/artists_view
|
||||||
import elekf/web/components/library_views/albums_view
|
import elekf/web/components/library_views/albums_view
|
||||||
import elekf/web/components/library_views/single_artist_view
|
import elekf/web/components/library_views/single_artist_view
|
||||||
|
import elekf/web/components/library_views/single_album_view
|
||||||
import elekf/web/components/button_group
|
import elekf/web/components/button_group
|
||||||
import elekf/web/components/link
|
import elekf/web/components/link
|
||||||
import elekf/web/events/start_play
|
import elekf/web/events/start_play
|
||||||
import elekf/web/events/show_artist
|
|
||||||
import elekf/web/utils
|
import elekf/web/utils
|
||||||
import elekf/web/components/icon.{Alt, icon}
|
import elekf/web/components/icon.{Alt, icon}
|
||||||
import elektrofoni
|
import elektrofoni
|
||||||
|
@ -83,20 +82,29 @@ pub type Msg {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(auth_data: common.AuthData) {
|
pub fn init(auth_data: common.AuthData) {
|
||||||
let model =
|
let initial_library = library.empty()
|
||||||
Model(
|
|
||||||
loading_library: True,
|
|
||||||
library: library.empty(),
|
|
||||||
settings: option.None,
|
|
||||||
request_config: form_request_config(auth_data),
|
|
||||||
play_status: NoTracks,
|
|
||||||
view: library_view.Tracks,
|
|
||||||
)
|
|
||||||
|
|
||||||
let router_effect =
|
let router_effect =
|
||||||
effect.from(fn(dispatch) { router.init(dispatch) })
|
effect.from(fn(dispatch) { router.init(dispatch) })
|
||||||
|> effect.map(Router)
|
|> effect.map(Router)
|
||||||
|
|
||||||
|
let initial_view =
|
||||||
|
router.get_current_path()
|
||||||
|
|> router.parse()
|
||||||
|
|> result.unwrap(router.TrackList)
|
||||||
|
|> route_to_view()
|
||||||
|
|> result.unwrap(library_view.Tracks)
|
||||||
|
|
||||||
|
let model =
|
||||||
|
Model(
|
||||||
|
loading_library: True,
|
||||||
|
library: initial_library,
|
||||||
|
settings: option.None,
|
||||||
|
request_config: form_request_config(auth_data),
|
||||||
|
play_status: NoTracks,
|
||||||
|
view: initial_view,
|
||||||
|
)
|
||||||
|
|
||||||
#(model, effect.batch([load_library(model), router_effect]))
|
#(model, effect.batch([load_library(model), router_effect]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,8 +191,14 @@ pub fn update(model: Model, msg) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Router(router.RouteChanged(route)) -> {
|
Router(router.RouteChanged(route)) -> {
|
||||||
let view = result.unwrap(route_to_view(route), library_view.Tracks)
|
case route_to_view(route) {
|
||||||
#(Model(..model, view: view), effect.none())
|
Ok(view) -> #(Model(..model, view: view), effect.none())
|
||||||
|
Error(_) -> {
|
||||||
|
io.println_error("Unable to change to route:")
|
||||||
|
io.debug(route)
|
||||||
|
#(model, effect.none())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,13 +219,13 @@ pub fn view(model: Model) {
|
||||||
div([attribute.id("authed-view-library")], [
|
div([attribute.id("authed-view-library")], [
|
||||||
nav([attribute.id("library-top-nav")], [
|
nav([attribute.id("library-top-nav")], [
|
||||||
button_group.view("", [], [
|
button_group.view("", [], [
|
||||||
link.view(router.to_hash(router.TrackList), "", [], [
|
link.button(router.to_hash(router.TrackList), "", [], [
|
||||||
icon("music-note-beamed", Alt("Tracks")),
|
icon("music-note-beamed", Alt("Tracks")),
|
||||||
]),
|
]),
|
||||||
link.view(router.to_hash(router.ArtistList), "", [], [
|
link.button(router.to_hash(router.ArtistList), "", [], [
|
||||||
icon("file-person", Alt("Artists")),
|
icon("file-person", Alt("Artists")),
|
||||||
]),
|
]),
|
||||||
link.view(router.to_hash(router.AlbumList), "", [], [
|
link.button(router.to_hash(router.AlbumList), "", [], [
|
||||||
icon("disc", Alt("Albums")),
|
icon("disc", Alt("Albums")),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
@ -234,12 +248,18 @@ pub fn view(model: Model) {
|
||||||
attribute.class("glass-bg"),
|
attribute.class("glass-bg"),
|
||||||
start_play.on(StartPlay),
|
start_play.on(StartPlay),
|
||||||
])
|
])
|
||||||
library_view.SingleArtist(artist_info) ->
|
library_view.SingleArtist(id) ->
|
||||||
single_artist_view.render(model.library, artist_info, model.settings, [
|
single_artist_view.render(model.library, id, model.settings, [
|
||||||
attribute.id("single-artist-view"),
|
attribute.id("single-artist-view"),
|
||||||
attribute.class("glass-bg"),
|
attribute.class("glass-bg"),
|
||||||
start_play.on(StartPlay),
|
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),
|
||||||
|
])
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
div(
|
div(
|
||||||
|
@ -382,5 +402,10 @@ fn route_to_view(route: router.Route) {
|
||||||
router.TrackList -> Ok(library_view.Tracks)
|
router.TrackList -> Ok(library_view.Tracks)
|
||||||
router.ArtistList -> Ok(library_view.Artists)
|
router.ArtistList -> Ok(library_view.Artists)
|
||||||
router.AlbumList -> Ok(library_view.Albums)
|
router.AlbumList -> Ok(library_view.Albums)
|
||||||
|
router.Artist(id) -> Ok(library_view.SingleArtist(id))
|
||||||
|
// TODO
|
||||||
|
router.Album(id) -> Ok(library_view.SingleAlbum(id))
|
||||||
|
// TODO
|
||||||
|
router.Settings -> Error(Nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
import gleam/dynamic
|
import gleam/dynamic
|
||||||
import gleam/list
|
import gleam/list
|
||||||
import gleam/map
|
import gleam/dict
|
||||||
import gleam/option
|
import gleam/option
|
||||||
import gleam/string
|
import gleam/string
|
||||||
import lustre
|
import lustre
|
||||||
|
@ -13,7 +13,7 @@ import lustre/element
|
||||||
import lustre/element/html.{div}
|
import lustre/element/html.{div}
|
||||||
import lustre/attribute
|
import lustre/attribute
|
||||||
import lustre/event
|
import lustre/event
|
||||||
import elekf/utils/order.{type Sorter} as order_utils
|
import elekf/utils/order.{type Sorter}
|
||||||
import elekf/library.{type Library}
|
import elekf/library.{type Library}
|
||||||
import elekf/library/track.{type Track}
|
import elekf/library/track.{type Track}
|
||||||
import elekf/library/artist.{type Artist}
|
import elekf/library/artist.{type Artist}
|
||||||
|
@ -54,11 +54,11 @@ pub type View {
|
||||||
/// All albums.
|
/// All albums.
|
||||||
Albums
|
Albums
|
||||||
/// Tracks of a single album.
|
/// Tracks of a single album.
|
||||||
/// SingleAlbum(Int, Album)
|
SingleAlbum(Int)
|
||||||
/// All artists.
|
/// All artists.
|
||||||
Artists
|
Artists
|
||||||
/// Albums of a single artist.
|
/// Albums of a single artist.
|
||||||
SingleArtist(LibraryItem(Artist))
|
SingleArtist(Int)
|
||||||
/// Tracks of a single artist.
|
/// Tracks of a single artist.
|
||||||
/// SingleArtistTracks(Int, Artist)
|
/// SingleArtistTracks(Int, Artist)
|
||||||
/// All tracks.
|
/// All tracks.
|
||||||
|
@ -73,11 +73,13 @@ pub type Msg {
|
||||||
ShowArtist(LibraryItem(Artist))
|
ShowArtist(LibraryItem(Artist))
|
||||||
Search(search.Msg)
|
Search(search.Msg)
|
||||||
AlbumExpandToggled(LibraryItem(Album))
|
AlbumExpandToggled(LibraryItem(Album))
|
||||||
|
FilterUpdated
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Model(a) {
|
pub type Model(a) {
|
||||||
Model(
|
Model(
|
||||||
library: Library,
|
library: Library,
|
||||||
|
library_loading: Bool,
|
||||||
data: List(LibraryItem(a)),
|
data: List(LibraryItem(a)),
|
||||||
data_getter: DataGetter(a),
|
data_getter: DataGetter(a),
|
||||||
shuffler: Shuffler(a),
|
shuffler: Shuffler(a),
|
||||||
|
@ -106,7 +108,7 @@ pub fn register(
|
||||||
) {
|
) {
|
||||||
lustre.component(
|
lustre.component(
|
||||||
name,
|
name,
|
||||||
fn() { init(library.empty(), data_getter, shuffler, sorter) },
|
fn() { init(library.empty(), True, data_getter, shuffler, sorter) },
|
||||||
update,
|
update,
|
||||||
generate_view(item_view, search_filter),
|
generate_view(item_view, search_filter),
|
||||||
generic_attributes(),
|
generic_attributes(),
|
||||||
|
@ -116,7 +118,7 @@ pub fn register(
|
||||||
/// Get the generic properties common to all library views, that can be input
|
/// Get the generic properties common to all library views, that can be input
|
||||||
/// into the Lustre component attribute change
|
/// into the Lustre component attribute change
|
||||||
pub fn generic_attributes() {
|
pub fn generic_attributes() {
|
||||||
map.from_list([#("library", library_decode), #("settings", settings_decode)])
|
dict.from_list([#("library", library_decode), #("settings", settings_decode)])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render the component using a custom element.
|
/// Render the component using a custom element.
|
||||||
|
@ -137,10 +139,11 @@ pub fn render(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(library, data_getter, shuffler, sorter) {
|
pub fn init(library, library_loading, data_getter, shuffler, sorter) {
|
||||||
#(
|
#(
|
||||||
Model(
|
Model(
|
||||||
library,
|
library,
|
||||||
|
library_loading,
|
||||||
data_getter(library),
|
data_getter(library),
|
||||||
data_getter,
|
data_getter,
|
||||||
shuffler,
|
shuffler,
|
||||||
|
@ -155,13 +158,8 @@ pub fn init(library, data_getter, shuffler, sorter) {
|
||||||
pub fn update(model, msg) {
|
pub fn update(model, msg) {
|
||||||
case msg {
|
case msg {
|
||||||
LibraryUpdated(library) -> #(
|
LibraryUpdated(library) -> #(
|
||||||
Model(
|
Model(..model, library: library, library_loading: False)
|
||||||
..model,
|
|> update_data(library),
|
||||||
library: library,
|
|
||||||
data: library
|
|
||||||
|> model.data_getter()
|
|
||||||
|> list.sort(fn(a, b) { model.sorter(a.1, b.1) }),
|
|
||||||
),
|
|
||||||
effect.none(),
|
effect.none(),
|
||||||
)
|
)
|
||||||
SettingsUpdated(settings) -> #(
|
SettingsUpdated(settings) -> #(
|
||||||
|
@ -178,6 +176,8 @@ pub fn update(model, msg) {
|
||||||
|
|
||||||
// Base case, this should be handled in an implementing view
|
// Base case, this should be handled in an implementing view
|
||||||
AlbumExpandToggled(_album) -> #(model, effect.none())
|
AlbumExpandToggled(_album) -> #(model, effect.none())
|
||||||
|
|
||||||
|
FilterUpdated -> #(update_data(model, model.library), effect.none())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,3 +227,12 @@ fn settings_decode(data: dynamic.Dynamic) {
|
||||||
let settings: option.Option(common.Settings) = dynamic.unsafe_coerce(data)
|
let settings: option.Option(common.Settings) = dynamic.unsafe_coerce(data)
|
||||||
Ok(SettingsUpdated(settings))
|
Ok(SettingsUpdated(settings))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_data(model: Model(a), library: Library) {
|
||||||
|
Model(
|
||||||
|
..model,
|
||||||
|
data: library
|
||||||
|
|> model.data_getter()
|
||||||
|
|> list.sort(fn(a, b) { model.sorter(a.1, b.1) }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -17,11 +17,9 @@ import elekf/web/components/library_item.{type LibraryItem}
|
||||||
import elekf/web/components/library_views/track_item
|
import elekf/web/components/library_views/track_item
|
||||||
import elekf/web/components/thumbnail
|
import elekf/web/components/thumbnail
|
||||||
import elekf/web/components/shuffle_all
|
import elekf/web/components/shuffle_all
|
||||||
|
import elekf/web/components/link
|
||||||
import elekf/web/common.{type Settings}
|
import elekf/web/common.{type Settings}
|
||||||
|
import elekf/web/router
|
||||||
const base_classes = "library-item album-item"
|
|
||||||
|
|
||||||
const showing_tracks_class = "library-item-expanded"
|
|
||||||
|
|
||||||
pub fn view(
|
pub fn view(
|
||||||
library: Library,
|
library: Library,
|
||||||
|
@ -37,32 +35,12 @@ pub fn view(
|
||||||
}
|
}
|
||||||
let assert Ok(first_track) = list.first(tracks)
|
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(
|
list.append(
|
||||||
[
|
[
|
||||||
div(
|
link.link(
|
||||||
[
|
router.to_hash(router.Album(album_id)),
|
||||||
attribute.id("album-list-" <> int.to_string(album_id)),
|
"library-item album-item",
|
||||||
attribute.class(class),
|
[attribute.id("album-list-" <> int.to_string(album_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"),
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
thumbnail.maybe_item_thumbnail(
|
thumbnail.maybe_item_thumbnail(
|
||||||
settings,
|
settings,
|
||||||
|
@ -77,8 +55,6 @@ pub fn view(
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
],
|
|
||||||
case show_tracks {
|
case show_tracks {
|
||||||
True -> [view_tracks(library, tracks)]
|
True -> [view_tracks(library, tracks)]
|
||||||
False -> []
|
False -> []
|
||||||
|
@ -99,7 +75,7 @@ pub fn view_tracks(library: Library, tracks: List(LibraryItem(Track))) {
|
||||||
list.flatten([
|
list.flatten([
|
||||||
[shuffle_all.view([event.on_click(ShuffleAll)])],
|
[shuffle_all.view([event.on_click(ShuffleAll)])],
|
||||||
list.index_map(tracks, fn(track_item, index) {
|
list.index_map(tracks, fn(track_item, index) {
|
||||||
track_item.view(library, tracks, track_item, index, "album-tracks-list")
|
track_item.view(library, tracks, index, track_item, "album-tracks-list")
|
||||||
})
|
})
|
||||||
|> list.flatten(),
|
|> list.flatten(),
|
||||||
]),
|
]),
|
||||||
|
|
|
@ -60,6 +60,7 @@ fn init() {
|
||||||
let #(lib_m, lib_e) =
|
let #(lib_m, lib_e) =
|
||||||
library_view.init(
|
library_view.init(
|
||||||
library.empty(),
|
library.empty(),
|
||||||
|
True,
|
||||||
data_getter,
|
data_getter,
|
||||||
shuffler,
|
shuffler,
|
||||||
album_utils.sort_by_name,
|
album_utils.sort_by_name,
|
||||||
|
|
|
@ -5,17 +5,18 @@ import gleam/int
|
||||||
import gleam/list
|
import gleam/list
|
||||||
import gleam/dict
|
import gleam/dict
|
||||||
import gleam/option
|
import gleam/option
|
||||||
import lustre/element/html.{div, h3, p}
|
import lustre/element/html.{h3, p}
|
||||||
import lustre/element.{text}
|
import lustre/element.{text}
|
||||||
import lustre/attribute
|
import lustre/attribute
|
||||||
import lustre/event
|
|
||||||
import elekf/library.{type Library}
|
import elekf/library.{type Library}
|
||||||
import elekf/library/artist.{type Artist}
|
import elekf/library/artist.{type Artist}
|
||||||
import elekf/library/artist_utils
|
import elekf/library/artist_utils
|
||||||
import elekf/web/components/library_view.{type Model, ShowArtist}
|
import elekf/web/components/library_view.{type Model}
|
||||||
import elekf/web/components/library_item.{type LibraryItem}
|
import elekf/web/components/library_item.{type LibraryItem}
|
||||||
import elekf/web/components/thumbnail
|
import elekf/web/components/thumbnail
|
||||||
import elekf/web/common
|
import elekf/web/common
|
||||||
|
import elekf/web/router
|
||||||
|
import elekf/web/components/link
|
||||||
|
|
||||||
const component_name = "artists-view"
|
const component_name = "artists-view"
|
||||||
|
|
||||||
|
@ -69,14 +70,10 @@ fn item_view(
|
||||||
) {
|
) {
|
||||||
let #(artist_id, artist) = item
|
let #(artist_id, artist) = item
|
||||||
[
|
[
|
||||||
div(
|
link.link(
|
||||||
[
|
router.to_hash(router.Artist(artist_id)),
|
||||||
attribute.id("artist-list-" <> int.to_string(artist_id)),
|
"library-item artist-item",
|
||||||
attribute.class("library-item artist-item"),
|
[attribute.id("artist-list-" <> int.to_string(artist_id))],
|
||||||
attribute.type_("button"),
|
|
||||||
event.on_click(ShowArtist(item)),
|
|
||||||
attribute.attribute("role", "button"),
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
thumbnail.maybe_item_thumbnail(
|
thumbnail.maybe_item_thumbnail(
|
||||||
model.settings,
|
model.settings,
|
||||||
|
|
200
src/elekf/web/components/library_views/single_album_view.gleam
Normal file
200
src/elekf/web/components/library_views/single_album_view.gleam
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
//// A library view to a single album's tracks.
|
||||||
|
|
||||||
|
import gleam/string
|
||||||
|
import gleam/list
|
||||||
|
import gleam/dict
|
||||||
|
import gleam/option
|
||||||
|
import gleam/dynamic
|
||||||
|
import gleam/result
|
||||||
|
import gleam/int
|
||||||
|
import lustre
|
||||||
|
import lustre/element/html.{div, header}
|
||||||
|
import lustre/element.{text}
|
||||||
|
import lustre/attribute
|
||||||
|
import lustre/effect
|
||||||
|
import elekf/library.{type Library}
|
||||||
|
import elekf/library/album.{type Album}
|
||||||
|
import elekf/library/track.{type Track}
|
||||||
|
import elekf/library/track_utils
|
||||||
|
import elekf/web/components/library_view
|
||||||
|
import elekf/web/components/library_item.{type LibraryItem}
|
||||||
|
import elekf/web/components/library_views/track_item
|
||||||
|
import elekf/web/common
|
||||||
|
|
||||||
|
const component_name = "single-album-view"
|
||||||
|
|
||||||
|
type Model {
|
||||||
|
Model(
|
||||||
|
library_view: library_view.Model(Track),
|
||||||
|
album_id: Int,
|
||||||
|
album: option.Option(Album),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Msg {
|
||||||
|
LibraryViewMsg(library_view.Msg)
|
||||||
|
AlbumUpdated(Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register the single album view as a custom element.
|
||||||
|
pub fn register() {
|
||||||
|
lustre.component(
|
||||||
|
component_name,
|
||||||
|
init,
|
||||||
|
update,
|
||||||
|
generate_view(search_filter),
|
||||||
|
dict.merge(
|
||||||
|
library_view.generic_attributes()
|
||||||
|
|> dict.map_values(fn(_key, decoder) {
|
||||||
|
fn(data: dynamic.Dynamic) {
|
||||||
|
data
|
||||||
|
|> decoder()
|
||||||
|
|> result.map(LibraryViewMsg)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
dict.from_list([#("album-id", id_decode)]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render the single album view.
|
||||||
|
pub fn render(
|
||||||
|
library: Library,
|
||||||
|
album_id: Int,
|
||||||
|
settings: option.Option(common.Settings),
|
||||||
|
extra_attrs: List(attribute.Attribute(msg)),
|
||||||
|
) {
|
||||||
|
library_view.render(component_name, library, settings, [
|
||||||
|
attribute.property("album-id", album_id),
|
||||||
|
..extra_attrs
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init() {
|
||||||
|
let #(lib_m, lib_e) =
|
||||||
|
library_view.init(
|
||||||
|
library.empty(),
|
||||||
|
True,
|
||||||
|
fn(_) -> List(LibraryItem(Track)) { [] },
|
||||||
|
shuffler,
|
||||||
|
track_utils.sort_by_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
#(
|
||||||
|
Model(album_id: library.invalid_id, album: option.None, library_view: lib_m),
|
||||||
|
effect.map(lib_e, LibraryViewMsg),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(model: Model, msg) {
|
||||||
|
case msg {
|
||||||
|
AlbumUpdated(id) -> {
|
||||||
|
let new_getter = data_getter(_, id)
|
||||||
|
|
||||||
|
let album =
|
||||||
|
load_album(model.library_view.library, id)
|
||||||
|
|> option.from_result()
|
||||||
|
|
||||||
|
#(
|
||||||
|
Model(
|
||||||
|
album_id: id,
|
||||||
|
album: album,
|
||||||
|
library_view: library_view.Model(
|
||||||
|
..model.library_view,
|
||||||
|
data_getter: new_getter,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
effect.map(
|
||||||
|
effect.from(fn(dispatch) { dispatch(library_view.FilterUpdated) }),
|
||||||
|
LibraryViewMsg,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
LibraryViewMsg(lib_msg) -> {
|
||||||
|
let #(lib_m, lib_e) = library_view.update(model.library_view, lib_msg)
|
||||||
|
|
||||||
|
// Update album when library is updated
|
||||||
|
let album = case lib_msg {
|
||||||
|
library_view.LibraryUpdated(new_lib) ->
|
||||||
|
load_album(new_lib, model.album_id)
|
||||||
|
|> option.from_result()
|
||||||
|
_ -> model.album
|
||||||
|
}
|
||||||
|
|
||||||
|
#(
|
||||||
|
Model(..model, album: album, library_view: lib_m),
|
||||||
|
effect.map(lib_e, LibraryViewMsg),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_getter(library: Library, album_id: Int) {
|
||||||
|
library.tracks
|
||||||
|
|> dict.fold([], fn(acc, key, val) {
|
||||||
|
case val.album_id == album_id {
|
||||||
|
True -> [#(key, val), ..acc]
|
||||||
|
False -> acc
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shuffler(_library, items: List(LibraryItem(Track))) {
|
||||||
|
items
|
||||||
|
|> list.shuffle()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_filter(item: Track, search_text: String) {
|
||||||
|
string.contains(item.title_lower, search_text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_view(search_filter: library_view.SearchFilter(Track)) {
|
||||||
|
fn(model: Model) {
|
||||||
|
case model.library_view.library_loading, model.album {
|
||||||
|
True, _ ->
|
||||||
|
div([attribute.class("library-view-loading")], [
|
||||||
|
text("Loading library…"),
|
||||||
|
])
|
||||||
|
False, option.None ->
|
||||||
|
div([attribute.class("library-view-not-specified")], [
|
||||||
|
text("Album not found with ID: " <> int.to_string(model.album_id)),
|
||||||
|
])
|
||||||
|
False, option.Some(album) ->
|
||||||
|
view(model, #(model.album_id, album), search_filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(
|
||||||
|
model: Model,
|
||||||
|
album: LibraryItem(Album),
|
||||||
|
search_filter: library_view.SearchFilter(Track),
|
||||||
|
) {
|
||||||
|
div([], [
|
||||||
|
header([attribute.class("library-header")], [text({ album.1 }.name)]),
|
||||||
|
library_view.library_view(
|
||||||
|
model.library_view,
|
||||||
|
fn(library, items, index, item) {
|
||||||
|
track_item.view(
|
||||||
|
library.library,
|
||||||
|
items,
|
||||||
|
index,
|
||||||
|
item,
|
||||||
|
"album-tracks-list",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
search_filter,
|
||||||
|
)
|
||||||
|
|> element.map(LibraryViewMsg),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id_decode(data: dynamic.Dynamic) {
|
||||||
|
let album_id: Int = dynamic.unsafe_coerce(data)
|
||||||
|
Ok(AlbumUpdated(album_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_album(library: Library, id: Int) {
|
||||||
|
library.get_album(library, id)
|
||||||
|
}
|
|
@ -2,21 +2,21 @@
|
||||||
|
|
||||||
import gleam/string
|
import gleam/string
|
||||||
import gleam/list
|
import gleam/list
|
||||||
import gleam/map
|
import gleam/dict
|
||||||
import gleam/option
|
import gleam/option
|
||||||
import gleam/dynamic
|
import gleam/dynamic
|
||||||
import gleam/result
|
import gleam/result
|
||||||
|
import gleam/int
|
||||||
import lustre
|
import lustre
|
||||||
import lustre/element/html.{div, header}
|
import lustre/element/html.{div, header}
|
||||||
import lustre/element.{text}
|
import lustre/element.{text}
|
||||||
import lustre/attribute
|
import lustre/attribute
|
||||||
import lustre/effect
|
import lustre/effect
|
||||||
import elekf/utils/misc
|
|
||||||
import elekf/library.{type Library}
|
import elekf/library.{type Library}
|
||||||
import elekf/library/album.{type Album}
|
import elekf/library/album.{type Album}
|
||||||
import elekf/library/album_utils
|
import elekf/library/album_utils
|
||||||
import elekf/library/artist.{type Artist}
|
import elekf/library/artist.{type Artist}
|
||||||
import elekf/web/components/library_view.{AlbumExpandToggled}
|
import elekf/web/components/library_view
|
||||||
import elekf/web/components/library_item.{type LibraryItem}
|
import elekf/web/components/library_item.{type LibraryItem}
|
||||||
import elekf/web/components/library_views/album_item
|
import elekf/web/components/library_views/album_item
|
||||||
import elekf/web/common
|
import elekf/web/common
|
||||||
|
@ -26,14 +26,14 @@ const component_name = "single-artist-view"
|
||||||
type Model {
|
type Model {
|
||||||
Model(
|
Model(
|
||||||
library_view: library_view.Model(Album),
|
library_view: library_view.Model(Album),
|
||||||
artist: option.Option(LibraryItem(Artist)),
|
artist_id: Int,
|
||||||
expanded_album: Int,
|
artist: option.Option(Artist),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Msg {
|
type Msg {
|
||||||
LibraryViewMsg(library_view.Msg)
|
LibraryViewMsg(library_view.Msg)
|
||||||
ArtistUpdated(option.Option(LibraryItem(Artist)))
|
ArtistUpdated(Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register the single artist view as a custom element.
|
/// Register the single artist view as a custom element.
|
||||||
|
@ -43,16 +43,16 @@ pub fn register() {
|
||||||
init,
|
init,
|
||||||
update,
|
update,
|
||||||
generate_view(search_filter),
|
generate_view(search_filter),
|
||||||
map.merge(
|
dict.merge(
|
||||||
library_view.generic_attributes()
|
library_view.generic_attributes()
|
||||||
|> map.map_values(fn(_key, decoder) {
|
|> dict.map_values(fn(_key, decoder) {
|
||||||
fn(data: dynamic.Dynamic) {
|
fn(data: dynamic.Dynamic) {
|
||||||
data
|
data
|
||||||
|> decoder()
|
|> decoder()
|
||||||
|> result.map(LibraryViewMsg)
|
|> result.map(LibraryViewMsg)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
map.from_list([#("artist", artist_decode)]),
|
dict.from_list([#("artist-id", id_decode)]),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -60,22 +60,21 @@ pub fn register() {
|
||||||
/// Render the single artist view.
|
/// Render the single artist view.
|
||||||
pub fn render(
|
pub fn render(
|
||||||
library: Library,
|
library: Library,
|
||||||
artist: LibraryItem(Artist),
|
artist_id: Int,
|
||||||
settings: option.Option(common.Settings),
|
settings: option.Option(common.Settings),
|
||||||
extra_attrs: List(attribute.Attribute(msg)),
|
extra_attrs: List(attribute.Attribute(msg)),
|
||||||
) {
|
) {
|
||||||
library_view.render(
|
library_view.render(component_name, library, settings, [
|
||||||
component_name,
|
attribute.property("artist-id", artist_id),
|
||||||
library,
|
..extra_attrs
|
||||||
settings,
|
])
|
||||||
[attribute.property("artist", option.Some(artist)), ..extra_attrs],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init() {
|
fn init() {
|
||||||
let #(lib_m, lib_e) =
|
let #(lib_m, lib_e) =
|
||||||
library_view.init(
|
library_view.init(
|
||||||
library.empty(),
|
library.empty(),
|
||||||
|
True,
|
||||||
fn(_) -> List(LibraryItem(Album)) { [] },
|
fn(_) -> List(LibraryItem(Album)) { [] },
|
||||||
shuffler,
|
shuffler,
|
||||||
album_utils.sort_by_year,
|
album_utils.sort_by_year,
|
||||||
|
@ -83,8 +82,8 @@ fn init() {
|
||||||
|
|
||||||
#(
|
#(
|
||||||
Model(
|
Model(
|
||||||
|
artist_id: library.invalid_id,
|
||||||
artist: option.None,
|
artist: option.None,
|
||||||
expanded_album: library.invalid_id,
|
|
||||||
library_view: lib_m,
|
library_view: lib_m,
|
||||||
),
|
),
|
||||||
effect.map(lib_e, LibraryViewMsg),
|
effect.map(lib_e, LibraryViewMsg),
|
||||||
|
@ -93,55 +92,56 @@ fn init() {
|
||||||
|
|
||||||
fn update(model: Model, msg) {
|
fn update(model: Model, msg) {
|
||||||
case msg {
|
case msg {
|
||||||
ArtistUpdated(option.Some(artist)) -> #(
|
ArtistUpdated(id) -> {
|
||||||
|
let new_getter = data_getter(_, id)
|
||||||
|
|
||||||
|
let artist =
|
||||||
|
load_artist(model.library_view.library, id)
|
||||||
|
|> option.from_result()
|
||||||
|
|
||||||
|
#(
|
||||||
Model(
|
Model(
|
||||||
artist: option.Some(artist),
|
artist_id: id,
|
||||||
expanded_album: library.invalid_id,
|
artist: artist,
|
||||||
library_view: library_view.Model(
|
library_view: library_view.Model(
|
||||||
..model.library_view,
|
..model.library_view,
|
||||||
data: data_getter(model.library_view.library, artist)
|
data_getter: new_getter,
|
||||||
|> list.sort(fn(a, b) { model.library_view.sorter(a.1, b.1) }),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
effect.none(),
|
effect.map(
|
||||||
)
|
effect.from(fn(dispatch) { dispatch(library_view.FilterUpdated) }),
|
||||||
ArtistUpdated(option.None) -> #(
|
LibraryViewMsg,
|
||||||
Model(
|
),
|
||||||
artist: option.None,
|
|
||||||
expanded_album: library.invalid_id,
|
|
||||||
library_view: library_view.Model(..model.library_view, data: []),
|
|
||||||
),
|
|
||||||
effect.none(),
|
|
||||||
)
|
|
||||||
LibraryViewMsg(AlbumExpandToggled(album)) -> #(
|
|
||||||
Model(
|
|
||||||
..model,
|
|
||||||
expanded_album: misc.toggle(
|
|
||||||
model.expanded_album,
|
|
||||||
album.0,
|
|
||||||
library.invalid_id,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
effect.none(),
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
LibraryViewMsg(lib_msg) -> {
|
LibraryViewMsg(lib_msg) -> {
|
||||||
let #(lib_m, lib_e) = library_view.update(model.library_view, 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))
|
|
||||||
|
// Update artist when library is updated
|
||||||
|
let artist = case lib_msg {
|
||||||
|
library_view.LibraryUpdated(new_lib) ->
|
||||||
|
load_artist(new_lib, model.artist_id)
|
||||||
|
|> option.from_result()
|
||||||
|
_ -> model.artist
|
||||||
|
}
|
||||||
|
|
||||||
|
#(
|
||||||
|
Model(..model, artist: artist, library_view: lib_m),
|
||||||
|
effect.map(lib_e, LibraryViewMsg),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_getter(library: Library, artist: LibraryItem(Artist)) {
|
fn data_getter(library: Library, artist_id: Int) {
|
||||||
library.albums
|
library.albums
|
||||||
|> map.fold(
|
|> dict.fold([], fn(acc, key, val) {
|
||||||
[],
|
case val.artist_id == artist_id {
|
||||||
fn(acc, key, val) {
|
|
||||||
case val.artist_id == artist.0 {
|
|
||||||
True -> [#(key, val), ..acc]
|
True -> [#(key, val), ..acc]
|
||||||
False -> acc
|
False -> acc
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shuffler(library, items: List(LibraryItem(Album))) {
|
fn shuffler(library, items: List(LibraryItem(Album))) {
|
||||||
|
@ -157,13 +157,17 @@ fn search_filter(item: Album, search_text: String) {
|
||||||
|
|
||||||
fn generate_view(search_filter: library_view.SearchFilter(Album)) {
|
fn generate_view(search_filter: library_view.SearchFilter(Album)) {
|
||||||
fn(model: Model) {
|
fn(model: Model) {
|
||||||
case model.artist {
|
case model.library_view.library_loading, model.artist {
|
||||||
option.None ->
|
True, _ ->
|
||||||
div(
|
div([attribute.class("library-view-loading")], [
|
||||||
[attribute.class("library-view-not-specified")],
|
text("Loading library…"),
|
||||||
[text("Artist not specified.")],
|
])
|
||||||
)
|
False, option.None ->
|
||||||
option.Some(artist) -> view(model, artist, search_filter)
|
div([attribute.class("library-view-not-specified")], [
|
||||||
|
text("Artist not found with ID: " <> int.to_string(model.artist_id)),
|
||||||
|
])
|
||||||
|
False, option.Some(artist) ->
|
||||||
|
view(model, #(model.artist_id, artist), search_filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,9 +177,7 @@ fn view(
|
||||||
artist: LibraryItem(Artist),
|
artist: LibraryItem(Artist),
|
||||||
search_filter: library_view.SearchFilter(Album),
|
search_filter: library_view.SearchFilter(Album),
|
||||||
) {
|
) {
|
||||||
div(
|
div([], [
|
||||||
[],
|
|
||||||
[
|
|
||||||
header([attribute.class("library-header")], [text({ artist.1 }.name)]),
|
header([attribute.class("library-header")], [text({ artist.1 }.name)]),
|
||||||
library_view.library_view(
|
library_view.library_view(
|
||||||
model.library_view,
|
model.library_view,
|
||||||
|
@ -183,8 +185,7 @@ fn view(
|
||||||
search_filter,
|
search_filter,
|
||||||
)
|
)
|
||||||
|> element.map(LibraryViewMsg),
|
|> element.map(LibraryViewMsg),
|
||||||
],
|
])
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn album_view(model: Model, item: LibraryItem(Album)) {
|
fn album_view(model: Model, item: LibraryItem(Album)) {
|
||||||
|
@ -192,11 +193,15 @@ fn album_view(model: Model, item: LibraryItem(Album)) {
|
||||||
model.library_view.library,
|
model.library_view.library,
|
||||||
model.library_view.settings,
|
model.library_view.settings,
|
||||||
item,
|
item,
|
||||||
item.0 == model.expanded_album,
|
False,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn artist_decode(data: dynamic.Dynamic) {
|
fn id_decode(data: dynamic.Dynamic) {
|
||||||
let artist: option.Option(LibraryItem(Artist)) = dynamic.unsafe_coerce(data)
|
let artist_id: Int = dynamic.unsafe_coerce(data)
|
||||||
Ok(ArtistUpdated(artist))
|
Ok(ArtistUpdated(artist_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_artist(library: Library, id: Int) {
|
||||||
|
library.get_artist(library, id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ import elekf/web/components/library_view.{StartPlay}
|
||||||
pub fn view(
|
pub fn view(
|
||||||
library: Library,
|
library: Library,
|
||||||
items: List(LibraryItem(Track)),
|
items: List(LibraryItem(Track)),
|
||||||
item: LibraryItem(Track),
|
|
||||||
index: Int,
|
index: Int,
|
||||||
|
item: LibraryItem(Track),
|
||||||
id_prefix: String,
|
id_prefix: String,
|
||||||
) {
|
) {
|
||||||
let #(track_id, track) = item
|
let #(track_id, track) = item
|
||||||
|
|
|
@ -54,5 +54,5 @@ fn item_view(
|
||||||
index: Int,
|
index: Int,
|
||||||
item: LibraryItem(Track),
|
item: LibraryItem(Track),
|
||||||
) {
|
) {
|
||||||
track_item.view(model.library, items, item, index, "track-list")
|
track_item.view(model.library, items, index, item, "track-list")
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import lustre/attribute
|
||||||
import lustre/element
|
import lustre/element
|
||||||
import lustre/element/html.{a}
|
import lustre/element/html.{a}
|
||||||
|
|
||||||
pub fn view(
|
pub fn button(
|
||||||
href: String,
|
href: String,
|
||||||
class: String,
|
class: String,
|
||||||
extra_attributes: List(attribute.Attribute(a)),
|
extra_attributes: List(attribute.Attribute(a)),
|
||||||
|
@ -17,3 +17,12 @@ pub fn view(
|
||||||
content,
|
content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn link(
|
||||||
|
href: String,
|
||||||
|
class: String,
|
||||||
|
extra_attributes: List(attribute.Attribute(a)),
|
||||||
|
content: List(element.Element(a)),
|
||||||
|
) {
|
||||||
|
a([attribute.href(href), attribute.class(class), ..extra_attributes], content)
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import elekf/web/components/library_views/tracks_view
|
||||||
import elekf/web/components/library_views/artists_view
|
import elekf/web/components/library_views/artists_view
|
||||||
import elekf/web/components/library_views/albums_view
|
import elekf/web/components/library_views/albums_view
|
||||||
import elekf/web/components/library_views/single_artist_view
|
import elekf/web/components/library_views/single_artist_view
|
||||||
|
import elekf/web/components/library_views/single_album_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
|
||||||
|
@ -41,6 +42,7 @@ pub fn main() {
|
||||||
let assert Ok(_) = artists_view.register()
|
let assert Ok(_) = artists_view.register()
|
||||||
let assert Ok(_) = albums_view.register()
|
let assert Ok(_) = albums_view.register()
|
||||||
let assert Ok(_) = single_artist_view.register()
|
let assert Ok(_) = single_artist_view.register()
|
||||||
|
let assert Ok(_) = single_album_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)
|
||||||
|
@ -128,29 +130,20 @@ fn update(model: Model, msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(model: Model) {
|
fn view(model: Model) {
|
||||||
html.main(
|
html.main([], [
|
||||||
[],
|
|
||||||
[
|
|
||||||
case model.current_view {
|
case model.current_view {
|
||||||
view.LoginView ->
|
view.LoginView ->
|
||||||
div(
|
div([attribute.id("login-view")], [
|
||||||
[attribute.id("login-view")],
|
|
||||||
[
|
|
||||||
login_view.view(model.login_view)
|
login_view.view(model.login_view)
|
||||||
|> element.map(LoginView),
|
|> element.map(LoginView),
|
||||||
],
|
])
|
||||||
)
|
|
||||||
view.AuthedView ->
|
view.AuthedView ->
|
||||||
div(
|
div([attribute.id("authed-view")], [
|
||||||
[attribute.id("authed-view")],
|
|
||||||
[
|
|
||||||
authed_view.view(option_utils.assert_some(model.authed_view))
|
authed_view.view(option_utils.assert_some(model.authed_view))
|
||||||
|> element.map(AuthedView),
|
|> element.map(AuthedView),
|
||||||
],
|
])
|
||||||
)
|
|
||||||
},
|
},
|
||||||
],
|
])
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn logged_in(model: Model, token: String, session: login_token.Session) {
|
fn logged_in(model: Model, token: String, session: login_token.Session) {
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub fn init(dispatch: fn(Msg) -> Nil) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(dispatch: fn(Msg) -> Nil) {
|
pub fn run(dispatch: fn(Msg) -> Nil) {
|
||||||
use hash <- result.try(window.get_hash())
|
let hash = get_current_path()
|
||||||
use route <- result.try(parse(hash))
|
use route <- result.try(parse(hash))
|
||||||
Ok(dispatch(RouteChanged(route)))
|
Ok(dispatch(RouteChanged(route)))
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ pub fn to_hash(route: Route) {
|
||||||
"#" <> to_string(route)
|
"#" <> to_string(route)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(route: String) {
|
pub fn parse(route: String) {
|
||||||
let parts =
|
let parts =
|
||||||
route
|
route
|
||||||
|> string.drop_left(1)
|
|> string.drop_left(1)
|
||||||
|
@ -62,6 +62,10 @@ fn parse(route: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_current_path() {
|
||||||
|
result.unwrap(window.get_hash(), "/")
|
||||||
|
}
|
||||||
|
|
||||||
fn to_string(route: Route) {
|
fn to_string(route: Route) {
|
||||||
let parts = case route {
|
let parts = case route {
|
||||||
TrackList -> []
|
TrackList -> []
|
||||||
|
|
17
style.css
17
style.css
|
@ -144,7 +144,8 @@ a {
|
||||||
tracks-view,
|
tracks-view,
|
||||||
albums-view,
|
albums-view,
|
||||||
artists-view,
|
artists-view,
|
||||||
single-artist-view {
|
single-artist-view,
|
||||||
|
single-album-view {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
margin-top: calc(var(--library-top-nav-height) + var(--side-margin));
|
margin-top: calc(var(--library-top-nav-height) + var(--side-margin));
|
||||||
|
@ -166,11 +167,6 @@ single-artist-view {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure that the view stack only shows the topmost view and the nav bar. */
|
|
||||||
#authed-view-library > *:not(:first-child, :last-child) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#authed-view-player {
|
#authed-view-player {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
@ -362,6 +358,15 @@ single-artist-view {
|
||||||
font-size: var(--search-size);
|
font-size: var(--search-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.library-header {
|
||||||
|
padding: var(--side-margin);
|
||||||
|
overflow-x: hidden;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.library-list {
|
.library-list {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
Loading…
Reference in a new issue