Search works

This commit is contained in:
Mikko Ahlroth 2023-10-15 20:23:50 +03:00
parent bf31c2fd87
commit a8ed5aa8ff
4 changed files with 48 additions and 41 deletions

View file

@ -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(

View file

@ -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) },
), ),
), ),

View file

@ -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)),

View file

@ -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;
} }