Skip to content

Commit

Permalink
feat: add pages
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume Hivert <[email protected]>
  • Loading branch information
ghivert committed May 12, 2024
1 parent 6ce4c5f commit 6e6490d
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 27 deletions.
4 changes: 2 additions & 2 deletions apps/backend/src/backend/postgres/queries.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ pub fn content_search(db: pgo.Connection, query: String) {
OR replace(s.signature_, ' ', '') ILIKE '%' || replace($1, ' ', '') || '%'
)
ORDER BY s.name, s.kind, m.name, ordering DESC
LIMIT 100"
LIMIT 20"
|> pgo.execute(db, [query], decode_type_search)
|> result.map_error(error.DatabaseError)
|> result.map(fn(r) { r.rows })
Expand Down Expand Up @@ -523,7 +523,7 @@ pub fn search(db: pgo.Connection, q: String) {
JOIN package p
ON p.id = r.package_id
WHERE to_tsvector(s.signature_) @@ to_tsquery($1)
LIMIT 100"
LIMIT 20"
|> pgo.execute(db, [query], decode_type_search)
|> result.map_error(error.DatabaseError)
|> result.map(fn(r) { r.rows })
Expand Down
Binary file added apps/frontend/public/images/internal_error.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/frontend/public/images/shadow_lucy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 16 additions & 1 deletion apps/frontend/src/data/decoders/search_result.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ pub type SearchResult {
)
}

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

pub fn decode_search_result(dyn) {
dynamic.decode8(
SearchResult,
Expand All @@ -61,5 +67,14 @@ pub fn decode_search_result(dyn) {
}

pub fn decode_search_results(dyn) {
dynamic.list(decode_search_result)(dyn)
dynamic.any([
dynamic.decode1(fn(_) { NoSearchResults }, {
dynamic.field("error", dynamic.string)
}),
dynamic.decode2(
SearchResults,
dynamic.field("exact-matches", dynamic.list(decode_search_result)),
dynamic.field("matches", dynamic.list(decode_search_result)),
),
])(dyn)
}
15 changes: 5 additions & 10 deletions apps/frontend/src/data/model.gleam
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
import data/decoders/search_result.{type SearchResult}
import data/model/mock
import gleam/result
import data/decoders/search_result.{type SearchResults}

pub type Model {
Model(input: String, search_results: List(SearchResult))
Model(input: String, search_results: SearchResults)
}

pub fn init() {
let search_results =
mock.mock()
|> result.unwrap([])
Model(input: "", search_results: search_results)
Model(input: "", search_results: search_result.Start)
}

pub fn update_input(model: Model, content: String) {
Model(..model, input: content)
}

pub fn update_search_results(model: Model, search_results: List(SearchResult)) {
pub fn update_search_results(model: Model, search_results: SearchResults) {
Model(..model, search_results: search_results)
}

pub fn reset(_model: Model) {
Model(search_results: [], input: "")
Model(search_results: search_result.Start, input: "")
}
4 changes: 2 additions & 2 deletions apps/frontend/src/data/msg.gleam
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import data/decoders/search_result.{type SearchResult}
import data/decoders/search_result.{type SearchResults}
import lustre_http as http

pub type Msg {
None
SubmitSearch
SearchResults(Result(List(SearchResult), http.HttpError))
SearchResults(Result(SearchResults, http.HttpError))
UpdateInput(String)
Reset
}
9 changes: 9 additions & 0 deletions apps/frontend/src/frontend/strings.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub const gloogle_description = "Gloogle can search through all public gleam packages, to help you find the function you're looking for! Enter a type or a function name to get some results."

pub const exact_match = "Matched directly from the signature of the functions, constants or types."

pub const partial_match = "Partly matched from the signature of the functions, constants or types."

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."
55 changes: 55 additions & 0 deletions apps/frontend/src/frontend/styles.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,58 @@ pub fn documentation_title() {
|> s.memo()
|> s.to_lustre()
}

pub fn matches_titles() {
s.class([
s.line_height("1.3"),
s.color(palette.dark.dark_white),
s.display("flex"),
s.align_items("baseline"),
s.gap(px(6)),
s.font_size(px(12)),
])
|> s.memo()
|> s.to_lustre()
}

pub fn matches_title() {
s.class([s.color(palette.dark.white), s.font_size(px(18))])
|> s.memo()
|> s.to_lustre()
}

pub fn empty_state() {
s.class([
s.display("flex"),
s.align_items("center"),
s.gap(px(24)),
s.justify_content("center"),
])
|> s.memo()
|> s.to_lustre()
}

pub fn empty_state_lucy() {
s.class([s.width(px(100))])
|> s.memo()
|> s.to_lustre()
}

pub fn empty_state_titles() {
s.class([
s.font_size(px(20)),
s.display("flex"),
s.flex_direction("column"),
s.gap(px(9)),
s.line_height("1.3"),
s.max_width(px(400)),
])
|> s.memo()
|> s.to_lustre()
}

pub fn empty_state_subtitle() {
s.class([s.font_size(px(16)), s.color(palette.dark.dark_white)])
|> s.memo()
|> s.to_lustre()
}
70 changes: 58 additions & 12 deletions apps/frontend/src/frontend/view.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import data/model.{type Model}
import data/msg
import frontend/documentation
import frontend/footer/view as footer
import frontend/strings as frontend_strings
import frontend/styles as s
import frontend/types as t
import gleam/bool
import gleam/int
import gleam/io
import gleam/list
import gleam/option.{None, Some}
import gleam/string
Expand All @@ -25,17 +27,15 @@ pub fn idt(indent: Int) {
h.text(string.repeat(" ", indent))
}

pub const gloogle_description = "Gloogle can search through all public gleam packages, to help you find the function you're looking for! Enter a type or a function name to get some results."

pub fn view(model: Model) {
h.div([s.layout()], [navbar(model), body(model), footer.view()])
}

fn navbar(model: Model) {
h.div([s.navbar()], [
case model.search_results {
[] -> h.div([], [])
_ ->
search_result.Start -> h.div([], [])
search_result.NoSearchResults | search_result.SearchResults(_, _) ->
h.div([s.navbar_search()], [
h.a([s.navbar_search_title(), e.on_click(msg.Reset)], [
h.img([a.src("/images/lucy.svg"), s.search_lucy()]),
Expand Down Expand Up @@ -208,8 +208,16 @@ fn render_parameters(count: Int) {

fn view_type_constructor(constructor: signature.TypeConstructor, indent: Int) {
let inline = constructor.params_width <= 70
let has_params = !list.is_empty(constructor.parameters)
list.concat([
[idt(indent), t.type_(constructor.name), h.text("(")],
[
idt(indent),
t.type_(constructor.name),
case has_params {
True -> h.text("(")
False -> element.none()
},
],
case inline {
False ->
list.concat([
Expand All @@ -224,9 +232,10 @@ fn view_type_constructor(constructor: signature.TypeConstructor, indent: Int) {
|> list.intersperse([h.text(", ")])
|> list.concat()
},
case inline {
True -> [h.text(")")]
False -> [idt(indent), h.text(")")]
case has_params, inline {
False, _ -> []
True, True -> [h.text(")")]
True, False -> [idt(indent), h.text(")")]
},
])
}
Expand Down Expand Up @@ -342,7 +351,7 @@ fn view_search_input(model: Model) {
h.img([a.src("/images/lucy.svg"), s.search_lucy()]),
h.text("Gloogle"),
]),
h.text(gloogle_description),
h.text(frontend_strings.gloogle_description),
]),
h.input([
s.search_input(),
Expand All @@ -354,9 +363,46 @@ fn view_search_input(model: Model) {
])
}

fn match_title(results: List(a), title: String) {
use <- bool.guard(when: list.is_empty(results), return: element.none())
h.div([s.matches_titles()], [
h.div([s.matches_title()], [h.text("Exact matches")]),
h.div([], [h.text(title)]),
])
}

fn empty_state(image: String, title: String, content: String) {
h.div([s.empty_state()], [
h.img([a.src(image), s.empty_state_lucy()]),
h.div([s.empty_state_titles()], [
h.div([], [h.text(title)]),
h.div([s.empty_state_subtitle()], [h.text(content)]),
]),
])
}

fn body(model: Model) {
h.main([s.main_wrapper()], case model.search_results {
[] -> [view_search_input(model)]
_ -> [view_search_results(model.search_results)]
h.main([s.main_wrapper()], case io.debug(model.search_results) {
search_result.Start -> [view_search_input(model)]
search_result.NoSearchResults -> [
empty_state(
"/images/internal_error.png",
"Internal server error",
frontend_strings.internal_server_error,
),
]
search_result.SearchResults([], []) -> [
empty_state(
"/images/shadow_lucy.png",
"No match found!",
frontend_strings.retry_query,
),
]
search_result.SearchResults(exact, others) -> [
match_title(exact, frontend_strings.exact_match),
view_search_results(exact),
match_title(others, frontend_strings.partial_match),
view_search_results(others),
]
})
}

0 comments on commit 6e6490d

Please sign in to comment.