Album and artist views added, previous track selection added
This commit is contained in:
parent
89f30e0225
commit
8856392c98
13 changed files with 274 additions and 20 deletions
|
@ -2,16 +2,16 @@
|
|||
# You typically do not need to edit this file
|
||||
|
||||
packages = [
|
||||
{ name = "gleam_fetch", version = "0.2.1", build_tools = ["gleam"], requirements = ["gleam_javascript", "gleam_http", "gleam_stdlib"], otp_app = "gleam_fetch", source = "hex", outer_checksum = "F64E93C754D948B2D37ABC4ADD5482FE0FAED4B99C79E66012DDE96BEDC40544" },
|
||||
{ name = "gleam_fetch", version = "0.2.1", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_javascript", "gleam_stdlib"], otp_app = "gleam_fetch", source = "hex", outer_checksum = "F64E93C754D948B2D37ABC4ADD5482FE0FAED4B99C79E66012DDE96BEDC40544" },
|
||||
{ name = "gleam_http", version = "3.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "FAE9AE3EB1CA90C2194615D20FFFD1E28B630E84DACA670B28D959B37BCBB02C" },
|
||||
{ name = "gleam_javascript", version = "0.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "BFEBB63ABE4A1694E07DEFD19B160C2980304B5D775A89D4B02E7DE7C9D8008B" },
|
||||
{ name = "gleam_json", version = "0.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "C6CC5BEECA525117E97D0905013AB3F8836537455645DDDD10FE31A511B195EF" },
|
||||
{ name = "gleam_stdlib", version = "0.31.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "6D1BC5B4D4179B9FEE866B1E69FE180AC2CE485AD90047C0B32B2CA984052736" },
|
||||
{ name = "gleeunit", version = "0.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "1397E5C4AC4108769EE979939AC39BF7870659C5AFB714630DEEEE16B8272AD5" },
|
||||
{ name = "lustre", version = "3.0.7", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "0C4763BEB8426DEFCCD96015CE2F0EF1446E26A731B614DDF3EA09BEF9BA8DB2" },
|
||||
{ name = "plinth", version = "0.1.3", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_javascript"], otp_app = "plinth", source = "hex", outer_checksum = "E81BA6A6CEAFFADBCB85B04DC817A4CDC43AFA7BB6AE56CE0B7C7E66D1C9ADD1" },
|
||||
{ name = "lustre", version = "3.0.9", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "CBD20163B7CB7FA2AC2BCFBAB5A083B22AED9612B6522BE479E8FEF09C82A9E1" },
|
||||
{ name = "plinth", version = "0.1.4", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_javascript"], otp_app = "plinth", source = "hex", outer_checksum = "43307A0271B8AC9B646B3C39F80D22C41DB4022CDAD67ABFAA555BB3F1321E2E" },
|
||||
{ name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" },
|
||||
{ name = "varasto", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib", "plinth"], otp_app = "varasto", source = "hex", outer_checksum = "0621E5BFD0B9B7F7D19B8FC6369C6E2EAC5C1F3858A1E5E51342F5BCE10C3728" },
|
||||
{ name = "varasto", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "plinth", "gleam_json"], otp_app = "varasto", source = "hex", outer_checksum = "0621E5BFD0B9B7F7D19B8FC6369C6E2EAC5C1F3858A1E5E51342F5BCE10C3728" },
|
||||
]
|
||||
|
||||
[requirements]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
pub type Album {
|
||||
Album(
|
||||
name: String,
|
||||
name_lower: String,
|
||||
tracks: List(Int),
|
||||
artist_id: Int,
|
||||
trashed: Bool,
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
pub type Artist {
|
||||
Artist(name: String, tracks: List(Int), trashed: Bool, rating: Int)
|
||||
Artist(
|
||||
name: String,
|
||||
name_lower: String,
|
||||
tracks: List(Int),
|
||||
trashed: Bool,
|
||||
rating: Int,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import gleam/string
|
||||
import ibroadcast/library/library.{Album as APIAlbum}
|
||||
import elekf/library/album.{Album}
|
||||
|
||||
|
@ -5,6 +6,7 @@ import elekf/library/album.{Album}
|
|||
pub fn from(album: APIAlbum) {
|
||||
Album(
|
||||
name: album.name,
|
||||
name_lower: string.lowercase(album.name),
|
||||
tracks: album.tracks,
|
||||
artist_id: album.artist_id,
|
||||
trashed: album.trashed,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import gleam/string
|
||||
import ibroadcast/library/library.{Artist as APIArtist}
|
||||
import elekf/library/artist.{Artist}
|
||||
|
||||
|
@ -5,6 +6,7 @@ import elekf/library/artist.{Artist}
|
|||
pub fn from(artist: APIArtist) {
|
||||
Artist(
|
||||
name: artist.name,
|
||||
name_lower: string.lowercase(artist.name),
|
||||
tracks: artist.tracks,
|
||||
trashed: artist.trashed,
|
||||
rating: artist.rating,
|
||||
|
|
|
@ -6,9 +6,10 @@ import gleam/list
|
|||
import gleam/option
|
||||
import gleam/javascript/promise
|
||||
import lustre/element.{text}
|
||||
import lustre/element/html.{div, p}
|
||||
import lustre/element/html.{button, div, nav, p}
|
||||
import lustre/attribute
|
||||
import lustre/effect
|
||||
import lustre/event
|
||||
import elekf/web/common.{PlayQueue}
|
||||
import ibroadcast/library/library as library_api
|
||||
import ibroadcast/authed_request.{RequestConfig}
|
||||
|
@ -18,9 +19,13 @@ import elekf/library.{Library}
|
|||
import elekf/library/track.{Track}
|
||||
import elekf/transfer/library as library_transfer
|
||||
import elekf/web/components/player
|
||||
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/events/start_play
|
||||
import elekf/web/utils
|
||||
import elekf/web/components/icon.{Alt, icon}
|
||||
|
||||
pub type PlayInfo {
|
||||
PlayInfo(
|
||||
|
@ -44,6 +49,7 @@ pub type Model {
|
|||
settings: option.Option(common.Settings),
|
||||
request_config: RequestConfig,
|
||||
play_status: PlayStatus,
|
||||
library_view: library_view.View,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -52,6 +58,7 @@ pub type Msg {
|
|||
LibraryResult(Result(library_api.ResponseData, http.ResponseError))
|
||||
PlayerMsg(player.Msg)
|
||||
StartPlay(PlayQueue, Int)
|
||||
ChangeView(library_view.View)
|
||||
}
|
||||
|
||||
pub fn init(auth_data: common.AuthData) {
|
||||
|
@ -62,6 +69,7 @@ pub fn init(auth_data: common.AuthData) {
|
|||
settings: option.None,
|
||||
request_config: form_request_config(auth_data),
|
||||
play_status: NoTracks,
|
||||
library_view: library_view.Tracks,
|
||||
)
|
||||
|
||||
#(model, load_library(model))
|
||||
|
@ -124,11 +132,14 @@ pub fn update(model: Model, msg) {
|
|||
let #(status, effect) = handle_start_play(model, tracks, position)
|
||||
#(Model(..model, play_status: status), effect)
|
||||
}
|
||||
PlayerMsg(player.NextTrack) -> {
|
||||
PlayerMsg(player.NextTrack) | PlayerMsg(player.PrevTrack) -> {
|
||||
if_player(
|
||||
model,
|
||||
fn(info) {
|
||||
let next_index = info.play_index + 1
|
||||
let next_index = case msg {
|
||||
PlayerMsg(player.NextTrack) -> info.play_index + 1
|
||||
_ -> info.play_index - 1
|
||||
}
|
||||
case list.at(info.play_queue, next_index) {
|
||||
Ok(_) -> {
|
||||
let #(status, effect) =
|
||||
|
@ -157,6 +168,9 @@ pub fn update(model: Model, msg) {
|
|||
},
|
||||
)
|
||||
}
|
||||
ChangeView(view) -> {
|
||||
#(Model(..model, library_view: view), effect.none())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,10 +190,49 @@ pub fn view(model: Model) {
|
|||
div(
|
||||
[attribute.id("authed-view-library")],
|
||||
[
|
||||
tracks_view.render(
|
||||
model.library,
|
||||
[attribute.id("tracks-view"), start_play.on(StartPlay)],
|
||||
nav(
|
||||
[attribute.id("library-top-nav")],
|
||||
[
|
||||
button(
|
||||
[
|
||||
attribute.type_("button"),
|
||||
event.on_click(ChangeView(library_view.Tracks)),
|
||||
],
|
||||
[icon("music-note-beamed", Alt("Tracks"))],
|
||||
),
|
||||
button(
|
||||
[
|
||||
attribute.type_("button"),
|
||||
event.on_click(ChangeView(library_view.Artists)),
|
||||
],
|
||||
[icon("file-person", Alt("Artists"))],
|
||||
),
|
||||
button(
|
||||
[
|
||||
attribute.type_("button"),
|
||||
event.on_click(ChangeView(library_view.Albums)),
|
||||
],
|
||||
[icon("disc", Alt("Albums"))],
|
||||
),
|
||||
],
|
||||
),
|
||||
case model.library_view {
|
||||
library_view.Tracks ->
|
||||
tracks_view.render(
|
||||
model.library,
|
||||
[attribute.id("tracks-view"), start_play.on(StartPlay)],
|
||||
)
|
||||
library_view.Artists ->
|
||||
artists_view.render(
|
||||
model.library,
|
||||
[attribute.id("artists-view"), start_play.on(StartPlay)],
|
||||
)
|
||||
library_view.Albums ->
|
||||
albums_view.render(
|
||||
model.library,
|
||||
[attribute.id("albums-view"), start_play.on(StartPlay)],
|
||||
)
|
||||
},
|
||||
],
|
||||
),
|
||||
div(
|
||||
|
|
|
@ -12,8 +12,6 @@ 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
|
||||
import elekf/web/components/search
|
||||
|
@ -40,19 +38,19 @@ pub type SearchFilter(a) =
|
|||
/// Shuffler takes all of the items, finds all of the tracks in them, and
|
||||
/// shuffles them for playback.
|
||||
pub type Shuffler(a) =
|
||||
fn(List(LibraryItem(a))) -> List(LibraryItem(Track))
|
||||
fn(Library, List(LibraryItem(a))) -> List(LibraryItem(Track))
|
||||
|
||||
pub type View {
|
||||
/// All albums.
|
||||
Albums
|
||||
/// Tracks of a single album.
|
||||
SingleAlbum(Int, Album)
|
||||
/// SingleAlbum(Int, Album)
|
||||
/// All artists.
|
||||
Artists
|
||||
/// Albums of a single artist.
|
||||
SingleArtist(Int, Artist)
|
||||
/// SingleArtist(Int, Artist)
|
||||
/// Tracks of a single artist.
|
||||
SingleArtistTracks(Int, Artist)
|
||||
/// SingleArtistTracks(Int, Artist)
|
||||
/// All tracks.
|
||||
Tracks
|
||||
}
|
||||
|
@ -167,7 +165,7 @@ fn generate_view(item_view: ItemView(a), search_filter: SearchFilter(a)) {
|
|||
}
|
||||
|
||||
fn shuffle_all(model: Model(a)) {
|
||||
let tracks = model.shuffler(model.data)
|
||||
let tracks = model.shuffler(model.library, model.data)
|
||||
start_play.emit(tracks, 0)
|
||||
}
|
||||
|
||||
|
|
84
src/elekf/web/components/library_views/albums_view.gleam
Normal file
84
src/elekf/web/components/library_views/albums_view.gleam
Normal file
|
@ -0,0 +1,84 @@
|
|||
//// A library view to all of the albums in the library.
|
||||
|
||||
import gleam/string
|
||||
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/album.{Album}
|
||||
import elekf/web/components/library_view.{LibraryItem, StartPlay}
|
||||
|
||||
const component_name = "albums-view"
|
||||
|
||||
/// Register the albums view as a custom element.
|
||||
pub fn register() {
|
||||
library_view.register(
|
||||
component_name,
|
||||
data_getter,
|
||||
item_view,
|
||||
shuffler,
|
||||
search_filter,
|
||||
)
|
||||
}
|
||||
|
||||
/// Render the albums view.
|
||||
pub fn render(library: Library, extra_attrs: List(attribute.Attribute(msg))) {
|
||||
library_view.render(component_name, library, extra_attrs)
|
||||
}
|
||||
|
||||
fn data_getter(library: Library) {
|
||||
map.to_list(library.albums)
|
||||
}
|
||||
|
||||
fn shuffler(library, items: List(LibraryItem(Album))) {
|
||||
items
|
||||
|> list.shuffle()
|
||||
|> list.map(fn(album) { 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(
|
||||
library: Library,
|
||||
_items: List(LibraryItem(Album)),
|
||||
_index: Int,
|
||||
item: LibraryItem(Album),
|
||||
) {
|
||||
let #(album_id, album) = item
|
||||
let tracks = get_tracks(library, album)
|
||||
let artist_name = case album.artist_id {
|
||||
0 -> "Unknown artist"
|
||||
id -> library.assert_artist(library, id).name
|
||||
}
|
||||
|
||||
div(
|
||||
[
|
||||
attribute.id("album-list-" <> int.to_string(album_id)),
|
||||
attribute.type_("button"),
|
||||
event.on_click(StartPlay(tracks, 0)),
|
||||
attribute.attribute("role", "button"),
|
||||
],
|
||||
[
|
||||
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)) },
|
||||
)
|
||||
}
|
87
src/elekf/web/components/library_views/artists_view.gleam
Normal file
87
src/elekf/web/components/library_views/artists_view.gleam
Normal file
|
@ -0,0 +1,87 @@
|
|||
//// A library view to all of the artists in the library.
|
||||
|
||||
import gleam/string
|
||||
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/artist.{Artist}
|
||||
import elekf/web/components/library_view.{LibraryItem, StartPlay}
|
||||
|
||||
const component_name = "artists-view"
|
||||
|
||||
/// Register the artists view as a custom element.
|
||||
pub fn register() {
|
||||
library_view.register(
|
||||
component_name,
|
||||
data_getter,
|
||||
item_view,
|
||||
shuffler,
|
||||
search_filter,
|
||||
)
|
||||
}
|
||||
|
||||
/// Render the artists view.
|
||||
pub fn render(library: Library, extra_attrs: List(attribute.Attribute(msg))) {
|
||||
library_view.render(component_name, library, extra_attrs)
|
||||
}
|
||||
|
||||
fn data_getter(library: Library) {
|
||||
library.artists
|
||||
|> map.fold(
|
||||
[],
|
||||
fn(acc, key, val) {
|
||||
case val.tracks {
|
||||
[] -> acc
|
||||
_ -> [#(key, val), ..acc]
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn shuffler(library, items: List(LibraryItem(Artist))) {
|
||||
items
|
||||
|> list.shuffle()
|
||||
|> list.map(fn(artist) { get_tracks(library, artist.1) })
|
||||
|> list.flatten()
|
||||
}
|
||||
|
||||
fn search_filter(item: Artist, search_text: String) {
|
||||
string.contains(item.name_lower, search_text)
|
||||
}
|
||||
|
||||
fn item_view(
|
||||
library: Library,
|
||||
_items: List(LibraryItem(Artist)),
|
||||
_index: Int,
|
||||
item: LibraryItem(Artist),
|
||||
) {
|
||||
let #(artist_id, artist) = item
|
||||
let tracks = get_tracks(library, artist)
|
||||
div(
|
||||
[
|
||||
attribute.id("artist-list-" <> int.to_string(artist_id)),
|
||||
attribute.type_("button"),
|
||||
event.on_click(StartPlay(tracks, 0)),
|
||||
attribute.attribute("role", "button"),
|
||||
],
|
||||
[
|
||||
h3([attribute.class("artist-title")], [text(artist.name)]),
|
||||
p(
|
||||
[attribute.class("artist-tracks")],
|
||||
[text(int.to_string(list.length(artist.tracks)) <> " tracks")],
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn get_tracks(library: Library, artist: Artist) {
|
||||
list.map(
|
||||
artist.tracks,
|
||||
fn(track_id) { #(track_id, library.assert_track(library, track_id)) },
|
||||
)
|
||||
}
|
|
@ -34,7 +34,7 @@ fn data_getter(library: Library) {
|
|||
map.to_list(library.tracks)
|
||||
}
|
||||
|
||||
fn shuffler(items) {
|
||||
fn shuffler(_library, items) {
|
||||
list.shuffle(items)
|
||||
}
|
||||
|
||||
|
|
|
@ -151,7 +151,11 @@ pub fn view(model: Model) {
|
|||
[attribute.id("player-controls")],
|
||||
[
|
||||
button(
|
||||
[attribute.id("player-previous"), attribute.type_("button")],
|
||||
[
|
||||
attribute.id("player-previous"),
|
||||
attribute.type_("button"),
|
||||
event.on_click(PrevTrack),
|
||||
],
|
||||
[icon("skip-backward-fill", Alt("Previous"))],
|
||||
),
|
||||
case is_playing {
|
||||
|
|
|
@ -13,6 +13,8 @@ import elekf/web/authed_view
|
|||
import elekf/web/view
|
||||
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/api/auth/storage as auth_storage
|
||||
import elekf/api/auth/models as auth_models
|
||||
import elekf/utils/option as option_utils
|
||||
|
@ -35,6 +37,8 @@ type Msg {
|
|||
|
||||
pub fn main() {
|
||||
let assert Ok(_) = tracks_view.register()
|
||||
let assert Ok(_) = artists_view.register()
|
||||
let assert Ok(_) = albums_view.register()
|
||||
|
||||
let app = lustre.application(init, update, view)
|
||||
let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
|
||||
|
|
13
style.css
13
style.css
|
@ -30,6 +30,19 @@ main {
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#library-top-nav {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
#library-top-nav button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
#search-bar {
|
||||
position: absolute;
|
||||
bottom: 100px;
|
||||
|
|
Loading…
Reference in a new issue