Skip to content

Commit

Permalink
feat: add modem to handle multipages
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume Hivert <[email protected]>
  • Loading branch information
ghivert committed May 18, 2024
1 parent 715c65b commit d1434cc
Show file tree
Hide file tree
Showing 11 changed files with 133 additions and 38 deletions.
1 change: 1 addition & 0 deletions apps/frontend/public/images/loading.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 11 additions & 4 deletions apps/frontend/src/data/model.gleam
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import data/msg.{type Msg}
import data/search_result.{type SearchResult, type SearchResults}
import frontend/router
import frontend/view/body/cache
import gleam/list
import gleam/pair
Expand All @@ -16,6 +17,7 @@ pub type Model {
index: Index,
loading: Bool,
view_cache: Element(Msg),
route: router.Route,
)
}

Expand All @@ -28,9 +30,14 @@ pub fn init() {
index: index,
loading: False,
view_cache: element.none(),
route: router.Home,
)
}

pub fn update_route(model: Model, route: router.Route) {
Model(..model, route: route)
}

pub fn toggle_loading(model: Model) {
Model(..model, loading: !model.loading)
}
Expand All @@ -42,7 +49,7 @@ pub fn update_input(model: Model, content: String) {
pub fn update_search_results(model: Model, search_results: SearchResults) {
let index = compute_index(search_results)
let view_cache = case search_results {
search_result.Start | search_result.NoSearchResults -> element.none()
search_result.Start | search_result.InternalServerError -> element.none()
search_result.SearchResults(e, m, s) ->
cache.cache_search_results(index, e, m, s)
}
Expand All @@ -56,18 +63,18 @@ pub fn update_search_results(model: Model, search_results: SearchResults) {

pub fn reset(_model: Model) {
Model(
search_results: search_result.Start,
search_results: search_result.SearchResults([], [], []),
input: "",
index: [],
loading: False,
view_cache: element.none(),
route: router.Home,
)
}

fn compute_index(search_results: SearchResults) -> Index {
case search_results {
search_result.Start -> []
search_result.NoSearchResults -> []
search_result.Start | search_result.InternalServerError -> []
search_result.SearchResults(exact, others, searches) -> {
[]
|> insert_module_names(exact)
Expand Down
4 changes: 3 additions & 1 deletion apps/frontend/src/data/msg.gleam
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import data/search_result.{type SearchResults}
import frontend/router
import lustre_http as http

pub type Msg {
None
SubmitSearch
SearchResults(Result(SearchResults, http.HttpError))
SearchResults(input: String, result: Result(SearchResults, http.HttpError))
UpdateInput(String)
Reset
ScrollTo(String)
OnRouteChange(router.Route)
}
4 changes: 2 additions & 2 deletions apps/frontend/src/data/search_result.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ pub type SearchResult {

pub type SearchResults {
Start
InternalServerError
SearchResults(
exact_matches: List(SearchResult),
matches: List(SearchResult),
searches: List(SearchResult),
)
NoSearchResults
}

pub fn decode_search_result(dyn) {
Expand All @@ -43,7 +43,7 @@ pub fn decode_search_result(dyn) {

pub fn decode_search_results(dyn) {
dynamic.any([
dynamic.decode1(fn(_) { NoSearchResults }, {
dynamic.decode1(fn(_) { InternalServerError }, {
dynamic.field("error", dynamic.string)
}),
dynamic.decode3(
Expand Down
56 changes: 45 additions & 11 deletions apps/frontend/src/frontend.gleam
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import data/model.{type Model}
import data/msg.{type Msg}
import data/search_result
import frontend/router
import frontend/view
import gleam/bool
import gleam/option
import gleam/option.{None}
import gleam/pair
import gleam/result
import gleam/uri.{type Uri}
import grille_pain
import grille_pain/lustre/toast
import grille_pain/options
import lustre
import lustre/effect
import lustre/update
import lustre_http as http
import modem
import sketch/lustre as sketch
import sketch/options as sketch_options
import tardis
Expand All @@ -28,8 +31,6 @@ fn scroll_to_element(id: String) -> Nil
fn capture_message(content: String) -> String

pub fn main() {
let init = fn(_) { #(model.init(), effect.none()) }

let debugger_ = case is_dev() {
False -> Error(Nil)
True ->
Expand Down Expand Up @@ -60,15 +61,31 @@ pub fn main() {
|> apply_debugger(tardis.activate)
}

fn init(_) {
let initial =
modem.initial_uri()
|> result.map(router.parse_uri)
|> result.unwrap(router.Home)
|> handle_route_change(model.init(), _)
submit_search(initial.0)
|> update.add_effect(modem.init(on_url_change))
}

fn on_url_change(uri: Uri) -> Msg {
router.parse_uri(uri)
|> msg.OnRouteChange()
}

fn update(model: Model, msg: Msg) {
case msg {
msg.UpdateInput(content) -> update_input(model, content)
msg.SubmitSearch -> submit_search(model)
msg.Reset -> reset(model)
msg.None -> update.none(model)
msg.ScrollTo(id) -> scroll_to(model, id)
msg.SearchResults(search_results) ->
handle_search_results(model, search_results)
msg.OnRouteChange(route) -> handle_route_change(model, route)
msg.SearchResults(input, search_results) ->
handle_search_results(model, input, search_results)
}
}

Expand All @@ -92,7 +109,9 @@ fn submit_search(model: Model) {
use <- bool.guard(when: model.input == "", return: #(model, effect.none()))
use <- bool.guard(when: model.loading, return: #(model, effect.none()))
let new_model = model.toggle_loading(model)
http.expect_json(search_result.decode_search_results, msg.SearchResults)
http.expect_json(search_result.decode_search_results, {
msg.SearchResults(input: model.input, result: _)
})
|> http.get(endpoint <> "/search?q=" <> model.input, _)
|> pair.new(new_model, _)
}
Expand All @@ -104,14 +123,29 @@ fn scroll_to(model: Model, id: String) {

fn handle_search_results(
model: Model,
input: String,
search_results: Result(search_result.SearchResults, http.HttpError),
) {
let toast = display_toast(search_results)
search_results
|> result.map(model.update_search_results(model, _))
|> result.unwrap(model)
|> model.toggle_loading()
|> pair.new(toast)
let up =
search_results
|> result.map(model.update_search_results(model, _))
|> result.map(model.update_route(_, router.Search(input)))
|> result.unwrap(model)
|> model.toggle_loading()
|> update.effect(toast)
uri.parse("/search?q=" <> input)
|> result.map(modem.push(_))
|> result.map(update.add_effect(up, _))
|> result.unwrap(up)
}

fn handle_route_change(model: Model, route: router.Route) {
let model = model.update_route(model, route)
update.none(case route {
router.Home -> model.update_input(model, "")
router.Search(q) -> model.update_input(model, q)
})
}

fn display_toast(
Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/src/frontend/images.gleam
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub const internal_error = "/images/internal_error.png"

pub const shadow_lucy = "/images/shadow_lucy.png"

pub const loading = "/images/loading.svg"
29 changes: 29 additions & 0 deletions apps/frontend/src/frontend/router.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import gleam/list
import gleam/option
import gleam/result
import gleam/uri.{type Uri}

pub type Route {
Home
Search(query: String)
}

pub fn parse_uri(uri: Uri) -> Route {
case uri.path_segments(uri.path) {
["search"] -> handle_search_path(uri)
_ -> Home
}
}

fn handle_search_path(uri: Uri) {
uri.query
|> option.map(uri.parse_query)
|> option.then(fn(query_params) {
query_params
|> result.unwrap([])
|> list.key_find("q")
|> option.from_result()
})
|> option.map(Search)
|> option.unwrap(Home)
}
2 changes: 2 additions & 0 deletions apps/frontend/src/frontend/strings.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ pub const searches_match = "Matched from a document search in functions, constan
pub const retry_query = "Retry with a different query. You can match functions, types or constants names, as well as functions types directly!"

pub const internal_server_error = "Internal server error. The error should be fixed soon. Please, retry later."

pub const loading = "Your content is loading. Please wait…"
40 changes: 25 additions & 15 deletions apps/frontend/src/frontend/view/body/body.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import data/model.{type Model}
import data/msg
import data/search_result
import frontend/images
import frontend/router
import frontend/strings as frontend_strings
import frontend/view/body/styles as s
import lustre/attribute as a
Expand Down Expand Up @@ -49,21 +50,30 @@ fn empty_state(

pub fn body(model: Model) {
s.main([], [
case model.search_results {
search_result.Start -> view_search_input(model)
search_result.NoSearchResults ->
empty_state(
image: images.internal_error,
title: "Internal server error",
content: frontend_strings.internal_server_error,
)
search_result.SearchResults([], [], []) ->
empty_state(
image: images.shadow_lucy,
title: "No match found!",
content: frontend_strings.retry_query,
)
search_result.SearchResults(_, _, _) -> model.view_cache
case model.route {
router.Home -> view_search_input(model)
router.Search(_) ->
case model.search_results {
search_result.Start ->
empty_state(
image: images.loading,
title: "Loading…",
content: frontend_strings.loading,
)
search_result.InternalServerError ->
empty_state(
image: images.internal_error,
title: "Internal server error",
content: frontend_strings.internal_server_error,
)
search_result.SearchResults([], [], []) ->
empty_state(
image: images.shadow_lucy,
title: "No match found!",
content: frontend_strings.retry_query,
)
search_result.SearchResults(_, _, _) -> model.view_cache
}
},
])
}
10 changes: 5 additions & 5 deletions apps/frontend/src/frontend/view/navbar/navbar.gleam
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import data/model.{type Model}
import data/msg
import data/search_result
import frontend/router
import frontend/view/navbar/styles as s
import lustre/attribute as a
import lustre/element/html as h
Expand All @@ -21,11 +21,11 @@ fn navbar_links() {

pub fn navbar(model: Model) {
s.navbar([a.class("navbar")], [
case model.search_results {
search_result.Start -> h.div([], [])
search_result.NoSearchResults | search_result.SearchResults(_, _, _) ->
case model.route {
router.Home -> h.div([], [])
router.Search(_) ->
s.navbar_search([], [
s.navbar_search_title([e.on_click(msg.Reset)], [
s.navbar_search_title([a.href("/")], [
s.search_lucy([a.src("/images/lucy.svg")]),
h.text("Gloogle"),
]),
Expand Down
8 changes: 8 additions & 0 deletions apps/frontend/src/lustre/update.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ pub fn none(model: model) {
#(model, effect.none())
}

pub fn effect(model: model, effect: Effect(msg)) {
#(model, effect)
}

pub fn effects(model: model, effects: List(Effect(msg))) {
#(model, effect.batch(effects))
}

pub fn add_effect(tuple: #(model, Effect(msg)), effect: Effect(msg)) {
let #(model, fst_effect) = tuple
#(model, effect.batch([fst_effect, effect]))
Expand Down

0 comments on commit d1434cc

Please sign in to comment.