-module(gliew). -compile([no_auto_import, nowarn_unused_vars]). -export([view/2, response/1, with_header/3, on_click/3, live_mount/3, serve/2]). -export_type([response/0]). -opaque response() :: {view, integer(), list({binary(), binary()}), nakai@html:node_(gliew@internal@event:event())} | {response, integer(), list({binary(), binary()})}. -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, []}. -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} -> _pipe@2 = Headers@1, _pipe@3 = gleam@list:prepend(_pipe@2, {Key, Value}), {response, Status@1, _pipe@3} end. -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 add_morph_attr(nakai@html:node_(gliew@internal@event:event())) -> nakai@html:node_(gliew@internal@event:event()). add_morph_attr(Node) -> _pipe = Node, add_attr(_pipe, {attr, <<"hx-swap-oob"/utf8>>, <<"morph"/utf8>>}). -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()) ) -> nakai@html:node_(gliew@internal@event:event()). process_tree(Node, Id) -> case Node of {element, Tag, Attrs, Children} -> _pipe = Attrs, _pipe@1 = ensure_id(_pipe, Id), {element, Tag, _pipe@1, Children}; {leaf_element, Tag@1, Attrs@1} -> _pipe@2 = Attrs@1, _pipe@3 = ensure_id(_pipe@2, Id), {leaf_element, Tag@1, _pipe@3}; Other -> Other end. -spec live_mount( fun((MEI) -> gleam@erlang@process:subject(MEJ)), MEI, fun((gleam@option:option(MEJ)) -> 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) end, Id = extract_id(Tree), _pipe@6 = 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}), _pipe@4 = add_morph_attr(_pipe@3), _pipe@5 = nakai:to_inline_string(_pipe@4), {live_update, _pipe@5} end ) end, _pipe@7 = {live_mount, _pipe@6}, insert_event(_pipe@7, Tree). -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((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, Handler) -> _pipe@8 = 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} -> _pipe = gleam@http@response:new(Status), _pipe@2 = gleam@list:fold( Headers, _pipe, fun(Res, Pair) -> _pipe@1 = Res, gleam@http@response:prepend_header( _pipe@1, erlang:element(1, Pair), erlang:element(2, Pair) ) end ), mist:empty_response(_pipe@2); {view, Status@1, Headers@1, Node} -> _pipe@3 = gleam@http@response:new(Status@1), _pipe@5 = gleam@list:fold( Headers@1, _pipe@3, fun(Res@1, Pair@1) -> _pipe@4 = Res@1, gleam@http@response:prepend_header( _pipe@4, erlang:element(1, Pair@1), erlang:element(2, Pair@1) ) end ), mist:bit_builder_response( _pipe@5, begin _pipe@6 = {html, [], [{head, [{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>> )], []}]}, {body, [], [{unsafe_text, gliew@internal@manager:render_tree( Manager, Req, Node )}]}]}, _pipe@7 = nakai:to_string(_pipe@6), gleam@bit_builder:from_string(_pipe@7) end ) end end end, mist:handler_func(_pipe@8). -spec serve( integer(), fun((gleam@http@request:request(mist@internal@http:body())) -> response()) ) -> {ok, nil} | {error, glisten:start_error()}. serve(Port, Handler) -> 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(Port, handler_func(Manager, Handler)) end ).