Skip to content

Commit

Permalink
ui: start search implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume Hivert <hivert.is.coming@gmail.com>
  • Loading branch information
ghivert committed May 6, 2024
1 parent 6183869 commit 989a183
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 28 deletions.
4 changes: 3 additions & 1 deletion apps/backend/src/backend.gleam
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import backend/config.{type Config}
import backend/postgres/postgres
import backend/router
import dot_env
import gleam/erlang/process
Expand All @@ -18,9 +19,10 @@ pub fn main() {

let secret_key_base = config.get_secret_key_base()
let cnf = config.read_config()
let ctx = postgres.connect(cnf)

let assert Ok(_) =
router.handle_request(_, cnf)
router.handle_request(_, ctx)
|> wisp.mist_handler(secret_key_base)
|> mist.new()
|> mist.port(3000)
Expand Down
6 changes: 0 additions & 6 deletions apps/backend/src/backend/postgres/postgres.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import gleam/option.{Some}
import gleam/pgo.{Config}
import gleam/regex
import gleam/result
import wisp.{type Response}

pub fn connect(cnf: Config) {
let assert Ok(config) = parse_database_url(cnf.database_url)
Expand All @@ -14,11 +13,6 @@ pub fn connect(cnf: Config) {
|> fn(db) { Context(db: db, hex_api_key: cnf.hex_api_key) }
}

pub fn middleware(cnf: Config, handler: fn(Context) -> Response) {
connect(cnf)
|> handler()
}

fn database_url_matcher() {
"postgres://(.*):(.*)@(.*):(.*)/(.*)\\?sslmode=(.*)"
|> regex.from_string()
Expand Down
6 changes: 2 additions & 4 deletions apps/backend/src/backend/router.gleam
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import api/hex
import backend/config.{type Config, type Context}
import backend/config.{type Context}
import backend/error
import backend/postgres/postgres
import backend/postgres/queries
import backend/web
import cors_builder as cors
Expand Down Expand Up @@ -50,10 +49,9 @@ pub fn handle_post(req: Request, ctx: Context) {
}
}

pub fn handle_request(req: Request, cnf: Config) -> Response {
pub fn handle_request(req: Request, ctx: Context) -> Response {
use req <- cors.wisp_handle(req, web.cors())
use req <- web.foundations(req)
use ctx <- postgres.middleware(cnf)
case req.method {
http.Get -> handle_get(req, ctx)
http.Post -> handle_post(req, ctx)
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/frontend/footer/styles.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub fn footer() {
s.padding(px(24)),
s.align_items("center"),
s.gap(px(48)),
s.margin_top(px(48)),
])
|> s.memo()
|> s.to_lustre()
Expand Down
17 changes: 16 additions & 1 deletion apps/frontend/src/frontend/styles.gleam
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import frontend/colors/palette
import gleam/int
import sketch as s
import sketch/size.{px, vh}

Expand All @@ -23,6 +22,8 @@ pub fn layout() {

pub fn navbar() {
s.class([
s.position("sticky"),
s.top(px(0)),
s.display("flex"),
s.align_items("baseline"),
s.justify_content("end"),
Expand Down Expand Up @@ -75,6 +76,7 @@ pub fn main_wrapper() {
s.class([
s.grid_area("main"),
s.display("flex"),
s.flex_direction("column"),
s.align_items("center"),
s.justify_content("center"),
])
Expand Down Expand Up @@ -217,5 +219,18 @@ pub fn foot_title() {

pub fn foot_lk() {
s.class([s.font_size(size.rem_(0.9))])
|> s.memo()
|> s.to_lustre()
}

pub fn flex() {
s.class([s.display("flex")])
|> s.memo()
|> s.to_lustre()
}

pub fn signature() {
s.class([s.display("flex"), s.white_space("pre-wrap")])
|> s.memo()
|> s.to_lustre()
}
153 changes: 137 additions & 16 deletions apps/frontend/src/frontend/view.gleam
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import data/decoders/search_result
import data/decoders/signature.{
type Parameter, type Signature, type Type, Parameter,
}
import data/model.{type Model}
import data/msg
import frontend/footer/view as footer
import frontend/styles as s
import gleam/bool
import gleam/io
import gleam/list
import gleam/option
import gleam/string
import lustre/attribute as a
import lustre/element
import lustre/element/html as h
import lustre/event as e

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(), body(model), footer.view()])
}
Expand All @@ -20,25 +32,134 @@ fn navbar() {
])
}

fn body(model: Model) {
h.main([s.main_wrapper()], [
h.form([e.on_submit(msg.SubmitSearch), s.search_wrapper()], [
h.div([s.search_title_wrapper()], [
h.div([s.search_title()], [
h.img([a.src("/images/lucy.svg"), s.search_lucy()]),
h.text("Gloogle"),
fn view_type(type_: Type) {
case type_ {
signature.Tuple(elements) ->
h.div(
[s.flex()],
list.concat([
[h.text("#(")],
list.map(elements, view_type)
|> list.intersperse(h.text(", ")),
[h.text(")")],
]),
)
signature.Fn(parameters, return) ->
h.div(
[s.flex()],
list.concat([
[h.text("fn(")],
list.map(parameters, view_type)
|> list.intersperse(h.text(", ")),
[h.text(") -> "), view_type(return)],
]),
)
signature.Variable(id) ->
h.div([], {
let assert Ok(utf_a) =
string.to_utf_codepoints("a")
|> list.first()
let assert Ok(letter) =
{ string.utf_codepoint_to_int(utf_a) + id }
|> string.utf_codepoint()
[h.text(string.from_utf_codepoints([letter]))]
})
signature.Named(name, package, module, parameters, ref) -> {
let parameters =
list.map(parameters, view_type)
|> list.intersperse(h.text(", "))
h.div(
[s.flex()],
list.concat([
[h.text(name)],
case parameters {
[] -> [element.none()]
_ -> [h.text("("), ..parameters]
},
[h.text(")")],
]),
)
}
}
}

fn view_parameter(parameter: Parameter) {
let Parameter(label, type_) = parameter
let label =
option.map(label, fn(t) { h.text(t <> ": ") })
|> option.unwrap(element.none())
h.div([s.flex()], [label, view_type(type_)])
}

fn render_parameters(from: Int, to: Int, acc: String) {
use <- bool.guard(when: from == to, return: acc)
let assert Ok(utf_a) =
string.to_utf_codepoints("a")
|> list.first()
let assert Ok(letter) =
{ string.utf_codepoint_to_int(utf_a) + from }
|> string.utf_codepoint()
render_parameters(
from + 1,
to,
acc <> ", " <> string.from_utf_codepoints([letter]),
)
}

fn view_signature(signature: Signature) {
case signature {
signature.TypeDefinition(parameters, constructors) -> h.div([], [])
signature.Constant(type_) -> view_type(type_)
signature.TypeAlias(parameters, alias) ->
h.div([], [h.text("(" <> render_parameters(0, parameters - 1, "") <> ")")])
signature.Function(_, return, parameters) ->
h.div(
[s.flex()],
list.concat([
[h.text("(")],
list.map(parameters, view_parameter)
|> list.intersperse(h.text(", ")),
[h.text(") -> "), view_type(return)],
]),
h.text(
"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.",
),
)
}
}

fn view_search_results(search_results: List(search_result.SearchResult)) {
element.fragment({
use item <- list.map(search_results)
h.div([], [
h.div([s.signature()], [
h.div([], [h.text(item.name <> " : fn")]),
h.div([], [view_signature(item.json_signature)]),
]),
h.input([
s.search_input(),
a.placeholder("Search for a function, or a type"),
e.on_input(msg.UpdateInput),
a.value(model.input),
h.div([], [h.text(item.documentation)]),
])
})
}

fn view_search_input(model: Model) {
h.form([e.on_submit(msg.SubmitSearch), s.search_wrapper()], [
h.div([s.search_title_wrapper()], [
h.div([s.search_title()], [
h.img([a.src("/images/lucy.svg"), s.search_lucy()]),
h.text("Gloogle"),
]),
h.input([a.type_("submit"), a.value("Submit"), s.search_submit()]),
h.text(gloogle_description),
]),
h.input([
s.search_input(),
a.placeholder("Search for a function, or a type"),
e.on_input(msg.UpdateInput),
a.value(model.input),
]),
h.input([a.type_("submit"), a.value("Submit"), s.search_submit()]),
])
}

fn body(model: Model) {
h.main([s.main_wrapper()], case model.search_results {
[] -> [view_search_input(model)]
_ -> [view_search_results(model.search_results)]
})
}

0 comments on commit 989a183

Please sign in to comment.