From c6f737415fd20f1cf419dc14fdc9d52a83af9ddb Mon Sep 17 00:00:00 2001 From: Emiel Date: Tue, 23 Jul 2024 21:44:13 +0200 Subject: [PATCH 01/23] #934 Add breadcrumbs to project and project items --- core/frameworks/concept/context.ex | 16 +++- core/frameworks/concept/special.ex | 75 +++++++++++++++++++ core/frameworks/pixel.ex | 1 + .../pixel/components/breadcrumbs.ex | 71 ++++++++++++++++++ .../pixel/components/button_face.ex | 8 +- .../frameworks/pixel/components/navigation.ex | 28 ++++--- core/frameworks/pixel/components/separator.ex | 21 ++++++ core/frameworks/pixel/components/tabbar.ex | 3 +- .../gettext/de/LC_MESSAGES/eyra-project.po | 4 + .../gettext/en/LC_MESSAGES/eyra-project.po | 4 + core/priv/gettext/eyra-project.pot | 4 + .../gettext/nl/LC_MESSAGES/eyra-project.po | 4 + .../static/images/icons/forward_grey2.svg | 3 + core/systems/admin/account_view.ex | 8 +- core/systems/advert/content_page.ex | 1 + core/systems/advert/content_page_builder.ex | 8 ++ core/systems/alliance/content_page.ex | 1 + core/systems/alliance/content_page_builder.ex | 1 + core/systems/assignment/content_page.ex | 1 + .../assignment/content_page_builder.ex | 8 +- core/systems/content/html.ex | 3 +- core/systems/document/content_page.ex | 1 + core/systems/document/content_page_builder.ex | 1 + core/systems/feldspar/content_page.ex | 1 + core/systems/feldspar/content_page_builder.ex | 1 + .../graphite/leaderboard_content_page.ex | 1 + .../leaderboard_content_page_builder.ex | 8 +- core/systems/lab/content_page.ex | 1 + core/systems/lab/content_page_builder.ex | 1 + core/systems/org/content_page.ex | 1 + core/systems/pool/detail_page.ex | 2 +- core/systems/project/_public.ex | 37 +++++++++ core/systems/project/item_model.ex | 5 +- core/systems/project/node_page.ex | 12 +++ core/systems/project/node_page_builder.ex | 6 +- core/systems/storage/_public.ex | 1 + core/systems/storage/controller.ex | 2 +- core/systems/storage/endpoint_content_page.ex | 1 + .../storage/endpoint_content_page_builder.ex | 10 ++- core/systems/storage/endpoint_form.ex | 5 +- core/systems/storage/endpoint_model.ex | 52 +------------ 41 files changed, 335 insertions(+), 87 deletions(-) create mode 100644 core/frameworks/concept/special.ex create mode 100644 core/frameworks/pixel/components/breadcrumbs.ex create mode 100644 core/frameworks/pixel/components/separator.ex create mode 100644 core/priv/static/images/icons/forward_grey2.svg diff --git a/core/frameworks/concept/context.ex b/core/frameworks/concept/context.ex index 06d4e18f1..80dffbe1a 100644 --- a/core/frameworks/concept/context.ex +++ b/core/frameworks/concept/context.ex @@ -2,18 +2,28 @@ defmodule Frameworks.Concept.Context do defmodule Handler do @type scope :: :self | :parent @type model :: struct() - @callback name(scope, model) :: {:ok, binary()} | {:error, atom()} + @callback name(model, scope) :: {:ok, binary()} | {:error, atom()} + @callback breadcrumbs(model) :: {:ok, list()} | {:error, atom()} end - def name(scope, model, default) when is_struct(model) and is_binary(default) do + def name(model, scope, default) when is_struct(model) and is_binary(default) do Enum.reduce(handlers(), default, fn handler, acc -> - case handler.name(scope, model) do + case handler.name(model, scope) do {:ok, name} -> name {:error, _} -> acc end end) end + def breadcrumbs(model) when is_struct(model) do + Enum.reduce(handlers(), [], fn handler, acc -> + case handler.breadcrumbs(model) do + {:ok, breadcrumbs} -> breadcrumbs + {:error, _} -> acc + end + end) + end + defp handlers() do Access.get(settings(), :handlers, []) end diff --git a/core/frameworks/concept/special.ex b/core/frameworks/concept/special.ex new file mode 100644 index 000000000..d12163b57 --- /dev/null +++ b/core/frameworks/concept/special.ex @@ -0,0 +1,75 @@ +defmodule Frameworks.Concept.Special do + import Ecto.Changeset + + def field_value(model, special_fields) do + if field = field(model, special_fields) do + Map.get(model, field) + else + nil + end + end + + def field_id(model, special_fields) do + if field = field(model, special_fields) do + map_to_field_id(field) + else + nil + end + end + + def field(model, special_fields) do + Enum.reduce(special_fields, nil, fn field, acc -> + field_id = map_to_field_id(field) + + if Map.get(model, field_id) != nil do + field + else + acc + end + end) + end + + def change(changeset, special_field, special, special_fields) when is_atom(special_field) do + specials = + Enum.map( + special_fields, + &{&1, + if &1 == special_field do + special + else + nil + end} + ) + + changeset + |> then( + &Enum.reduce(specials, &1, fn {field, value}, changeset -> + put_assoc(changeset, field, value) + end) + ) + end + + defp map_to_field_id(field), do: String.to_existing_atom("#{field}_id") + + defmacro __using__(special_fields) do + quote do + alias Frameworks.Concept.Special + + def special(model) do + Special.field_value(model, unquote(special_fields)) + end + + def special_field_id(model) do + Special.field_id(model, unquote(special_fields)) + end + + def special_field(model) do + Special.field(model, unquote(special_fields)) + end + + def change_special(changeset, special_field, special) do + Special.change(changeset, special_field, special, unquote(special_fields)) + end + end + end +end diff --git a/core/frameworks/pixel.ex b/core/frameworks/pixel.ex index bd6388fe3..a2adbd722 100644 --- a/core/frameworks/pixel.ex +++ b/core/frameworks/pixel.ex @@ -8,6 +8,7 @@ defmodule Frameworks.Pixel do alias Frameworks.Pixel.Button alias Frameworks.Pixel.Text alias Frameworks.Pixel.Icon + alias Frameworks.Pixel.Separator end end end diff --git a/core/frameworks/pixel/components/breadcrumbs.ex b/core/frameworks/pixel/components/breadcrumbs.ex new file mode 100644 index 000000000..a12f61cc1 --- /dev/null +++ b/core/frameworks/pixel/components/breadcrumbs.ex @@ -0,0 +1,71 @@ +defmodule Frameworks.Pixel.Breadcrumbs do + use CoreWeb, :live_component + + @impl true + def update(%{elements: elements}, %{assigns: %{}} = socket) do + { + :ok, + socket + |> assign(elements: elements) + |> update_blocks() + } + end + + defp update_blocks(%{assigns: %{elements: elements}} = socket) do + count = Enum.count(elements) + + blocks = + elements + |> Enum.with_index() + |> Enum.map(fn {element, index} -> map_to_block(element, index + 1 == count) end) + |> Enum.intersperse({:separator, %{type: :forward}}) + + assign(socket, blocks: blocks) + end + + defp map_to_block(%{label: label, path: path}, last?) do + { + :button, + %{ + face: %{ + type: :plain, + label: label, + text_color: + if last? do + "text-primary" + else + "text-grey2" + end + }, + action: %{type: :send, event: "handle_click", item: path} + } + } + end + + @impl true + def handle_event("handle_click", %{"item" => path}, socket) do + {:noreply, socket |> push_navigate(to: path)} + end + + @impl true + def render(assigns) do + ~H""" +
+
+ <%= for {type, value} <- @blocks do %> + <%= if type == :separator do %> +
+ +
+ <% end %> + <%= if type == :button do %> +
+ +
+ <% end %> + <% end %> +
+
+ """ + end +end diff --git a/core/frameworks/pixel/components/button_face.ex b/core/frameworks/pixel/components/button_face.ex index b843d860b..42ff058f8 100644 --- a/core/frameworks/pixel/components/button_face.ex +++ b/core/frameworks/pixel/components/button_face.ex @@ -100,10 +100,10 @@ defmodule Frameworks.Pixel.Button.Face do def plain(assigns) do ~H""" -
-
-
-
+
+
+
+
<%= @label %>
diff --git a/core/frameworks/pixel/components/navigation.ex b/core/frameworks/pixel/components/navigation.ex index d6efa30e3..25d9d54d9 100644 --- a/core/frameworks/pixel/components/navigation.ex +++ b/core/frameworks/pixel/components/navigation.ex @@ -5,6 +5,7 @@ defmodule Frameworks.Pixel.Navigation do alias Frameworks.Pixel.Button alias Frameworks.Pixel.Menu alias Frameworks.Pixel.Align + alias Frameworks.Pixel.Breadcrumbs import Frameworks.Pixel.Line @@ -60,6 +61,7 @@ defmodule Frameworks.Pixel.Navigation do """ end + attr(:breadcrumbs, :list, required: true) attr(:right_bar_buttons, :list, default: []) attr(:more_buttons, :list, default: []) attr(:hide_seperator, :boolean, default: true) @@ -76,7 +78,17 @@ defmodule Frameworks.Pixel.Navigation do -
+
+
@@ -84,17 +96,9 @@ defmodule Frameworks.Pixel.Navigation do <%= render_slot(@inner_block) %>
<%= if @has_right_bar_buttons do %> - <%= if not @hide_seperator do %> -
- -
- <% end %> -
-
- <%= for button <- @right_bar_buttons do %> - - <% end %> -
+
+
+
<% end %>
diff --git a/core/frameworks/pixel/components/separator.ex b/core/frameworks/pixel/components/separator.ex new file mode 100644 index 000000000..d56f80aaa --- /dev/null +++ b/core/frameworks/pixel/components/separator.ex @@ -0,0 +1,21 @@ +defmodule Frameworks.Pixel.Separator do + use CoreWeb, :html + + attr(:type, :atom, required: true) + + def dynamic(assigns) do + ~H""" + <%= if @type == :forward do %> + <.forward /> + <% end %> + """ + end + + def forward(assigns) do + ~H""" +
+ +
+ """ + end +end diff --git a/core/frameworks/pixel/components/tabbar.ex b/core/frameworks/pixel/components/tabbar.ex index 0754d85b2..f50ca4077 100644 --- a/core/frameworks/pixel/components/tabbar.ex +++ b/core/frameworks/pixel/components/tabbar.ex @@ -123,6 +123,7 @@ defmodule Frameworks.Pixel.Tabbar do ~H"""
<%= if @include_top_margin do %> +