Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customisable catalogue sort #36

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/surface/catalogue/components/component_tree.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down
96 changes: 96 additions & 0 deletions lib/surface/catalogue/components/extendable_sort/item.ex
Original file line number Diff line number Diff line change
@@ -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}
<div></div>
{/for}
"""
end

def render(assigns, %ExtendableSort.Category{name: "Root"} = node) do
~F"""
<ul class={""}>
{render(assigns, node.children)}
</ul>
"""
end

def render(assigns, %ExtendableSort.Category{} = node) do
~F"""
<li>
<a href="#" onclick="toggleNode(this)">
<span class="icon">
<i class={:far, "fa-folder-open"}></i>
</span>
{node.name}
</a>
<ul>
{render(assigns, node.children)}
</ul>
</li>
"""
end

def render(assigns, %ExtendableSort.Module{} = node) do
~F"""
<li>
<LivePatch
to={@socket.router.__helpers__().live_path(@socket, Surface.Catalogue.PageLive, inspect(node.module))}>
<span class="icon">
<i class={"far fa-file-code"}></i>
</span> {node.name}
</LivePatch>
</li>
"""
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
21 changes: 21 additions & 0 deletions lib/surface/catalogue/components/extendable_sort/tree.ex
Original file line number Diff line number Diff line change
@@ -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"""
<div class={"menu-list"}>
{Surface.Catalogue.Components.ExtendableSort.Item.render(assigns, @components)}
</div>
"""
end
end
37 changes: 37 additions & 0 deletions lib/surface/catalogue/extendable_sort.ex
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions lib/surface/catalogue/extendable_sort/adapters.ex
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions lib/surface/catalogue/extendable_sort/adapters/by_catalogue_abc.ex
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions lib/surface/catalogue/extendable_sort/adapters/category_first.ex
Original file line number Diff line number Diff line change
@@ -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
55 changes: 55 additions & 0 deletions lib/surface/catalogue/extendable_sort/builder.ex
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions lib/surface/catalogue/extendable_sort/category.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule Surface.Catalogue.ExtendableSort.Category do
defstruct [:name, :parent, :children]
end
32 changes: 32 additions & 0 deletions lib/surface/catalogue/extendable_sort/map_handler.ex
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions lib/surface/catalogue/extendable_sort/module.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule Surface.Catalogue.ExtendableSort.Module do
defstruct [:name, :module, :children]
end
9 changes: 6 additions & 3 deletions lib/surface/catalogue/live/page_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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: []
Expand All @@ -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)
Expand Down Expand Up @@ -76,7 +78,7 @@ defmodule Surface.Catalogue.PageLive do
<div class="sidebar-bg"/>
<div class="container is-fullhd">
<section class="main-content columns">
<ComponentTree
<Surface.Catalogue.Components.ExtendableSort.Tree
id="component-tree"
components={@components}
selected_component={@component_name}
Expand All @@ -86,6 +88,7 @@ defmodule Surface.Catalogue.PageLive do
<div :if={!@component_module and @home_view}>
{live_render(@socket, @home_view, id: "home_view")}
</div>

<div :if={!@component_module and !@home_view} class="columns is-centered is-vcentered is-mobile" style="height: 300px">
<div class="column is-narrow has-text-centered subtitle has-text-grey">
No component selected
Expand Down