Packages
surface
0.7.3
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.ex
defmodule Surface.Catalogue do
@moduledoc """
A behaviour to provide additional information about the catalogue.
Optional for local catalogues. Usually required if you want to share
your components as a library.
"""
@doc """
Returns a keyword list of config options to be used by the catalogue tool.
Available options:
* `head_css` - CSS related content to be added to the `<head>...</head>` section
of each example or playground.
* `head_js` - JS related content to be added to the `<head>...</head>` section
of each example or playground.
* `example` - A keyword list of options to be applied for all examples
in the catalogue.
* `playground` - A keyword list of options to be applied for all playgrounds
in the catalogue.
"""
@callback config :: keyword()
@default_config [
head_css: """
<link phx-track-static rel="stylesheet" href="/assets/app.css"/>
""",
head_js: """
<script defer type="module" src="/assets/app.js"></script>
"""
]
defmacro __using__(_opts) do
quote do
@behaviour Surface.Catalogue
import Surface.Catalogue, only: [load_asset: 2]
end
end
@doc """
Loads a text file as module attribute so you can inject its content directly
in `head_css` or `head_js` config options.
Useful to avoid depending on external css or js code. The path should be relative
to the caller's folder.
Available options:
* `as` - the name of the module attribute to be generated.
"""
defmacro load_asset(file, opts) do
as = Keyword.fetch!(opts, :as)
quote do
path = Path.join(__DIR__, unquote(file))
@external_resource path
Module.put_attribute(__MODULE__, unquote(as), File.read!(path))
end
end
@doc false
def get_metadata(module) do
case Code.fetch_docs(module) do
{:docs_v1, _, _, "text/markdown", docs, %{catalogue: meta}, _} ->
doc =
if :hidden == docs,
do: "",
else: Map.get(docs, "en")
meta |> Map.new() |> Map.put(:doc, doc)
_ ->
nil
end
end
@doc false
def get_config(module) do
meta = get_metadata(module)
project_config = Application.get_env(:surface_catalogue, :assets_config, [])
user_config = Map.get(meta, :config, [])
catalogue = Keyword.get(user_config, :catalogue)
catalogue_config = get_catalogue_config(catalogue)
{type_config, catalogue_config} = Keyword.split(catalogue_config, [:example, :playground])
@default_config
|> Keyword.merge(project_config)
|> Keyword.merge(catalogue_config)
|> Keyword.merge(type_config[meta.type] || [])
|> Keyword.merge(user_config)
end
@doc false
def fetch_subject!(config, type, caller) do
case Keyword.fetch(config, :subject) do
{:ok, subject} ->
subject
_ ->
message = """
no subject defined for #{inspect(type)}
Hint: You can define the subject using the :subject option. Example:
use #{inspect(type)}, subject: MyApp.MyButton
"""
Surface.IOHelper.compile_error(message, caller.file, caller.line)
end
end
defp get_catalogue_config(nil) do
[]
end
defp get_catalogue_config(catalogue) do
if module_loaded?(catalogue) do
catalogue.config()
else
[]
end
end
defp module_loaded?(module) do
match?({:module, _mod}, Code.ensure_compiled(module))
end
end