live_svelte/lib/component.ex

168 lines
3.6 KiB
Elixir
Raw Permalink Normal View History

defmodule LiveSvelte do
use Phoenix.Component
2023-02-20 01:46:05 +00:00
import Phoenix.HTML
2023-06-15 03:00:49 +00:00
import LiveSvelte.LiveJson
2023-02-20 01:46:05 +00:00
2023-03-08 18:05:35 +00:00
alias LiveSvelte.Slots
alias LiveSvelte.SSR
attr(
:props,
:map,
default: %{},
doc: "Props to pass to the Svelte component",
examples: [%{foo: "bar"}, %{foo: "bar", baz: 1}, %{list: [], baz: 1, qux: %{a: 1, b: 2}}]
)
attr(
:name,
:string,
required: true,
doc: "Name of the Svelte component",
examples: ["YourComponent", "directory/Example"]
)
attr(
:class,
:string,
default: nil,
doc: "Class to apply to the Svelte component",
examples: ["my-class", "my-class another-class"]
)
attr(
:ssr,
:boolean,
default: true,
doc: "Whether to render the component on the server",
examples: [true, false]
)
2023-06-15 03:00:49 +00:00
attr(
:live_json_props,
:map,
default: %{},
doc: "LiveJson props to pass to the Svelte component",
examples: [
%{my_big_data_set: %{some_data: 1}}
]
)
slot(:inner_block, doc: "Inner block of the Svelte component")
2023-02-20 01:46:05 +00:00
2023-02-26 02:40:33 +00:00
@doc """
Renders a Svelte component on the server.
"""
2023-05-27 00:22:50 +00:00
def svelte(assigns) do
2023-07-30 11:39:47 +00:00
init = Map.get(assigns, :__changed__) == nil
2023-02-20 01:46:05 +00:00
2023-03-08 18:05:35 +00:00
slots =
assigns
|> Slots.rendered_slot_map()
|> Slots.js_process()
2023-02-25 20:33:09 +00:00
ssr_code =
2023-03-25 20:50:58 +00:00
if init and Map.get(assigns, :ssr) do
try do
2023-06-15 03:00:49 +00:00
props =
Map.merge(
Map.get(assigns, :props, %{}),
Map.get(assigns, :live_json_props, %{})
)
SSR.render(assigns.name, props, slots)
2023-03-25 20:50:58 +00:00
rescue
SSR.NodeNotConfigured -> nil
end
2023-02-25 20:33:09 +00:00
end
2023-02-20 01:46:05 +00:00
assigns =
assigns
2023-06-28 23:01:19 +00:00
|> assign(:init, init)
2023-03-08 18:05:35 +00:00
|> assign(:slots, slots)
2023-02-25 20:33:09 +00:00
|> assign(:ssr_render, ssr_code)
2023-02-20 01:46:05 +00:00
~H"""
2023-06-15 03:00:49 +00:00
<.live_json live_json_props={@live_json_props}>
<script><%= raw(@ssr_render["head"]) %></script>
<div
id={id(@name)}
data-name={@name}
data-props={json(@props)}
2023-07-30 11:39:47 +00:00
data-ssr={@ssr_render != nil}
2023-06-28 23:01:19 +00:00
data-live-json={if @init, do: json(@live_json_props), else: @live_json_props |> Map.keys() |> json()}
2023-06-15 03:00:49 +00:00
data-slots={Slots.base_encode_64(@slots) |> json}
phx-update="ignore"
phx-hook="SvelteHook"
class={@class}
>
<style><%= raw(@ssr_render["css"]["code"]) %></style>
<%= raw(@ssr_render["html"]) %>
</div>
</.live_json>
"""
2023-02-20 01:46:05 +00:00
end
2023-05-27 00:22:50 +00:00
def render(assigns) do
2023-06-15 03:00:49 +00:00
IO.warn(
"`LiveSvelte.render/1` is deprecated; call `LiveSvelte.svelte/1` instead.",
Macro.Env.stacktrace(__ENV__)
)
2023-05-27 00:22:50 +00:00
svelte(assigns)
end
2023-02-20 01:46:05 +00:00
defp json(props) do
props
|> Jason.encode()
|> case do
{:ok, encoded} -> encoded
{:error, _} -> ""
end
end
defp id(name), do: "#{name}-#{System.unique_integer([:positive])}"
2023-04-26 17:47:27 +00:00
@doc false
def get_props(assigns) do
prop_keys =
assigns
|> Map.get(:__changed__)
|> Map.keys()
assigns
|> Map.filter(fn
2023-04-26 19:55:11 +00:00
{:svelte_opts, _v} -> false
{k, _v} -> k in prop_keys
end)
2023-04-26 17:47:27 +00:00
end
2023-04-26 19:55:11 +00:00
@doc false
def get_ssr(assigns) do
case get_in(assigns, [:svelte_opts, :ssr]) do
nil -> true
ssr -> ssr
end
end
2023-04-26 17:47:27 +00:00
@doc false
defmacro sigil_V({:<<>>, _meta, [string]}, []) do
path = "./assets/svelte/_build/#{__CALLER__.module}.svelte"
with :ok <- File.mkdir_p(Path.dirname(path)) do
File.write!(path, string)
end
quote do
~H"""
2023-05-27 00:22:50 +00:00
<LiveSvelte.svelte
2023-04-26 19:55:11 +00:00
name={"_build/#{__MODULE__}"}
props={get_props(assigns)}
ssr={get_ssr(assigns)}
class={get_in(assigns, [:svelte_opts, :class])}
/>
2023-04-26 17:47:27 +00:00
"""
end
end
2023-02-20 01:46:05 +00:00
end