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

353 lines
9.6 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,
post_id: current_id(context)
}
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
%{
title: title,
entity_name: entity_name,
entity_type: entity_type,
reference_list: reference_list
} = context.delete_details
%{
kind: :confirm_delete,
title: title,
entity_name: entity_name,
entity_type: entity_type,
reference_count: length(reference_list),
reference_list: reference_list
}
end
def open(:tags, :confirm_delete, context), do: open(:media, :confirm_delete, context)
def open(:tags, :confirm_merge, context) do
%{title: title, message: message} = context.merge_details
%{
kind: :confirm_dialog,
title: title,
message: message
}
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?(media.title, normalized) or
search_matches?(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
existing_translations = Map.get(context, :existing_translations, %{})
language_names = Map.get(context, :language_names, %{})
language_flags = Map.get(context, :language_flags, %{})
targets =
context
|> Map.get(:blog_languages, [])
|> Enum.uniq()
|> Enum.reject(&(&1 == source_language))
|> Enum.map(fn code ->
existing_status = Map.get(existing_translations, code)
%{
code: code,
name: Map.get(language_names, code, String.upcase(code)),
flag_emoji: Map.get(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
def set_ai_suggestions_error(%{kind: :ai_suggestions} = overlay, error_message) do
Map.put(overlay, :error, error_message)
end
def set_ai_suggestions_error(overlay, _error_message), do: overlay
defp normalize_ai_fields(fields) do
Enum.map(fields, fn %{key: key, label: label, current_value: current,
suggested_value: suggested, locked: locked} = field ->
%{
key: to_string(key),
label: label,
current_value: current,
suggested_value: suggested,
accepted: not locked,
locked: locked,
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, []), & &1.is_image)
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: post.status,
canonical_url: post.canonical_url,
similarity_score: nil
}
end
defp to_insert_media_result(media) do
%{
media_id: media.id,
title: media.title,
original_name: media.original_name,
is_image: media.is_image,
thumbnail_url: media.thumbnail_url
}
end
defp to_gallery_image(media) do
%{
media_id: media.id,
thumbnail_url: media.thumbnail_url,
image_url: media.image_url,
alt_text: media.alt_text,
title: media.title
}
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