defmodule BDS.Desktop.ShellLive.SidebarComponents do
@moduledoc false
use Phoenix.Component
alias BDS.Desktop.ShellData
alias BDS.Desktop.UILocale
alias BDS.UI.Registry
use Gettext, backend: BDS.Gettext
def sidebar_content(assigns) do
UILocale.put(assigns.page_language)
assigns = prepare_filter_assigns(assigns)
~H"""
<%= render_sidebar_filters(assigns) %>
<%= render_sidebar_body(assigns) %>
<%= render_sidebar_load_more(assigns) %>
"""
end
def filters_enabled?(sidebar_data) do
sidebar_data
|> Map.get(:filters)
|> then(&(is_map(&1) and Map.get(&1, :enabled, false)))
end
def filters_visible?(sidebar_data) do
sidebar_data
|> Map.get(:filters, %{})
|> Map.get(:filter_panel_visible, false)
end
defp prepare_filter_assigns(assigns) do
filters = Map.get(assigns.sidebar_data, :filters)
if is_map(filters) and Map.get(filters, :enabled) do
selected = Map.get(filters, :selected, %{})
assigns
|> assign(:sidebar_filters_config, filters)
|> assign(:selected_filters, selected)
|> assign(:filter_panel_visible, Map.get(filters, :filter_panel_visible, false))
|> assign(:archive_collapsed, Map.get(filters, :archive_collapsed, true))
|> assign(:tags_collapsed, Map.get(filters, :tags_collapsed, true))
|> assign(:categories_collapsed, Map.get(filters, :categories_collapsed, true))
|> assign(:expanded_year, Map.get(filters, :expanded_year))
|> assign(:year_groups, group_year_month_counts(Map.get(filters, :year_month_counts, [])))
else
assigns
end
end
defp render_sidebar_filters(assigns) do
filters = Map.get(assigns.sidebar_data, :filters)
if is_map(filters) and Map.get(filters, :enabled) do
~H"""
<%= if Map.get(@sidebar_filters_config, :has_active_filters) do %>
<%= @sidebar_filters_config.results_label %>: <%= @sidebar_filters_config.loaded_count %>/<%= @sidebar_filters_config.total_count %>
<%= @sidebar_filters_config.clear_filters_label %>
<% end %>
<%= if @filter_panel_visible do %>
<%= if Enum.any?(@year_groups) do %>
<%= if @archive_collapsed, do: "▶", else: "▼" %>
<%= @sidebar_filters_config.archive_label %>
<%= if Map.get(@selected_filters, :year) do %>
✕
<% end %>
<%= unless @archive_collapsed do %>
<%= for year_group <- @year_groups do %>
<%= if @expanded_year == year_group.year, do: "▼", else: "▶" %>
<%= year_group.year %>
<%= year_group.count %>
<%= if @expanded_year == year_group.year do %>
<%= for month_entry <- year_group.months do %>
<%= ShellData.format_dashboard_month(year_group.year, month_entry.month) %>
<%= month_entry.count %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<%= if Enum.any?(Map.get(@sidebar_filters_config, :available_tags, [])) do %>
<%= if @tags_collapsed, do: "▶", else: "▼" %>
<%= @sidebar_filters_config.tags_label %>
<%= if Enum.any?(Map.get(@selected_filters, :tags, [])) do %>
✕
<% end %>
<%= unless @tags_collapsed do %>
<%= for tag <- Map.get(@sidebar_filters_config, :available_tags, []) do %>
<%= tag %>
<% end %>
<% end %>
<% end %>
<%= if Enum.any?(Map.get(@sidebar_filters_config, :available_categories, [])) do %>
<%= if @categories_collapsed, do: "▶", else: "▼" %>
<%= @sidebar_filters_config.categories_label %>
<%= if Enum.any?(Map.get(@selected_filters, :categories, [])) do %>
✕
<% end %>
<%= unless @categories_collapsed do %>
<%= for category <- Map.get(@sidebar_filters_config, :available_categories, []) do %>
<%= category %>
<% end %>
<% end %>
<% end %>
<% end %>
"""
else
~H"""
"""
end
end
defp render_sidebar_load_more(assigns) do
filters = Map.get(assigns.sidebar_data, :filters, %{})
if Map.get(filters, :has_more) do
~H"""
"""
else
~H"""
"""
end
end
defp render_sidebar_body(assigns) do
case assigns.sidebar_data.layout do
"post_list" -> render_post_sidebar(assigns)
"media_grid" -> render_media_sidebar(assigns)
"entity_list" -> render_entity_sidebar(assigns)
"nav_list" -> render_nav_sidebar(assigns)
_other -> render_default_sidebar(assigns)
end
end
defp render_post_sidebar(assigns) do
~H"""
<%= for section <- Map.get(@sidebar_data, :sections, []) do %>
<% end %>
<%= if Enum.empty?(Map.get(@sidebar_data, :sections, [])) do %>
<% end %>
"""
end
defp render_media_sidebar(assigns) do
~H"""
<%= if Enum.any?(Map.get(@sidebar_data, :items, [])) do %>
<% else %>
<% end %>
"""
end
defp render_entity_sidebar(assigns) do
~H"""
<%= if Enum.any?(Map.get(@sidebar_data, :items, [])) do %>
<% else %>
<% end %>
"""
end
defp render_nav_sidebar(assigns) do
~H"""
<%= for item <- Map.get(@sidebar_data, :items, []) do %>
<%= Map.get(item, :icon, "") %>
<%= item.title %>
<% end %>
"""
end
defp render_default_sidebar(assigns) do
~H"""
<%= for section <- Map.get(@sidebar_data, :sections, []) do %>
<% end %>
"""
end
defp sidebar_deletable?(route),
do: route in ["post", "media", "scripts", "templates", "chat", "import"]
defp sidebar_delete_testid("post"), do: "sidebar-delete-post"
defp sidebar_delete_testid("media"), do: "sidebar-delete-media"
defp sidebar_delete_testid("scripts"), do: "sidebar-delete-script"
defp sidebar_delete_testid("templates"), do: "sidebar-delete-template"
defp sidebar_delete_testid("chat"), do: "sidebar-delete-chat"
defp sidebar_delete_testid("import"), do: "sidebar-delete-import"
defp sidebar_delete_testid(route), do: "sidebar-delete-#{route}"
defp sidebar_delete_title("chat"), do: dgettext("ui", "Delete conversation")
defp sidebar_delete_title("post"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Post")
defp sidebar_delete_title("media"),
do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Media")
defp sidebar_delete_title("scripts"),
do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Script")
defp sidebar_delete_title("templates"),
do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Template")
defp sidebar_delete_title("import"),
do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Import")
defp sidebar_delete_title(_route), do: dgettext("ui", "Delete")
defp template_sidebar?(sidebar_data), do: Map.get(sidebar_data, :title) == "Templates"
defp group_year_month_counts(entries) do
entries
|> Enum.group_by(& &1.year)
|> Enum.map(fn {year, months} ->
%{
year: year,
count: Enum.reduce(months, 0, fn entry, acc -> acc + (entry.count || 0) end),
months: Enum.sort_by(months, &(-&1.month))
}
end)
|> Enum.sort_by(&(-&1.year))
end
defp sidebar_filter_tag_color(filters_config, tag) do
filters_config
|> Map.get(:available_tag_colors, %{})
|> Map.get(tag)
|> normalize_sidebar_filter_color()
end
defp sidebar_filter_chip_style(filters_config, tag) do
case sidebar_filter_tag_color(filters_config, tag) do
nil ->
nil
color ->
"background-color: #{color}; color: #{sidebar_filter_contrast_color(color)}; border-color: #{color};"
end
end
defp normalize_sidebar_filter_color(nil), do: nil
defp normalize_sidebar_filter_color(""), do: nil
defp normalize_sidebar_filter_color("#" <> rest = color) when byte_size(rest) == 6 do
if String.match?(rest, ~r/\A[0-9a-fA-F]{6}\z/), do: color, else: nil
end
defp normalize_sidebar_filter_color(_color), do: nil
defp sidebar_filter_contrast_color("#" <> rgb) do
<> = rgb
{red, _} = Integer.parse(r, 16)
{green, _} = Integer.parse(g, 16)
{blue, _} = Integer.parse(b, 16)
luminance = (red * 299 + green * 587 + blue * 114) / 1000
if luminance > 150, do: "#1e1e1e", else: "#ffffff"
end
defp sidebar_filter_contrast_color(_color), do: "#ffffff"
defp format_sidebar_timestamp(nil), do: ""
defp format_sidebar_timestamp(timestamp) do
timestamp
|> DateTime.from_unix!(:millisecond)
|> Calendar.strftime("%x")
end
defp image_media?(item), do: String.starts_with?(to_string(item.mime_type || ""), "image/")
defp media_thumbnail_class(item) do
if image_media?(item), do: "media-thumbnail has-image", else: "media-thumbnail"
end
defp media_thumbnail_glyph(mime_type) do
case String.split(to_string(mime_type || ""), "/", parts: 2) do
["image", _rest] -> "IMG"
["video", _rest] -> "VID"
["audio", _rest] -> "AUD"
["application", _rest] -> "DOC"
_other -> "FILE"
end
end
defp sidebar_item_selected?(workbench, route, id) do
route_atom = sidebar_route_atom(route)
workbench.active_tab == {route_atom, tab_id_for_route(route_atom, id)}
end
defp sidebar_route_atom(route) when is_atom(route), do: route
defp sidebar_route_atom(route) when is_binary(route),
do: BDS.BoundedAtoms.editor_route(route, :dashboard)
defp tab_id_for_route(route, id) do
case Registry.editor_route(route) do
%{singleton: true} -> Atom.to_string(route)
_other -> id
end
end
end