Files
bDS2/lib/bds/desktop/overlay.ex

338 lines
9.8 KiB
Elixir

defmodule BDS.Desktop.Overlay do
@moduledoc false
def open(:post, :ai_suggestions, context) do
%{
kind: :ai_suggestions,
title: Map.get(context, :ai_title, "AI Suggestions"),
fields: normalize_ai_fields(Map.get(context, :ai_fields, []))
}
end
def open(:media, :ai_suggestions, context), do: open(:post, :ai_suggestions, context)
def open(:post, :insert_link, context) do
posts = related_posts(Map.get(context, :posts, []), current_id(context))
%{
kind: :insert_link,
title: Map.get(context, :insert_link_title, "Insert Link"),
active_tab: :internal,
search_query: "",
external_url: "",
external_text: current_title(context),
results: [],
related_posts: Enum.map(Enum.take(posts, 5), &to_insert_link_result/1),
all_posts: posts
}
end
def open(:post, :insert_media, context) do
media = Map.get(context, :media, [])
%{
kind: :insert_media,
title: Map.get(context, :insert_media_title, "Insert Media"),
search_query: "",
results: Enum.map(media, &to_insert_media_result/1),
all_media: media
}
end
def open(:post, :language_picker, context) do
language_picker(context, Map.get(context, :current_post_language, "en"))
end
def open(:media, :language_picker, context) do
language_picker(context, Map.get(context, :current_media_language, "en"))
end
def open(:media, :confirm_delete, context) do
delete_details = Map.get(context, :delete_details, %{})
%{
kind: :confirm_delete,
title: Map.get(delete_details, :title, "Delete"),
entity_name: Map.get(delete_details, :entity_name, ""),
entity_type: Map.get(delete_details, :entity_type, "media"),
reference_count: length(Map.get(delete_details, :reference_list, [])),
reference_list: Map.get(delete_details, :reference_list, [])
}
end
def open(:tags, :confirm_delete, context), do: open(:media, :confirm_delete, context)
def open(:tags, :confirm_merge, context) do
merge = Map.get(context, :merge_details, %{})
target = Map.get(merge, :target, "")
count = Map.get(merge, :count, 0)
%{
kind: :confirm_dialog,
title: Map.get(merge, :title, "Merge #{count} tags into #{target}?"),
message: Map.get(merge, :message, "Cannot be undone.")
}
end
def open(:post, :gallery, context) do
images =
context
|> gallery_images()
|> Enum.map(&to_gallery_image/1)
%{
kind: :gallery,
title: Map.get(context, :gallery_title, current_title(context)),
post_id: current_id(context),
images: images,
lightbox: nil
}
end
def open(_route, _action, _context), do: nil
def set_search_query(%{kind: :insert_link} = overlay, query) do
normalized = normalize_query(query)
results =
if String.length(normalized) < 2 do
[]
else
overlay
|> Map.get(:all_posts, [])
|> Enum.filter(&search_matches?(&1.title, normalized))
|> Enum.map(&to_insert_link_result/1)
end
%{overlay | search_query: normalized, results: results}
end
def set_search_query(%{kind: :insert_media} = overlay, query) do
normalized = normalize_query(query)
results =
overlay
|> Map.get(:all_media, [])
|> Enum.filter(fn media ->
normalized == "" or
search_matches?(Map.get(media, :title, ""), normalized) or
search_matches?(Map.get(media, :original_name, ""), normalized)
end)
|> Enum.map(&to_insert_media_result/1)
%{overlay | search_query: normalized, results: results}
end
def set_search_query(overlay, _query), do: overlay
def set_active_tab(%{kind: :insert_link} = overlay, tab) when tab in [:internal, :external] do
%{overlay | active_tab: tab}
end
def set_active_tab(overlay, _tab), do: overlay
def update_form_value(%{kind: :insert_link} = overlay, :external_url, value) do
%{overlay | external_url: normalize_query(value)}
end
def update_form_value(%{kind: :insert_link} = overlay, :external_text, value) do
%{overlay | external_text: to_string(value || "")}
end
def update_form_value(overlay, _key, _value), do: overlay
def toggle_ai_field(%{kind: :ai_suggestions} = overlay, key) do
fields =
Enum.map(overlay.fields, fn field ->
if field.key == key and not field.locked do
%{field | accepted: not field.accepted}
else
field
end
end)
%{overlay | fields: fields}
end
def toggle_ai_field(overlay, _key), do: overlay
def select_gallery_image(%{kind: :gallery} = overlay, media_id) do
case Enum.find_index(overlay.images, &(&1.media_id == media_id)) do
nil -> overlay
index -> %{overlay | lightbox: lightbox_from_index(overlay.images, index)}
end
end
def select_gallery_image(overlay, _media_id), do: overlay
def close_lightbox(%{kind: :gallery} = overlay), do: %{overlay | lightbox: nil}
def close_lightbox(overlay), do: overlay
def lightbox_next(%{kind: :gallery, lightbox: lightbox, images: images} = overlay)
when is_map(lightbox) and images != [] do
next_index = rem(lightbox.current_index + 1, length(images))
%{overlay | lightbox: lightbox_from_index(images, next_index)}
end
def lightbox_next(overlay), do: overlay
def lightbox_previous(%{kind: :gallery, lightbox: lightbox, images: images} = overlay)
when is_map(lightbox) and images != [] do
next_index = rem(lightbox.current_index - 1 + length(images), length(images))
%{overlay | lightbox: lightbox_from_index(images, next_index)}
end
def lightbox_previous(overlay), do: overlay
def selected_ai_fields(%{kind: :ai_suggestions, fields: fields}) do
Enum.filter(fields, & &1.accepted)
end
def selected_ai_fields(_overlay), do: []
def insert_link_result(%{kind: :insert_link} = overlay, post_id) do
Enum.find(overlay.results ++ overlay.related_posts, &(&1.post_id == post_id))
end
def insert_link_result(_overlay, _post_id), do: nil
def insert_media_result(%{kind: :insert_media} = overlay, media_id) do
Enum.find(overlay.results, &(&1.media_id == media_id))
end
def insert_media_result(_overlay, _media_id), do: nil
defp language_picker(context, source_language) do
targets =
context
|> Map.get(:blog_languages, [])
|> Enum.uniq()
|> Enum.reject(&(&1 == source_language))
|> Enum.map(fn code ->
existing_status = Map.get(Map.get(context, :existing_translations, %{}), code)
%{
code: code,
name: Map.get(Map.get(context, :language_names, %{}), code, String.upcase(code)),
flag_emoji: Map.get(Map.get(context, :language_flags, %{}), code, code),
has_existing_translation: not is_nil(existing_status),
existing_status: existing_status
}
end)
%{
kind: :language_picker,
title: Map.get(context, :language_picker_title, "Translate"),
source_language: source_language,
available_targets: targets
}
end
def set_ai_suggestions(%{kind: :ai_suggestions} = overlay, suggestions) do
fields =
Enum.map(overlay.fields, fn field ->
case Map.get(suggestions, field.key) do
nil ->
field
value when is_binary(value) and value != "" ->
%{field | suggested_value: value, loading: false}
_other ->
field
end
end)
%{overlay | fields: fields}
end
def set_ai_suggestions(overlay, _suggestions), do: overlay
defp normalize_ai_fields(fields) do
Enum.map(fields, fn field ->
%{
key: to_string(Map.get(field, :key, "")),
label: Map.get(field, :label, ""),
current_value: Map.get(field, :current_value, ""),
suggested_value: Map.get(field, :suggested_value, ""),
accepted: not Map.get(field, :locked, false),
locked: Map.get(field, :locked, false),
loading: Map.get(field, :loading, false)
}
end)
end
defp current_id(context), do: get_in(context, [:current_tab, :id])
defp current_title(context), do: get_in(context, [:current_tab, :title]) || ""
defp related_posts(posts, current_post_id) do
Enum.reject(posts, &(&1.id == current_post_id))
end
defp gallery_images(context) do
images = Enum.filter(Map.get(context, :media, []), &Map.get(&1, :is_image, false))
post_media_ids = Map.get(context, :post_media_ids, [])
case Enum.filter(images, &(&1.id in post_media_ids)) do
[] -> images
linked -> linked
end
end
defp to_insert_link_result(post) do
%{
post_id: post.id,
title: post.title,
status: to_string(Map.get(post, :status, "draft")),
canonical_url: Map.get(post, :canonical_url, "/posts/#{post.id}"),
similarity_score: Map.get(post, :similarity_score)
}
end
defp to_insert_media_result(media) do
%{
media_id: media.id,
title: Map.get(media, :title, ""),
original_name: Map.get(media, :original_name, media.id),
is_image: Map.get(media, :is_image, false),
thumbnail_url: Map.get(media, :thumbnail_url)
}
end
defp to_gallery_image(media) do
%{
media_id: media.id,
thumbnail_url: Map.get(media, :thumbnail_url),
image_url: Map.get(media, :image_url, Map.get(media, :thumbnail_url)),
alt_text: Map.get(media, :alt_text),
title: Map.get(media, :title, Map.get(media, :original_name, media.id))
}
end
defp lightbox_from_index(images, index) do
image = Enum.at(images, index)
%{
current_index: index,
total_count: length(images),
media_id: image.media_id,
image_url: image.image_url,
alt_text: image.alt_text,
title: image.title
}
end
defp search_matches?(value, query) do
value
|> to_string()
|> String.downcase()
|> String.contains?(String.downcase(query))
end
defp normalize_query(value) do
value
|> to_string()
|> String.trim()
end
end