Search works
This commit is contained in:
parent
bf31c2fd87
commit
a8ed5aa8ff
4 changed files with 48 additions and 41 deletions
|
@ -2,16 +2,12 @@
|
||||||
//// view for the app.
|
//// view for the app.
|
||||||
|
|
||||||
import gleam/io
|
import gleam/io
|
||||||
import gleam/int
|
|
||||||
import gleam/list
|
import gleam/list
|
||||||
import gleam/option
|
import gleam/option
|
||||||
import gleam/map
|
|
||||||
import gleam/string
|
|
||||||
import gleam/javascript/promise
|
import gleam/javascript/promise
|
||||||
import lustre/element.{text}
|
import lustre/element.{text}
|
||||||
import lustre/element/html.{div, h3, p}
|
import lustre/element/html.{div, p}
|
||||||
import lustre/attribute
|
import lustre/attribute
|
||||||
import lustre/event
|
|
||||||
import lustre/effect
|
import lustre/effect
|
||||||
import elekf/web/common.{PlayQueue}
|
import elekf/web/common.{PlayQueue}
|
||||||
import ibroadcast/library/library as library_api
|
import ibroadcast/library/library as library_api
|
||||||
|
@ -22,7 +18,6 @@ import elekf/library.{Library}
|
||||||
import elekf/library/track.{Track}
|
import elekf/library/track.{Track}
|
||||||
import elekf/transfer/library as library_transfer
|
import elekf/transfer/library as library_transfer
|
||||||
import elekf/web/components/player
|
import elekf/web/components/player
|
||||||
import elekf/web/components/search
|
|
||||||
import elekf/web/components/library_views/tracks_view
|
import elekf/web/components/library_views/tracks_view
|
||||||
import elekf/web/events/start_play
|
import elekf/web/events/start_play
|
||||||
import elekf/web/utils
|
import elekf/web/utils
|
||||||
|
@ -48,7 +43,6 @@ pub type Model {
|
||||||
library: Library,
|
library: Library,
|
||||||
settings: option.Option(common.Settings),
|
settings: option.Option(common.Settings),
|
||||||
request_config: RequestConfig,
|
request_config: RequestConfig,
|
||||||
search: search.Model,
|
|
||||||
play_status: PlayStatus,
|
play_status: PlayStatus,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -57,7 +51,6 @@ pub type Msg {
|
||||||
UpdateAuthData(common.AuthData)
|
UpdateAuthData(common.AuthData)
|
||||||
LibraryResult(Result(library_api.ResponseData, http.ResponseError))
|
LibraryResult(Result(library_api.ResponseData, http.ResponseError))
|
||||||
PlayerMsg(player.Msg)
|
PlayerMsg(player.Msg)
|
||||||
Search(search.Msg)
|
|
||||||
StartPlay(PlayQueue, Int)
|
StartPlay(PlayQueue, Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +61,6 @@ pub fn init(auth_data: common.AuthData) {
|
||||||
library: library.empty(),
|
library: library.empty(),
|
||||||
settings: option.None,
|
settings: option.None,
|
||||||
request_config: form_request_config(auth_data),
|
request_config: form_request_config(auth_data),
|
||||||
search: search.init(),
|
|
||||||
play_status: NoTracks,
|
play_status: NoTracks,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -165,17 +157,10 @@ pub fn update(model: Model, msg) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Search(msg) -> {
|
|
||||||
let search_model = search.update(model.search, msg)
|
|
||||||
|
|
||||||
#(Model(..model, search: search_model), effect.none())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(model: Model) {
|
pub fn view(model: Model) {
|
||||||
let search_text = string.lowercase(model.search.search_text)
|
|
||||||
|
|
||||||
div(
|
div(
|
||||||
[attribute.id("authed-view-wrapper")],
|
[attribute.id("authed-view-wrapper")],
|
||||||
[
|
[
|
||||||
|
@ -200,13 +185,6 @@ pub fn view(model: Model) {
|
||||||
div(
|
div(
|
||||||
[attribute.id("authed-view-player")],
|
[attribute.id("authed-view-player")],
|
||||||
[
|
[
|
||||||
div(
|
|
||||||
[attribute.id("search-positioner")],
|
|
||||||
[
|
|
||||||
search.view(model.search)
|
|
||||||
|> element.map(Search),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
case model.play_status {
|
case model.play_status {
|
||||||
HasTracks(PlayInfo(player: player, ..)) ->
|
HasTracks(PlayInfo(player: player, ..)) ->
|
||||||
div(
|
div(
|
||||||
|
|
|
@ -16,6 +16,7 @@ import elekf/library/album.{Album}
|
||||||
import elekf/library/artist.{Artist}
|
import elekf/library/artist.{Artist}
|
||||||
import elekf/library/track.{Track}
|
import elekf/library/track.{Track}
|
||||||
import elekf/web/events/start_play
|
import elekf/web/events/start_play
|
||||||
|
import elekf/web/components/search
|
||||||
|
|
||||||
/// An item in the library with its ID.
|
/// An item in the library with its ID.
|
||||||
pub type LibraryItem(a) =
|
pub type LibraryItem(a) =
|
||||||
|
@ -29,6 +30,13 @@ pub type DataGetter(a) =
|
||||||
pub type ItemView(a) =
|
pub type ItemView(a) =
|
||||||
fn(Library, List(LibraryItem(a)), Int, LibraryItem(a)) -> element.Element(Msg)
|
fn(Library, List(LibraryItem(a)), Int, LibraryItem(a)) -> element.Element(Msg)
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// The search string is lowercased.
|
||||||
|
pub type SearchFilter(a) =
|
||||||
|
fn(a, String) -> Bool
|
||||||
|
|
||||||
/// Shuffler takes all of the items, finds all of the tracks in them, and
|
/// Shuffler takes all of the items, finds all of the tracks in them, and
|
||||||
/// shuffles them for playback.
|
/// shuffles them for playback.
|
||||||
pub type Shuffler(a) =
|
pub type Shuffler(a) =
|
||||||
|
@ -53,6 +61,7 @@ pub type Msg {
|
||||||
LibraryUpdated(Library)
|
LibraryUpdated(Library)
|
||||||
ShuffleAll
|
ShuffleAll
|
||||||
StartPlay(List(LibraryItem(Track)), Int)
|
StartPlay(List(LibraryItem(Track)), Int)
|
||||||
|
Search(search.Msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Model(a) {
|
pub type Model(a) {
|
||||||
|
@ -61,6 +70,7 @@ pub type Model(a) {
|
||||||
data: List(LibraryItem(a)),
|
data: List(LibraryItem(a)),
|
||||||
data_getter: DataGetter(a),
|
data_getter: DataGetter(a),
|
||||||
shuffler: Shuffler(a),
|
shuffler: Shuffler(a),
|
||||||
|
search: search.Model,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,12 +88,13 @@ pub fn register(
|
||||||
data_getter: DataGetter(a),
|
data_getter: DataGetter(a),
|
||||||
item_view: ItemView(a),
|
item_view: ItemView(a),
|
||||||
shuffler: Shuffler(a),
|
shuffler: Shuffler(a),
|
||||||
|
search_filter: SearchFilter(a),
|
||||||
) {
|
) {
|
||||||
lustre.component(
|
lustre.component(
|
||||||
name,
|
name,
|
||||||
fn() { init(library.empty(), data_getter, shuffler) },
|
fn() { init(library.empty(), data_getter, shuffler) },
|
||||||
update,
|
update,
|
||||||
generate_view(item_view),
|
generate_view(item_view, search_filter),
|
||||||
map.from_list([#("library", library_decode)]),
|
map.from_list([#("library", library_decode)]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -102,7 +113,10 @@ pub fn render(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(library, data_getter, shuffler) {
|
fn init(library, data_getter, shuffler) {
|
||||||
#(Model(library, data_getter(library), data_getter, shuffler), effect.none())
|
#(
|
||||||
|
Model(library, data_getter(library), data_getter, shuffler, search.init()),
|
||||||
|
effect.none(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(model, msg) {
|
fn update(model, msg) {
|
||||||
|
@ -113,15 +127,28 @@ fn update(model, msg) {
|
||||||
)
|
)
|
||||||
ShuffleAll -> #(model, shuffle_all(model))
|
ShuffleAll -> #(model, shuffle_all(model))
|
||||||
StartPlay(tracks, position) -> #(model, start_play.emit(tracks, position))
|
StartPlay(tracks, position) -> #(model, start_play.emit(tracks, position))
|
||||||
|
Search(search_msg) -> {
|
||||||
|
let search_model = search.update(model.search, search_msg)
|
||||||
|
#(Model(..model, search: search_model), effect.none())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_view(item_view: ItemView(a)) {
|
fn generate_view(item_view: ItemView(a), search_filter: SearchFilter(a)) {
|
||||||
fn(model: Model(a)) {
|
fn(model: Model(a)) {
|
||||||
|
let items = case model.search.search_text {
|
||||||
|
"" -> model.data
|
||||||
|
txt ->
|
||||||
|
model.data
|
||||||
|
|> list.filter(fn(item) { search_filter(item.1, txt) })
|
||||||
|
}
|
||||||
|
|
||||||
div(
|
div(
|
||||||
[attribute.class("library-list")],
|
[attribute.class("library-list")],
|
||||||
list.append(
|
list.append(
|
||||||
[
|
[
|
||||||
|
search.view(model.search)
|
||||||
|
|> element.map(Search),
|
||||||
div(
|
div(
|
||||||
[
|
[
|
||||||
attribute.class("library-item library-list-shuffle-all"),
|
attribute.class("library-item library-list-shuffle-all"),
|
||||||
|
@ -131,7 +158,7 @@ fn generate_view(item_view: ItemView(a)) {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
list.index_map(
|
list.index_map(
|
||||||
model.data,
|
items,
|
||||||
fn(i, item) { item_view(model.library, model.data, i, item) },
|
fn(i, item) { item_view(model.library, model.data, i, item) },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//// A library view to all of the tracks in the library.
|
//// A library view to all of the tracks in the library.
|
||||||
|
|
||||||
|
import gleam/string
|
||||||
import gleam/int
|
import gleam/int
|
||||||
import gleam/list
|
import gleam/list
|
||||||
import gleam/map
|
import gleam/map
|
||||||
|
@ -15,7 +16,13 @@ const component_name = "tracks-view"
|
||||||
|
|
||||||
/// Register the tracks view as a custom element.
|
/// Register the tracks view as a custom element.
|
||||||
pub fn register() {
|
pub fn register() {
|
||||||
library_view.register(component_name, data_getter, item_view, shuffler)
|
library_view.register(
|
||||||
|
component_name,
|
||||||
|
data_getter,
|
||||||
|
item_view,
|
||||||
|
shuffler,
|
||||||
|
search_filter,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render the tracks view.
|
/// Render the tracks view.
|
||||||
|
@ -31,6 +38,10 @@ fn shuffler(items) {
|
||||||
list.shuffle(items)
|
list.shuffle(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_filter(item: Track, search_text: String) {
|
||||||
|
string.contains(item.title_lower, search_text)
|
||||||
|
}
|
||||||
|
|
||||||
fn item_view(
|
fn item_view(
|
||||||
library: Library,
|
library: Library,
|
||||||
items: List(LibraryItem(Track)),
|
items: List(LibraryItem(Track)),
|
||||||
|
|
17
style.css
17
style.css
|
@ -30,17 +30,12 @@ main {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#player {
|
|
||||||
}
|
|
||||||
|
|
||||||
#search-positioner {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#search-bar {
|
#search-bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translateY(-200%);
|
bottom: 100px;
|
||||||
width: 100%;
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: calc(100vw - 20px);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -49,10 +44,6 @@ main {
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search-bar button {
|
|
||||||
justify-self: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
#search-bar-input-wrapper {
|
#search-bar-input-wrapper {
|
||||||
flex: 1 1;
|
flex: 1 1;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue