Current section
Files
Jump to
Current section
Files
src/gliew.erl
-module(gliew).
-compile([no_auto_import, nowarn_unused_vars]).
-export([view/2, response/1, with_header/3, with_body/2, morph/0, append/0, on_click/3, live_mount/3, script/0, new/2, serve/1]).
-export_type([response/0, server/0]).
-opaque response() :: {view,
integer(),
list({binary(), binary()}),
nakai@html:node_(gliew@internal@event:event())} |
{response,
integer(),
list({binary(), binary()}),
gleam@option:option(binary())}.
-type server() :: {server,
integer(),
fun((nakai@html:node_(gliew@internal@event:event())) -> nakai@html:node_(gliew@internal@event:event())),
fun((gleam@http@request:request(mist@internal@http:body())) -> response())}.
-spec view(nakai@html:node_(gliew@internal@event:event()), integer()) -> response().
view(Node, Status) ->
{view, Status, [], Node}.
-spec response(integer()) -> response().
response(Status) ->
{response, Status, [], none}.
-spec with_header(response(), binary(), binary()) -> response().
with_header(Response, Key, Value) ->
case Response of
{view, Status, Headers, Node} ->
_pipe = Headers,
_pipe@1 = gleam@list:prepend(_pipe, {Key, Value}),
{view, Status, _pipe@1, Node};
{response, Status@1, Headers@1, Body} ->
_pipe@2 = Headers@1,
_pipe@3 = gleam@list:prepend(_pipe@2, {Key, Value}),
{response, Status@1, _pipe@3, Body}
end.
-spec with_body(response(), binary()) -> response().
with_body(Response, Body) ->
case Response of
{response, Status, Headers, _} ->
{response, Status, Headers, {some, Body}};
_ ->
Response
end.
-spec morph() -> nakai@html@attrs:attr(gliew@internal@event:event()).
morph() ->
{event, <<"gliew-event"/utf8>>, morph}.
-spec append() -> nakai@html@attrs:attr(gliew@internal@event:event()).
append() ->
{event, <<"gliew-event"/utf8>>, append}.
-spec add_attr(
nakai@html:node_(gliew@internal@event:event()),
nakai@html@attrs:attr(gliew@internal@event:event())
) -> nakai@html:node_(gliew@internal@event:event()).
add_attr(Node, Attr) ->
case Node of
{element, Tag, Attrs, Children} ->
_pipe = Attrs,
_pipe@1 = gleam@list:prepend(_pipe, Attr),
{element, Tag, _pipe@1, Children};
{leaf_element, Tag@1, Attrs@1} ->
_pipe@2 = Attrs@1,
_pipe@3 = gleam@list:prepend(_pipe@2, Attr),
{leaf_element, Tag@1, _pipe@3};
{html, Attrs@2, Children@1} ->
_pipe@4 = Attrs@2,
_pipe@5 = gleam@list:prepend(_pipe@4, Attr),
{html, _pipe@5, Children@1};
{body, Attrs@3, Children@2} ->
_pipe@6 = Attrs@3,
_pipe@7 = gleam@list:prepend(_pipe@6, Attr),
{body, _pipe@7, Children@2};
Other ->
Other
end.
-spec on_click(
nakai@html:node_(gliew@internal@event:event()),
gleam@http:method(),
binary()
) -> nakai@html:node_(gliew@internal@event:event()).
on_click(Node, Method, Path) ->
case Method of
get ->
_pipe = Node,
_pipe@1 = add_attr(_pipe, {attr, <<"hx-get"/utf8>>, Path}),
add_attr(_pipe@1, {attr, <<"hx-swap"/utf8>>, <<"none"/utf8>>});
post ->
_pipe@2 = Node,
_pipe@3 = add_attr(_pipe@2, {attr, <<"hx-post"/utf8>>, Path}),
add_attr(_pipe@3, {attr, <<"hx-swap"/utf8>>, <<"none"/utf8>>});
put ->
_pipe@4 = Node,
_pipe@5 = add_attr(_pipe@4, {attr, <<"hx-put"/utf8>>, Path}),
add_attr(_pipe@5, {attr, <<"hx-swap"/utf8>>, <<"none"/utf8>>});
patch ->
_pipe@6 = Node,
_pipe@7 = add_attr(_pipe@6, {attr, <<"hx-patch"/utf8>>, Path}),
add_attr(_pipe@7, {attr, <<"hx-swap"/utf8>>, <<"none"/utf8>>});
delete ->
_pipe@8 = Node,
_pipe@9 = add_attr(_pipe@8, {attr, <<"hx-delete"/utf8>>, Path}),
add_attr(_pipe@9, {attr, <<"hx-swap"/utf8>>, <<"none"/utf8>>});
_ ->
Node
end.
-spec insert_event(
gliew@internal@event:event(),
nakai@html:node_(gliew@internal@event:event())
) -> nakai@html:node_(gliew@internal@event:event()).
insert_event(Event, Node) ->
_pipe = Node,
add_attr(_pipe, {event, <<"gliew-event"/utf8>>, Event}).
-spec map_events(
list(nakai@html@attrs:attr(gliew@internal@event:event())),
boolean()
) -> list(nakai@html@attrs:attr(gliew@internal@event:event())).
map_events(Attrs, Render_events) ->
case Render_events of
true ->
gleam@list:map(Attrs, fun(Attr) -> case Attr of
{event, <<"gliew-event"/utf8>>, morph} ->
{attr, <<"hx-swap-oob"/utf8>>, <<"morph"/utf8>>};
{event, <<"gliew-event"/utf8>>, append} ->
{attr, <<"hx-swap-oob"/utf8>>, <<"beforeend"/utf8>>};
Other ->
Other
end end);
false ->
Attrs
end.
-spec find_id(list(nakai@html@attrs:attr(gliew@internal@event:event()))) -> {ok,
binary()} |
{error, nil}.
find_id(Attrs) ->
_pipe = Attrs,
gleam@list:find_map(_pipe, fun(Attr) -> case Attr of
{attr, <<"id"/utf8>>, Id} ->
{ok, Id};
_ ->
{error, nil}
end end).
-spec random_id() -> binary().
random_id() ->
<<"g-"/utf8, (gliew@internal@util:random_hex_string(3))/binary>>.
-spec extract_id(nakai@html:node_(gliew@internal@event:event())) -> binary().
extract_id(Node) ->
_pipe = case Node of
{element, _, Attrs, _} ->
find_id(Attrs);
{leaf_element, _, Attrs@1} ->
find_id(Attrs@1);
_ ->
{error, nil}
end,
gleam@result:unwrap(_pipe, random_id()).
-spec has_id(list(nakai@html@attrs:attr(any()))) -> boolean().
has_id(Attrs) ->
_pipe = Attrs,
gleam@list:any(_pipe, fun(A) -> case A of
{attr, <<"id"/utf8>>, _} ->
true;
_ ->
false
end end).
-spec ensure_id(
list(nakai@html@attrs:attr(gliew@internal@event:event())),
gleam@option:option(binary())
) -> list(nakai@html@attrs:attr(gliew@internal@event:event())).
ensure_id(Attrs, Id) ->
case has_id(Attrs) of
true ->
case Id of
{some, Id@1} ->
gleam@list:map(Attrs, fun(Attr) -> case Attr of
{attr, <<"id"/utf8>>, _} ->
nakai@html@attrs:id(Id@1);
Other ->
Other
end end);
none ->
Attrs
end;
false ->
_pipe = Attrs,
gleam@list:prepend(
_pipe,
nakai@html@attrs:id(
begin
_pipe@1 = Id,
gleam@option:unwrap(_pipe@1, random_id())
end
)
)
end.
-spec process_tree(
nakai@html:node_(gliew@internal@event:event()),
gleam@option:option(binary()),
boolean()
) -> nakai@html:node_(gliew@internal@event:event()).
process_tree(Node, Id, Render_events) ->
case Node of
{element, Tag, Attrs, Children} ->
_pipe = Attrs,
_pipe@1 = ensure_id(_pipe, Id),
_pipe@2 = map_events(_pipe@1, Render_events),
{element, Tag, _pipe@2, Children};
{leaf_element, Tag@1, Attrs@1} ->
_pipe@3 = Attrs@1,
_pipe@4 = ensure_id(_pipe@3, Id),
_pipe@5 = map_events(_pipe@4, Render_events),
{leaf_element, Tag@1, _pipe@5};
Other ->
Other
end.
-spec live_mount(
fun((MEC) -> gleam@erlang@process:subject(MED)),
MEC,
fun((gleam@option:option(MED)) -> nakai@html:node_(gliew@internal@event:event()))
) -> nakai@html:node_(gliew@internal@event:event()).
live_mount(Mount, Context, Render) ->
Tree = begin
_pipe = Render(none),
process_tree(_pipe, none, false)
end,
Id = extract_id(Tree),
_pipe@5 = fun(Selector) ->
Subject = Mount(Context),
_pipe@1 = Selector,
gleam@erlang@process:selecting(
_pipe@1,
Subject,
fun(Val) -> _pipe@2 = Render({some, Val}),
_pipe@3 = process_tree(_pipe@2, {some, Id}, true),
_pipe@4 = nakai:to_inline_string(_pipe@3),
{live_update, _pipe@4} end
)
end,
_pipe@6 = {live_mount, _pipe@5},
insert_event(_pipe@6, Tree).
-spec script() -> nakai@html:node_(any()).
script() ->
{fragment,
[{element,
<<"script"/utf8>>,
[nakai@html@attrs:src(
<<"https://unpkg.com/htmx.org@1.9.2/dist/htmx.min.js"/utf8>>
)],
[]},
{element,
<<"script"/utf8>>,
[nakai@html@attrs:src(
<<"https://unpkg.com/htmx.org@1.9.2/dist/ext/ws.js"/utf8>>
)],
[]},
{element,
<<"script"/utf8>>,
[nakai@html@attrs:src(
<<"https://unpkg.com/idiomorph@0.0.8/dist/idiomorph-ext.min.js"/utf8>>
)],
[]}]}.
-spec default_layout(nakai@html:node_(gliew@internal@event:event())) -> nakai@html:node_(gliew@internal@event:event()).
default_layout(Content) ->
{html, [], [{head, [script()]}, {body, [], [Content]}]}.
-spec new(
integer(),
fun((gleam@http@request:request(mist@internal@http:body())) -> response())
) -> server().
new(Port, Handler) ->
{server, Port, fun default_layout/1, Handler}.
-spec add_headers(gleam@http@response:response(MFW), list({binary(), binary()})) -> gleam@http@response:response(MFW).
add_headers(Response, Headers) ->
_pipe = Headers,
gleam@list:fold(_pipe, Response, fun(Res, Pair) -> _pipe@1 = Res,
gleam@http@response:prepend_header(
_pipe@1,
erlang:element(1, Pair),
erlang:element(2, Pair)
) end).
-spec to_mist_response(
gleam@http@response:response(any()),
gleam@option:option(binary())
) -> mist@internal@handler:handler_response().
to_mist_response(Response, Body) ->
case Body of
{some, Body@1} ->
_pipe = Response,
mist:bit_builder_response(
_pipe,
gleam@bit_builder:from_string(Body@1)
);
none ->
_pipe@1 = Response,
mist:empty_response(_pipe@1)
end.
-spec upgrade_connection(
gleam@erlang@process:subject(gliew@internal@manager:message()),
binary(),
binary()
) -> mist@internal@handler:handler_response().
upgrade_connection(Manager, Session, Csrf) ->
case gliew@internal@manager:get_worker(Manager, Session, Csrf) of
{ok, Worker} ->
_pipe = fun(_, _) -> {ok, nil} end,
_pipe@1 = mist@websocket:with_handler(_pipe),
_pipe@2 = mist@websocket:on_init(
_pipe@1,
fun(Socket) -> gliew@internal@worker:connect(Worker, Socket) end
),
_pipe@3 = mist@websocket:on_close(
_pipe@2,
fun(Socket@1) ->
gliew@internal@worker:disconnect(Worker, Socket@1)
end
),
mist:upgrade(_pipe@3);
{error, nil} ->
_pipe@4 = gleam@http@response:new(401),
mist:empty_response(_pipe@4)
end.
-spec get_params(list({binary(), binary()})) -> {ok, {binary(), binary()}} |
{error, nil}.
get_params(Params) ->
Pmap = gleam@map:from_list(Params),
gleam@result:then(
gleam@map:get(Pmap, <<"session"/utf8>>),
fun(Session) ->
gleam@result:then(
gleam@map:get(Pmap, <<"csrf"/utf8>>),
fun(Csrf) -> {ok, {Session, Csrf}} end
)
end
).
-spec parse_params(gleam@http@request:request(mist@internal@http:body())) -> {ok,
{binary(), binary()}} |
{error, nil}.
parse_params(Req) ->
case erlang:element(9, Req) of
{some, Params} ->
case gleam@uri:parse_query(Params) of
{ok, Params@1} ->
_pipe@1 = gleam@list:map(
Params@1,
fun(P) ->
{erlang:element(1, P),
begin
_pipe = erlang:element(2, P),
gleam@string:replace(
_pipe,
<<" "/utf8>>,
<<"+"/utf8>>
)
end}
end
),
get_params(_pipe@1);
{error, nil} ->
{error, nil}
end;
none ->
{error, nil}
end.
-spec handle_ws_connect(
gleam@erlang@process:subject(gliew@internal@manager:message()),
gleam@http@request:request(mist@internal@http:body())
) -> mist@internal@handler:handler_response().
handle_ws_connect(Manager, Req) ->
case parse_params(Req) of
{ok, {Id, Csrf}} ->
upgrade_connection(Manager, Id, Csrf);
{error, nil} ->
_pipe = gleam@http@response:new(401),
mist:empty_response(_pipe)
end.
-spec handler_func(
gleam@erlang@process:subject(gliew@internal@manager:message()),
fun((nakai@html:node_(gliew@internal@event:event())) -> nakai@html:node_(gliew@internal@event:event())),
fun((gleam@http@request:request(mist@internal@http:body())) -> response())
) -> fun((glisten@handler:handler_message(), glisten@handler:loop_state(mist@internal@handler:state())) -> gleam@otp@actor:next(glisten@handler:loop_state(mist@internal@handler:state()))).
handler_func(Manager, Layout, Handler) ->
_pipe@7 = fun(Req) ->
case {erlang:element(2, Req), erlang:element(8, Req)} of
{get, <<"/connect"/utf8>>} ->
handle_ws_connect(Manager, Req);
{_, _} ->
case Handler(Req) of
{response, Status, Headers, Body} ->
_pipe = gleam@http@response:new(Status),
_pipe@1 = add_headers(_pipe, Headers),
to_mist_response(_pipe@1, Body);
{view, Status@1, Headers@1, Node} ->
_pipe@2 = gleam@http@response:new(Status@1),
_pipe@3 = add_headers(_pipe@2, Headers@1),
mist:bit_builder_response(
_pipe@3,
begin
_pipe@4 = gliew@internal@manager:process_tree(
Manager,
Req,
Node
),
_pipe@5 = Layout(_pipe@4),
_pipe@6 = nakai:to_string_builder(_pipe@5),
gleam@bit_builder:from_string_builder(_pipe@6)
end
)
end
end
end,
mist:handler_func(_pipe@7).
-spec serve(server()) -> {ok, nil} | {error, glisten:start_error()}.
serve(Server) ->
gleam@result:'try'(
begin
_pipe = gliew@internal@manager:start_manager(),
gleam@result:map_error(_pipe, fun(Err) -> case Err of
init_timeout ->
acceptor_timeout;
{init_failed, Reason} ->
{acceptor_failed, Reason};
{init_crashed, Any} ->
{acceptor_crashed, Any}
end end)
end,
fun(Manager) ->
mist:serve(
erlang:element(2, Server),
handler_func(
Manager,
erlang:element(3, Server),
erlang:element(4, Server)
)
)
end
).