Packages
surface
0.5.1
0.12.3
0.12.2
0.12.1
0.12.0
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.1
0.1.0
0.1.0-alpha.2
0.1.0-alpha.1
0.1.0-alpha.0
A component based library for Phoenix LiveView
Current section
Files
Jump to
Current section
Files
lib/surface/catalogue/playground.ex
defmodule Surface.Catalogue.Playground do
@moduledoc """
Experimental LiveView to create Playgrounds for catalogue tools.
## Options
Besides the buit-in options provided by the LiveView itself, a Playground also
provides the following options:
* `subject` - Required. The target component of the Playground.
* `height` - Required. The initial height of the Playground.
* `catalogue` - Optional. A module that implements the `Surface.Catalogue`
providing additional information to the catalogue tool. Usually required
if you want to share your components as a library.
* `body` - Optional. Sets/overrides the attributes of the Playground's body tag.
Useful to set a different background or padding.
"""
import Phoenix.LiveView
@pubsub Surface.Catalogue.PubSub
defmacro __using__(opts) do
subject = Surface.Catalogue.fetch_subject!(opts, __MODULE__, __CALLER__)
quote do
use Surface.LiveView, unquote(opts)
alias unquote(subject)
require Surface.Catalogue.Data, as: Data
@config unquote(opts)
@before_compile unquote(__MODULE__)
@impl true
def mount(params, session, socket) do
unquote(__MODULE__).__mount__(params, session, socket, unquote(subject))
end
@impl true
def handle_info(message, socket) do
unquote(__MODULE__).__handle_info__(message, socket)
end
end
end
# Retrieves or creates the window id that should be used to filter
# PubSub messages from the Playground.
@doc false
def get_window_id(session, params) do
key = "__window_id__"
get_value_by_key(session, key) ||
get_value_by_key(params, key) ||
Base.encode16(:crypto.strong_rand_bytes(16))
end
# Subscribes to receive notification messages from the Playground.
@doc false
def subscribe(window_id) do
Phoenix.PubSub.subscribe(@pubsub, topic(window_id))
end
defp notify_init(window_id, subject, props, events, props_values_with_events) do
message = {:playground_init, self(), subject, props, events, props_values_with_events}
Phoenix.PubSub.broadcast(@pubsub, topic(window_id), message)
end
defp notify_event_received(window_id, event, value, props) do
message = {:playground_event_received, event, value, props}
Phoenix.PubSub.broadcast(@pubsub, topic(window_id), message)
end
defp topic(window_id) do
"#{@pubsub}:#{window_id}"
end
defmacro __before_compile__(env) do
config = Module.get_attribute(env.module, :config)
subject = Keyword.fetch!(config, :subject)
module_doc =
quote do
@moduledoc catalogue: [
type: :playground,
subject: unquote(subject),
config: unquote(config)
]
end
if Module.defines?(env.module, {:handle_event, 3}) do
quote do
unquote(module_doc)
defoverridable handle_event: 3
@impl true
def handle_event(event, value, socket) do
result = super(event, value, socket)
socket =
case result do
{:noreply, socket} -> socket
{:reply, _map, socket} -> socket
end
unquote(__MODULE__).__handle_event__(event, value, socket)
result
end
end
else
quote do
unquote(module_doc)
@impl true
def handle_event(event, value, socket) do
unquote(__MODULE__).__handle_event__(event, value, socket)
end
end
end
end
@doc false
def __mount__(params, session, socket, subject) do
window_id = get_window_id(session, params)
socket = assign(socket, :__window_id__, window_id)
if connected?(socket) do
{events, props} =
subject.__props__()
|> Enum.split_with(fn prop -> prop.type == :event end)
events_props_values = generate_events_props(events)
props_values =
props
|> get_props_default_values()
|> Map.merge(socket.assigns.props)
|> Map.merge(events_props_values)
notify_init(window_id, subject, props, events, props_values)
{:ok, assign(socket, :props, props_values)}
else
{:ok, socket}
end
end
@doc false
def __handle_info__({:update_props, values}, socket) do
{:noreply, assign(socket, :props, values)}
end
def __handle_info__(:wake_up, socket) do
{:noreply, socket}
end
@doc false
def __handle_event__(event, value, socket) do
window_id = socket.assigns[:__window_id__]
notify_event_received(window_id, event, value, socket.assigns.props)
{:noreply, socket}
end
defp get_value_by_key(map, key) when is_map(map) do
map[key]
end
defp get_value_by_key(_map, _key) do
nil
end
defp generate_events_props(events) do
for %{name: name} <- events, into: %{} do
{name, %{name: name, target: :live_view}}
end
end
defp get_props_default_values(props) do
for %{name: name, opts: opts} <- props,
Keyword.has_key?(opts, :default),
into: %{} do
{name, opts[:default]}
end
end
end