Current section

Files

Jump to
inlay src inlay@component.erl
Raw

src/inlay@component.erl

-module(inlay@component).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/inlay/component.gleam").
-export([init/1, update/3, view/2, embed_component/1, configure/1, register/0, embed_element/1]).
-export_type([model/0, resolution/0, msg/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" A single universal Lustre component for every inlay provider.\n"
"\n"
" Register one custom element, `<inlay-embed url=\"…\">`, that auto-detects the\n"
" provider from its `url` attribute and renders the matching embed. Configure\n"
" it imperatively with [`configure`](#configure) (or [`register`](#register)\n"
" for the default configuration) from a browser `main`, then use the tag in\n"
" your markup or the [`embed_element`](#embed_element) helper in a Lustre view.\n"
"\n"
" Registration only takes effect in the browser; on other targets `register`\n"
" returns `lustre.NotABrowser` and [`embed_element`](#embed_element) still\n"
" serialises to the plain custom-element tag.\n"
).
-type model() :: {model,
gleam@option:option(binary()),
gleam@option:option(boolean()),
gleam@option:option(binary()),
gleam@option:option(binary()),
resolution()}.
-type resolution() :: static | resolving | {resolved, binary()} | failed.
-type msg() :: {url_changed, binary()} |
{no_cookie_changed, boolean()} |
{parent_changed, binary()} |
{aspect_ratio_changed, binary()} |
{got_did, {ok, binary()} | {error, rsvp:error(binary())}}.
-file("src/inlay/component.gleam", 79).
?DOC(
" Install the embed resize listener on the component's shadow root once it has\n"
" been painted. The listener resizes self-contained embed iframes from the\n"
" provider height messages. It is browser-only; on other targets `after_paint`\n"
" effects never run.\n"
).
-spec install_resize_listener_effect() -> lustre@effect:effect(msg()).
install_resize_listener_effect() ->
lustre@effect:after_paint(
fun(_, Root) -> inlay_component_ffi:install_resize_listener(Root) end
).
-file("src/inlay/component.gleam", 62).
?DOC(" Initialise the component model before any attribute has been received.\n").
-spec init(nil) -> {model(), lustre@effect:effect(msg())}.
init(_) ->
{{model, none, none, none, none, static}, install_resize_listener_effect()}.
-file("src/inlay/component.gleam", 186).
-spec prefer(gleam@option:option(SPA), gleam@option:option(SPA)) -> gleam@option:option(SPA).
prefer(Override, Fallback) ->
case Override of
{some, _} ->
Override;
none ->
Fallback
end.
-file("src/inlay/component.gleam", 179).
-spec optional(gleam@option:option(SOY), SOY) -> SOY.
optional(Override, Fallback) ->
case Override of
{some, Value} ->
Value;
none ->
Fallback
end.
-file("src/inlay/component.gleam", 163).
?DOC(
" Layer this element's per-embed overrides onto the captured base config. Only\n"
" the providers whose rendering can be tuned per embed are affected.\n"
).
-spec effective_config(inlay@embed:config(), model()) -> inlay@embed:config().
effective_config(Config, Model) ->
Youtube@1 = case erlang:element(2, Config) of
{some, Youtube} ->
{some,
{youtube_config,
optional(
erlang:element(3, Model),
erlang:element(2, Youtube)
),
prefer(erlang:element(5, Model), erlang:element(3, Youtube))}};
none ->
none
end,
Twitch = case erlang:element(4, Model) of
{some, Parent} ->
{some, inlay@embed:twitch_config(Parent)};
none ->
erlang:element(9, Config)
end,
{config,
Youtube@1,
erlang:element(3, Config),
erlang:element(4, Config),
erlang:element(5, Config),
erlang:element(6, Config),
erlang:element(7, Config),
erlang:element(8, Config),
Twitch,
erlang:element(10, Config),
erlang:element(11, Config),
erlang:element(12, Config),
erlang:element(13, Config),
erlang:element(14, Config),
erlang:element(15, Config)}.
-file("src/inlay/component.gleam", 89).
?DOC(
" Advance the component model in response to a message, using `config` as the\n"
" base configuration before per-embed overrides are applied.\n"
).
-spec update(inlay@embed:config(), model(), msg()) -> {model(),
lustre@effect:effect(msg())}.
update(Config, Model, Msg) ->
case Msg of
{url_changed, Url} ->
Model@1 = {model,
{some, Url},
erlang:element(3, Model),
erlang:element(4, Model),
erlang:element(5, Model),
erlang:element(6, Model)},
case inlay@detect:detect_with(
Url,
effective_config(Config, Model@1)
) of
{some, {bluesky_post, Handle, _}} ->
case inlay@bluesky:needs_resolution(Handle) of
true ->
{{model,
erlang:element(2, Model@1),
erlang:element(3, Model@1),
erlang:element(4, Model@1),
erlang:element(5, Model@1),
resolving},
inlay@bluesky:resolve_effect(
Handle,
fun(Field@0) -> {got_did, Field@0} end
)};
false ->
{{model,
erlang:element(2, Model@1),
erlang:element(3, Model@1),
erlang:element(4, Model@1),
erlang:element(5, Model@1),
static},
lustre@effect:none()}
end;
_ ->
{{model,
erlang:element(2, Model@1),
erlang:element(3, Model@1),
erlang:element(4, Model@1),
erlang:element(5, Model@1),
static},
lustre@effect:none()}
end;
{no_cookie_changed, No_cookie} ->
{{model,
erlang:element(2, Model),
{some, No_cookie},
erlang:element(4, Model),
erlang:element(5, Model),
static},
lustre@effect:none()};
{parent_changed, Parent} ->
{{model,
erlang:element(2, Model),
erlang:element(3, Model),
{some, Parent},
erlang:element(5, Model),
static},
lustre@effect:none()};
{aspect_ratio_changed, Aspect_ratio} ->
{{model,
erlang:element(2, Model),
erlang:element(3, Model),
erlang:element(4, Model),
{some, Aspect_ratio},
static},
lustre@effect:none()};
{got_did, {ok, Did}} ->
{{model,
erlang:element(2, Model),
erlang:element(3, Model),
erlang:element(4, Model),
erlang:element(5, Model),
{resolved, Did}},
lustre@effect:none()};
{got_did, {error, _}} ->
{{model,
erlang:element(2, Model),
erlang:element(3, Model),
erlang:element(4, Model),
erlang:element(5, Model),
failed},
lustre@effect:none()}
end.
-file("src/inlay/component.gleam", 143).
-spec link(binary()) -> lustre@vdom@vnode:element(any()).
link(Url) ->
lustre@element@html:a(
[lustre@attribute:href(Url)],
[lustre@element:text(Url)]
).
-file("src/inlay/component.gleam", 150).
?DOC(
" Render a Bluesky post that needs no handle resolution. A handle already in\n"
" `did:` form is itself the DID, so the embed iframe renders directly;\n"
" anything else falls back to a link.\n"
).
-spec bluesky_static_view(inlay@embed:config(), binary(), binary()) -> lustre@vdom@vnode:element(any()).
bluesky_static_view(Config, Handle, Rkey) ->
case inlay@bluesky:needs_resolution(Handle) of
false ->
inlay@inline:bluesky_iframe(
Handle,
Rkey,
inlay@detect:bluesky_height(Config)
);
true ->
inlay@bluesky:fallback_view(Handle, Rkey)
end.
-file("src/inlay/component.gleam", 124).
?DOC(
" Render the current component model to HTML, using `config` as the base\n"
" configuration before per-embed overrides are applied.\n"
).
-spec view(inlay@embed:config(), model()) -> lustre@vdom@vnode:element(any()).
view(Config, Model) ->
Config@1 = effective_config(Config, Model),
case erlang:element(2, Model) of
none ->
lustre@element:none();
{some, Url} ->
case inlay@detect:detect_with(Url, Config@1) of
{some, {bluesky_post, Handle, Rkey}} ->
case erlang:element(6, Model) of
{resolved, Did} ->
inlay@inline:bluesky_iframe(
Did,
Rkey,
inlay@detect:bluesky_height(Config@1)
);
resolving ->
inlay@bluesky:fallback_view(Handle, Rkey);
failed ->
inlay@bluesky:fallback_view(Handle, Rkey);
static ->
bluesky_static_view(Config@1, Handle, Rkey)
end;
{some, Found} ->
inlay@detect:render_inline_with(Found, Config@1);
none ->
link(Url)
end
end.
-file("src/inlay/component.gleam", 198).
?DOC(
" The `<inlay-embed>` component configured with the given base config.\n"
"\n"
" The config is captured in the component's `update`/`view` closures. Pass it\n"
" to [`lustre.register`](https://hexdocs.pm/lustre/lustre.html#register) (see\n"
" [`configure`](#configure)) or run it as a server component.\n"
).
-spec embed_component(inlay@embed:config()) -> lustre@runtime@app:app(nil, model(), msg()).
embed_component(Config) ->
lustre:component(
fun init/1,
fun(Model, Msg) -> update(Config, Model, Msg) end,
fun(Model@1) -> view(Config, Model@1) end,
[lustre@component:on_attribute_change(
<<"url"/utf8>>,
fun(Value) -> {ok, {url_changed, Value}} end
),
lustre@component:on_attribute_change(
<<"no-cookie"/utf8>>,
fun(Value@1) ->
{ok, {no_cookie_changed, Value@1 /= <<"false"/utf8>>}}
end
),
lustre@component:on_attribute_change(
<<"parent"/utf8>>,
fun(Value@2) -> {ok, {parent_changed, Value@2}} end
),
lustre@component:on_attribute_change(
<<"aspect-ratio"/utf8>>,
fun(Value@3) -> {ok, {aspect_ratio_changed, Value@3}} end
),
lustre@component:open_shadow_root(true)]
).
-file("src/inlay/component.gleam", 225).
?DOC(
" Register `<inlay-embed>` with the given base configuration. Call this once\n"
" from a browser `main` before rendering any `<inlay-embed>` tags.\n"
"\n"
" Registration is browser-only; on other targets it returns\n"
" `lustre.NotABrowser`. Calling it more than once yields\n"
" `lustre.ComponentAlreadyRegistered`.\n"
).
-spec configure(inlay@embed:config()) -> {ok, nil} | {error, lustre:error()}.
configure(Config) ->
lustre:register(embed_component(Config), <<"inlay-embed"/utf8>>).
-file("src/inlay/component.gleam", 230).
?DOC(" Register `<inlay-embed>` with the default configuration.\n").
-spec register() -> {ok, nil} | {error, lustre:error()}.
register() ->
configure(inlay@embed:default_config()).
-file("src/inlay/component.gleam", 235).
?DOC(" Render the `<inlay-embed>` custom-element tag with the given attributes.\n").
-spec embed_element(list(lustre@vdom@vattr:attribute(SPL))) -> lustre@vdom@vnode:element(SPL).
embed_element(Attributes) ->
lustre@element:element(<<"inlay-embed"/utf8>>, Attributes, []).