-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, ``, 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 `` 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 `` with the given base configuration. Call this once\n" " from a browser `main` before rendering any `` 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 `` 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 `` 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, []).