diff --git a/lib/surface/catalogue/components/component_tree.ex b/lib/surface/catalogue/components/component_tree.ex
index b01093b..3b91e49 100644
--- a/lib/surface/catalogue/components/component_tree.ex
+++ b/lib/surface/catalogue/components/component_tree.ex
@@ -7,7 +7,7 @@ defmodule Surface.Catalogue.Components.ComponentTree do
prop selected_component, :string
prop single_catalogue?, :boolean
- prop components, :map
+ prop components, :any
def render(assigns) do
~F"""
diff --git a/lib/surface/catalogue/components/extendable_sort/item.ex b/lib/surface/catalogue/components/extendable_sort/item.ex
new file mode 100644
index 0000000..545b484
--- /dev/null
+++ b/lib/surface/catalogue/components/extendable_sort/item.ex
@@ -0,0 +1,96 @@
+defmodule Surface.Catalogue.Components.ExtendableSort.Item do
+ @moduledoc false
+
+ use Surface.LiveComponent
+
+ alias Surface.Catalogue.ExtendableSort
+ alias Surface.Components.LivePatch
+
+ prop selected_component, :string
+ prop single_catalogue?, :boolean
+ prop components, :map
+
+ def render(assigns, node) when is_list(node) do
+ ~F"""
+ {#for item <- node}
+
+ {render(assigns, item)}
+
+ {#else}
+
+ {/for}
+ """
+ end
+
+ def render(assigns, %ExtendableSort.Category{name: "Root"} = node) do
+ ~F"""
+
+ {render(assigns, node.children)}
+
+ """
+ end
+
+ def render(assigns, %ExtendableSort.Category{} = node) do
+ ~F"""
+
+
+
+
+
+ {node.name}
+
+
+ {render(assigns, node.children)}
+
+
+ """
+ end
+
+ def render(assigns, %ExtendableSort.Module{} = node) do
+ ~F"""
+
+
+
+
+ {node.name}
+
+
+ """
+ end
+
+ #
+ # Private
+ #
+
+ defp component_icon(type) do
+ case type do
+ Surface.MacroComponent ->
+ "fas fa-hashtag"
+
+ _ ->
+ "far fa-file-code"
+ end
+ end
+
+ defp selected_component?(mod_path, component) do
+ component == Enum.join(mod_path, ".")
+ end
+
+ defp has_child_selected?(_mod_path, nil) do
+ false
+ end
+
+ defp has_child_selected?(mod_path, component) do
+ String.starts_with?(component, Enum.join(mod_path, ".") <> ".")
+ end
+
+ defp show_nodes?(_parent_keys = [], _selected_component, _single_catalogue?) do
+ true
+ end
+
+ defp show_nodes?(parent_keys, selected_component, single_catalogue?) do
+ has_child_selected?(parent_keys, selected_component) or
+ (single_catalogue? and length(parent_keys) == 1)
+ end
+end
diff --git a/lib/surface/catalogue/components/extendable_sort/tree.ex b/lib/surface/catalogue/components/extendable_sort/tree.ex
new file mode 100644
index 0000000..bf4d1ae
--- /dev/null
+++ b/lib/surface/catalogue/components/extendable_sort/tree.ex
@@ -0,0 +1,21 @@
+defmodule Surface.Catalogue.Components.ExtendableSort.Tree do
+ @moduledoc false
+
+ use Surface.LiveComponent
+
+ alias Surface.Components.LivePatch
+
+ alias Surface.Catalogue.ExtendableSort
+
+ prop selected_component, :string
+ prop single_catalogue?, :boolean
+ prop components, :any
+
+ def render(assigns) do
+ ~F"""
+
+ """
+ end
+end
diff --git a/lib/surface/catalogue/extendable_sort.ex b/lib/surface/catalogue/extendable_sort.ex
new file mode 100644
index 0000000..7cc58db
--- /dev/null
+++ b/lib/surface/catalogue/extendable_sort.ex
@@ -0,0 +1,37 @@
+defmodule Surface.Catalogue.ExtendableSort do
+ def handle(surface_ast, config \\ []) do
+ surface_ast
+ |> Surface.Catalogue.ExtendableSort.MapHandler.to_extendable_sort()
+ |> Surface.Catalogue.ExtendableSort.Builder.from_map()
+ |> apply_sort_list(get_sort_config())
+ end
+
+ def get_sort_config do
+ Application.get_env(:surface_catalogue, :sort, [
+ Surface.Catalogue.ExtendableSort.Adapters.CategoryFirst,
+ Surface.Catalogue.ExtendableSort.Adapters.ByCatalogueABC
+ ])
+ end
+
+ def run_extendable(ast, module) do
+ case apply(module, :apply, [ast]) do
+ {:ok, returned_ast} ->
+ {:ok, returned_ast}
+
+ {:error, _message} ->
+ {:error, ast}
+
+ _ ->
+ {:error, ast}
+ end
+ end
+
+ def apply_sort_list(init_ast, [head | tail] = _sort_modules) do
+ {_resp_type, ast} = run_extendable(init_ast, head)
+ apply_sort_list(ast, tail)
+ end
+
+ def apply_sort_list(ast, [] = _sort_modules) do
+ ast
+ end
+end
diff --git a/lib/surface/catalogue/extendable_sort/adapters.ex b/lib/surface/catalogue/extendable_sort/adapters.ex
new file mode 100644
index 0000000..48c7880
--- /dev/null
+++ b/lib/surface/catalogue/extendable_sort/adapters.ex
@@ -0,0 +1,24 @@
+defmodule Surface.Catalogue.ExtendableSort.Adapter do
+ @type error :: Exception.t() | String.t()
+
+ @doc """
+ Selective filter `children` in a nested data structure and
+ apply `sort/1` to them.
+ """
+ @callback apply(ast :: any) :: {:ok, any} | {:error, error}
+
+ @doc """
+ Sorting rules
+
+ ```elixir
+ Enum.sort_by(points, fn(p) -> p.points end)
+ ```
+
+ ```elixir
+ Enum.sort_by(points, fn(p) -> {p.points, p.coordinate} end)
+ ```
+
+ https://stackoverflow.com/questions/48310861/sort-list-of-maps-based-on-a-value-within-the-map
+ """
+ @callback sort(ast :: any) :: {:ok, any} | {:error, error}
+end
diff --git a/lib/surface/catalogue/extendable_sort/adapters/by_catalogue_abc.ex b/lib/surface/catalogue/extendable_sort/adapters/by_catalogue_abc.ex
new file mode 100644
index 0000000..be6e708
--- /dev/null
+++ b/lib/surface/catalogue/extendable_sort/adapters/by_catalogue_abc.ex
@@ -0,0 +1,16 @@
+defmodule Surface.Catalogue.ExtendableSort.Adapters.ByCatalogueABC do
+ @moduledoc """
+ Sort Catalogues in alphabetical order.
+ """
+ @behaviour Surface.Catalogue.ExtendableSort.Adapter
+
+ @impl true
+ def apply(ast) do
+ {:ok, sort(ast)}
+ end
+
+ @impl true
+ def sort(ast) do
+ Enum.sort_by(ast, fn p -> p.name end)
+ end
+end
diff --git a/lib/surface/catalogue/extendable_sort/adapters/category_first.ex b/lib/surface/catalogue/extendable_sort/adapters/category_first.ex
new file mode 100644
index 0000000..ec87dbc
--- /dev/null
+++ b/lib/surface/catalogue/extendable_sort/adapters/category_first.ex
@@ -0,0 +1,32 @@
+defmodule Surface.Catalogue.ExtendableSort.Adapters.CategoryFirst do
+ @moduledoc """
+ Sort Module Maps by Module Name in alphabetical order.
+ """
+ @behaviour Surface.Catalogue.ExtendableSort.Adapter
+
+ @impl true
+ def apply(ast) do
+ {:ok, ast |> sort_children |> sort}
+ end
+
+ def sort_children([head | tail] = _ast) do
+ [sort_children(head) | sort_children(tail)]
+ end
+
+ def sort_children(%Surface.Catalogue.ExtendableSort.Category{} = ast) do
+ Map.put(
+ ast,
+ :children,
+ ast.children |> sort_children |> sort
+ )
+ end
+
+ def sort_children(ast) do
+ ast
+ end
+
+ @impl true
+ def sort(ast) do
+ Enum.sort_by(ast, fn p -> p.__struct__ end)
+ end
+end
diff --git a/lib/surface/catalogue/extendable_sort/builder.ex b/lib/surface/catalogue/extendable_sort/builder.ex
new file mode 100644
index 0000000..01f49e5
--- /dev/null
+++ b/lib/surface/catalogue/extendable_sort/builder.ex
@@ -0,0 +1,55 @@
+defmodule Surface.Catalogue.ExtendableSort.Builder do
+ alias Surface.Catalogue.ExtendableSort
+
+ def from_map(children) when is_list(children) do
+ Enum.map(children, fn x -> map_inspector(x) end) |> List.flatten()
+ end
+
+ def from_map(module) do
+ map_inspector(module)
+ end
+
+ def map_inspector(map) do
+ case {is_module?(map), has_children?(map)} do
+ # not module, has children
+ {false, true} ->
+ [cast_to_category(map)]
+
+ # is module, has children
+ {true, true} ->
+ [cast_to_module(map), cast_to_category(map)]
+
+ # is module, no children
+ {true, false} ->
+ [cast_to_module(map)]
+
+ # everything else would be pointless to include
+ {_, _} ->
+ []
+ end
+ end
+
+ defp cast_to_category(map) do
+ {_, new_map} =
+ map
+ # categories don't have modules
+ |> Map.drop([:module])
+ |> Map.get_and_update!(:children, fn current_value ->
+ {current_value, from_map(current_value)}
+ end)
+
+ struct(ExtendableSort.Category, new_map)
+ end
+
+ defp cast_to_module(map) do
+ # modules don't have children
+ new_map = map |> Map.drop([:children])
+ struct(ExtendableSort.Module, new_map)
+ end
+
+ defp is_module?(%{type: type}) when type != :none, do: true
+ defp is_module?(_), do: false
+
+ defp has_children?(%{children: [_head | _tail]}), do: true
+ defp has_children?(_), do: false
+end
diff --git a/lib/surface/catalogue/extendable_sort/category.ex b/lib/surface/catalogue/extendable_sort/category.ex
new file mode 100644
index 0000000..bc3feff
--- /dev/null
+++ b/lib/surface/catalogue/extendable_sort/category.ex
@@ -0,0 +1,3 @@
+defmodule Surface.Catalogue.ExtendableSort.Category do
+ defstruct [:name, :parent, :children]
+end
diff --git a/lib/surface/catalogue/extendable_sort/map_handler.ex b/lib/surface/catalogue/extendable_sort/map_handler.ex
new file mode 100644
index 0000000..ca8b3d3
--- /dev/null
+++ b/lib/surface/catalogue/extendable_sort/map_handler.ex
@@ -0,0 +1,32 @@
+defmodule Surface.Catalogue.ExtendableSort.MapHandler do
+ def to_extendable_sort(children, parent \\ "Root", parent_path \\ ["Root"]) do
+ for {key, value} <- Enum.sort(children) do
+ generated_chain = parent_path ++ [key]
+ module = generated_chain |> remove_root |> Module.concat()
+ type = module |> component_type
+
+ %{
+ name: key,
+ children: to_extendable_sort(value, key, generated_chain),
+ parent_path: parent_path,
+ module: module,
+ type: type,
+ parent: parent
+ }
+ end
+ end
+
+ defp remove_root(["Root" | rest]), do: rest
+ defp remove_root(rest), do: rest
+
+ defp component_type(module) do
+ with true <- function_exported?(module, :component_type, 0),
+ component_type = module.component_type(),
+ true <- component_type != Surface.LiveView do
+ component_type
+ else
+ _ ->
+ :none
+ end
+ end
+end
diff --git a/lib/surface/catalogue/extendable_sort/module.ex b/lib/surface/catalogue/extendable_sort/module.ex
new file mode 100644
index 0000000..79fd0b8
--- /dev/null
+++ b/lib/surface/catalogue/extendable_sort/module.ex
@@ -0,0 +1,3 @@
+defmodule Surface.Catalogue.ExtendableSort.Module do
+ defstruct [:name, :module, :children]
+end
diff --git a/lib/surface/catalogue/live/page_live.ex b/lib/surface/catalogue/live/page_live.ex
index 9c12f2e..aecfeba 100644
--- a/lib/surface/catalogue/live/page_live.ex
+++ b/lib/surface/catalogue/live/page_live.ex
@@ -12,7 +12,7 @@ defmodule Surface.Catalogue.PageLive do
data component_module, :module
data has_example?, :boolean
data has_playground?, :boolean
- data components, :map, default: %{}
+ data components, :any, default: []
data action, :string
data examples_and_playgrounds, :map, default: %{}
data examples, :list, default: []
@@ -29,13 +29,15 @@ defmodule Surface.Catalogue.PageLive do
if connected?(socket) do
{components, examples_and_playgrounds} = Util.get_components_info()
+ extendable_sort_components = components |> Surface.Catalogue.ExtendableSort.handle()
+
catalogues = Application.get_env(:surface_catalogue, :catalogues) || []
home_view = Application.get_env(:surface_catalogue, :home_view)
single_catalogue? = length(catalogues) == 1
socket
|> maybe_assign_window_id(params, session)
- |> assign(:components, components)
+ |> assign(:components, extendable_sort_components)
|> assign(:single_catalogue?, single_catalogue?)
|> assign(:home_view, home_view)
|> assign(:examples_and_playgrounds, examples_and_playgrounds)
@@ -76,7 +78,7 @@ defmodule Surface.Catalogue.PageLive do
-
{live_render(@socket, @home_view, id: "home_view")}
+