feat: delete buttons on sidebar entries
This commit is contained in:
@@ -5,7 +5,7 @@ defmodule BDS.Desktop.ShellLive do
|
||||
|
||||
import Phoenix.HTML
|
||||
|
||||
alias BDS.{AI, BoundedAtoms}
|
||||
alias BDS.{AI, BoundedAtoms, ImportDefinitions, Media, Posts, Scripts}
|
||||
alias BDS.CliSync.Watcher
|
||||
alias BDS.Desktop.{FolderPicker, Overlay, ShellData, UILocale}
|
||||
|
||||
@@ -419,67 +419,12 @@ defmodule BDS.Desktop.ShellLive do
|
||||
|> reload_shell(workbench)}
|
||||
end
|
||||
|
||||
def handle_event("delete_sidebar_template", %{"id" => template_id}, socket) do
|
||||
case Templates.get_template(template_id) do
|
||||
%Templates.Template{project_id: project_id}
|
||||
when project_id == socket.assigns.projects.active_project_id ->
|
||||
case Templates.delete_template(template_id) do
|
||||
{:ok, :deleted} ->
|
||||
workbench = Workbench.close_tab(socket.assigns.workbench, :templates, template_id)
|
||||
tab_meta = Map.delete(socket.assigns.tab_meta, {:templates, template_id})
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:tab_meta, tab_meta)
|
||||
|> reload_shell(workbench)}
|
||||
|
||||
{:error, reason} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> append_output_entry(
|
||||
translated("Delete") <> " " <> translated("Template"),
|
||||
inspect(reason),
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> reload_shell(socket.assigns.workbench)}
|
||||
end
|
||||
|
||||
_other ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> append_output_entry(
|
||||
translated("Delete") <> " " <> translated("Template"),
|
||||
inspect(:not_found),
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> reload_shell(socket.assigns.workbench)}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("delete_sidebar_chat", %{"id" => conversation_id}, socket) do
|
||||
case AI.delete_chat_conversation(conversation_id) do
|
||||
{:ok, :deleted} ->
|
||||
workbench = Workbench.close_tab(socket.assigns.workbench, :chat, conversation_id)
|
||||
tab_meta = Map.delete(socket.assigns.tab_meta, {:chat, conversation_id})
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:tab_meta, tab_meta)
|
||||
|> reload_shell(workbench)}
|
||||
|
||||
{:error, reason} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> append_output_entry(
|
||||
translated("sidebar.chat.deleteConversation"),
|
||||
inspect(reason),
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> reload_shell(socket.assigns.workbench)}
|
||||
end
|
||||
def handle_event(
|
||||
"confirm_sidebar_delete",
|
||||
%{"route" => route, "id" => id} = params,
|
||||
socket
|
||||
) do
|
||||
{:noreply, request_sidebar_delete(socket, route, id, Map.get(params, "title"))}
|
||||
end
|
||||
|
||||
def handle_event("toggle_offline_mode", _params, socket) do
|
||||
@@ -1348,6 +1293,9 @@ defmodule BDS.Desktop.ShellLive do
|
||||
|
||||
socket =
|
||||
case {socket.assigns[:shell_overlay], current_tab} do
|
||||
{%{kind: :confirm_delete, delete_action: %{source: :sidebar, route: route, id: id}}, _tab} ->
|
||||
execute_sidebar_delete(socket, route, id)
|
||||
|
||||
{%{kind: :ai_suggestions} = overlay, %{type: :post, id: post_id}} ->
|
||||
PostEditor.apply_ai_suggestions(
|
||||
socket,
|
||||
@@ -1926,4 +1874,247 @@ defmodule BDS.Desktop.ShellLive do
|
||||
|> append_output_entry(title, translated("Command completed"), details)
|
||||
|> assign(:shell_overlay, nil)
|
||||
end
|
||||
|
||||
defp request_sidebar_delete(socket, route, id, fallback_title) do
|
||||
case sidebar_delete_target(socket, route, id, fallback_title) do
|
||||
{:ok, entity_name} ->
|
||||
assign(socket, :shell_overlay, %{
|
||||
kind: :confirm_delete,
|
||||
title: sidebar_delete_title(route),
|
||||
entity_name: entity_name,
|
||||
entity_type: route,
|
||||
reference_count: 0,
|
||||
reference_list: [],
|
||||
delete_action: %{source: :sidebar, route: route, id: id}
|
||||
})
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> append_output_entry(sidebar_delete_title(route), inspect(reason), nil, "error")
|
||||
|> reload_shell(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
defp execute_sidebar_delete(socket, route, id) do
|
||||
case route do
|
||||
"post" ->
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> PostEditor.delete_socket(id, &reload_shell/2, &append_output_entry/5)
|
||||
|
||||
"media" ->
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> MediaEditor.delete_socket(id, &reload_shell/2, &append_output_entry/5)
|
||||
|
||||
"scripts" ->
|
||||
delete_sidebar_script(socket, id)
|
||||
|
||||
"templates" ->
|
||||
delete_sidebar_template(socket, id)
|
||||
|
||||
"chat" ->
|
||||
delete_sidebar_chat(socket, id)
|
||||
|
||||
"import" ->
|
||||
delete_sidebar_import(socket, id)
|
||||
|
||||
_other ->
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> append_output_entry(translated("Delete"), inspect(:unsupported_route), nil, "error")
|
||||
|> reload_shell(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_sidebar_script(socket, script_id) do
|
||||
case Scripts.delete_script(script_id) do
|
||||
{:ok, :deleted} ->
|
||||
workbench = Workbench.close_tab(socket.assigns.workbench, :scripts, script_id)
|
||||
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {:scripts, script_id}))
|
||||
|> assign(:script_editor_drafts, Map.delete(socket.assigns.script_editor_drafts, script_id))
|
||||
|> reload_shell(workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> append_output_entry(sidebar_delete_title("scripts"), inspect(reason), nil, "error")
|
||||
|> reload_shell(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_sidebar_template(socket, template_id) do
|
||||
case Templates.delete_template(template_id, force: true) do
|
||||
{:ok, :deleted} ->
|
||||
workbench = Workbench.close_tab(socket.assigns.workbench, :templates, template_id)
|
||||
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {:templates, template_id}))
|
||||
|> assign(
|
||||
:template_editor_drafts,
|
||||
Map.delete(socket.assigns.template_editor_drafts, template_id)
|
||||
)
|
||||
|> reload_shell(workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> append_output_entry(sidebar_delete_title("templates"), inspect(reason), nil, "error")
|
||||
|> reload_shell(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_sidebar_chat(socket, conversation_id) do
|
||||
case AI.delete_chat_conversation(conversation_id) do
|
||||
{:ok, :deleted} ->
|
||||
workbench = Workbench.close_tab(socket.assigns.workbench, :chat, conversation_id)
|
||||
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {:chat, conversation_id}))
|
||||
|> reload_shell(workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> append_output_entry(sidebar_delete_title("chat"), inspect(reason), nil, "error")
|
||||
|> reload_shell(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_sidebar_import(socket, definition_id) do
|
||||
case ImportDefinitions.delete_definition(definition_id) do
|
||||
{:ok, :deleted} ->
|
||||
workbench = Workbench.close_tab(socket.assigns.workbench, :import, definition_id)
|
||||
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {:import, definition_id}))
|
||||
|> clear_import_editor_state(definition_id)
|
||||
|> reload_shell(workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> append_output_entry(sidebar_delete_title("import"), inspect(reason), nil, "error")
|
||||
|> reload_shell(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
defp clear_import_editor_state(socket, definition_id) do
|
||||
socket
|
||||
|> assign(
|
||||
:import_editor_analysis_states,
|
||||
Map.delete(socket.assigns.import_editor_analysis_states, definition_id)
|
||||
)
|
||||
|> assign(
|
||||
:import_editor_analysis_task_refs,
|
||||
Map.delete(socket.assigns.import_editor_analysis_task_refs, definition_id)
|
||||
)
|
||||
|> assign(
|
||||
:import_editor_execution_states,
|
||||
Map.delete(socket.assigns.import_editor_execution_states, definition_id)
|
||||
)
|
||||
|> assign(
|
||||
:import_editor_execution_task_refs,
|
||||
Map.delete(socket.assigns.import_editor_execution_task_refs, definition_id)
|
||||
)
|
||||
|> assign(:import_editor_sections, Map.delete(socket.assigns.import_editor_sections, definition_id))
|
||||
|> assign(
|
||||
:import_editor_taxonomy_edits,
|
||||
Map.delete(socket.assigns.import_editor_taxonomy_edits, definition_id)
|
||||
)
|
||||
|> assign(
|
||||
:import_editor_model_selectors_open,
|
||||
Map.delete(socket.assigns.import_editor_model_selectors_open, definition_id)
|
||||
)
|
||||
|> assign(
|
||||
:import_editor_selected_models,
|
||||
Map.delete(socket.assigns.import_editor_selected_models, definition_id)
|
||||
)
|
||||
end
|
||||
|
||||
defp sidebar_delete_target(socket, route, id, fallback_title) do
|
||||
active_project_id = socket.assigns.projects.active_project_id
|
||||
|
||||
case route do
|
||||
"post" ->
|
||||
case Posts.get_post(id) do
|
||||
%{project_id: ^active_project_id} = post ->
|
||||
{:ok, present_title(fallback_title) || present_title(post.title) || present_title(post.slug) || id}
|
||||
|
||||
_other ->
|
||||
{:error, :not_found}
|
||||
end
|
||||
|
||||
"media" ->
|
||||
case Media.get_media(id) do
|
||||
%{project_id: ^active_project_id} = media ->
|
||||
{:ok,
|
||||
present_title(fallback_title) || present_title(media.title) ||
|
||||
present_title(media.original_name) || id}
|
||||
|
||||
_other ->
|
||||
{:error, :not_found}
|
||||
end
|
||||
|
||||
"scripts" ->
|
||||
case Scripts.get_script(id) do
|
||||
%{project_id: ^active_project_id} = script ->
|
||||
{:ok, present_title(fallback_title) || present_title(script.title) || id}
|
||||
|
||||
_other ->
|
||||
{:error, :not_found}
|
||||
end
|
||||
|
||||
"templates" ->
|
||||
case Templates.get_template(id) do
|
||||
%{project_id: ^active_project_id} = template ->
|
||||
{:ok, present_title(fallback_title) || present_title(template.title) || id}
|
||||
|
||||
_other ->
|
||||
{:error, :not_found}
|
||||
end
|
||||
|
||||
"chat" ->
|
||||
case AI.get_chat_conversation(id) do
|
||||
%{title: title} -> {:ok, present_title(fallback_title) || present_title(title) || id}
|
||||
_other -> {:error, :not_found}
|
||||
end
|
||||
|
||||
"import" ->
|
||||
case ImportDefinitions.get_definition(id) do
|
||||
%{project_id: ^active_project_id} = definition ->
|
||||
{:ok, present_title(fallback_title) || present_title(definition.name) || id}
|
||||
|
||||
_other ->
|
||||
{:error, :not_found}
|
||||
end
|
||||
|
||||
_other ->
|
||||
{:error, :unsupported_route}
|
||||
end
|
||||
end
|
||||
|
||||
defp sidebar_delete_title("chat"), do: translated("sidebar.chat.deleteConversation")
|
||||
defp sidebar_delete_title("post"), do: translated("Delete") <> " " <> translated("Post")
|
||||
defp sidebar_delete_title("media"), do: translated("Delete") <> " " <> translated("Media")
|
||||
defp sidebar_delete_title("scripts"), do: translated("Delete") <> " " <> translated("Script")
|
||||
defp sidebar_delete_title("templates"), do: translated("Delete") <> " " <> translated("Template")
|
||||
defp sidebar_delete_title("import"), do: translated("Delete") <> " " <> translated("Import")
|
||||
defp sidebar_delete_title(_route), do: translated("Delete")
|
||||
|
||||
defp present_title(value) when is_binary(value) do
|
||||
case String.trim(value) do
|
||||
"" -> nil
|
||||
trimmed -> trimmed
|
||||
end
|
||||
end
|
||||
|
||||
defp present_title(_value), do: nil
|
||||
end
|
||||
|
||||
@@ -271,28 +271,45 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
|
||||
</div>
|
||||
<div class="sidebar-list">
|
||||
<%= for item <- Map.get(section, :items, []) do %>
|
||||
<button
|
||||
class={["sidebar-item", "sidebar-post-item", "post-type-post", if(sidebar_item_selected?(@workbench, item.route, item.id), do: "selected")]}
|
||||
data-testid="sidebar-open-item"
|
||||
data-route={item.route}
|
||||
data-item-id={item.id}
|
||||
data-open-title={item.title}
|
||||
data-open-subtitle={format_sidebar_timestamp(item.meta_timestamp)}
|
||||
type="button"
|
||||
phx-click="open_sidebar_item"
|
||||
phx-value-route={item.route}
|
||||
phx-value-id={item.id}
|
||||
phx-value-title={item.title}
|
||||
phx-value-subtitle={format_sidebar_timestamp(item.meta_timestamp)}
|
||||
>
|
||||
<span class="post-type-icon" title="post">●</span>
|
||||
<span class="sidebar-item-content">
|
||||
<span class="sidebar-item-title-row">
|
||||
<span class="sidebar-item-title"><%= item.title %></span>
|
||||
<div class="sidebar-item-row" data-item-id={item.id}>
|
||||
<button
|
||||
class={["sidebar-item", "sidebar-post-item", "post-type-post", if(sidebar_item_selected?(@workbench, item.route, item.id), do: "selected")]}
|
||||
data-testid="sidebar-open-item"
|
||||
data-route={item.route}
|
||||
data-item-id={item.id}
|
||||
data-open-title={item.title}
|
||||
data-open-subtitle={format_sidebar_timestamp(item.meta_timestamp)}
|
||||
type="button"
|
||||
phx-click="open_sidebar_item"
|
||||
phx-value-route={item.route}
|
||||
phx-value-id={item.id}
|
||||
phx-value-title={item.title}
|
||||
phx-value-subtitle={format_sidebar_timestamp(item.meta_timestamp)}
|
||||
>
|
||||
<span class="post-type-icon" title="post">●</span>
|
||||
<span class="sidebar-item-content">
|
||||
<span class="sidebar-item-title-row">
|
||||
<span class="sidebar-item-title"><%= item.title %></span>
|
||||
</span>
|
||||
<span class="sidebar-item-meta"><%= format_sidebar_timestamp(item.meta_timestamp) %></span>
|
||||
</span>
|
||||
<span class="sidebar-item-meta"><%= format_sidebar_timestamp(item.meta_timestamp) %></span>
|
||||
</span>
|
||||
</button>
|
||||
</button>
|
||||
<%= if sidebar_deletable?(item.route) do %>
|
||||
<button
|
||||
class="sidebar-delete-button"
|
||||
data-testid={sidebar_delete_testid(item.route)}
|
||||
data-item-id={item.id}
|
||||
type="button"
|
||||
title={sidebar_delete_title(item.route)}
|
||||
phx-click="confirm_sidebar_delete"
|
||||
phx-value-route={item.route}
|
||||
phx-value-id={item.id}
|
||||
phx-value-title={item.title}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
@@ -310,34 +327,51 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
|
||||
<%= if Enum.any?(Map.get(@sidebar_data, :items, [])) do %>
|
||||
<div class="sidebar-list media-grid">
|
||||
<%= for item <- Map.get(@sidebar_data, :items, []) do %>
|
||||
<button
|
||||
class={["media-item", if(sidebar_item_selected?(@workbench, item.route, item.id), do: "selected")]}
|
||||
data-testid="sidebar-open-item"
|
||||
data-route={item.route}
|
||||
data-item-id={item.id}
|
||||
data-open-title={item.title}
|
||||
data-open-subtitle={item.meta}
|
||||
type="button"
|
||||
title={item.title}
|
||||
phx-click="open_sidebar_item"
|
||||
phx-value-route={item.route}
|
||||
phx-value-id={item.id}
|
||||
phx-value-title={item.title}
|
||||
phx-value-subtitle={item.meta}
|
||||
>
|
||||
<span class={media_thumbnail_class(item)}>
|
||||
<%= if image_media?(item) do %>
|
||||
<span class="media-thumbnail-fallback"><%= media_thumbnail_glyph(item.mime_type) %></span>
|
||||
<img class="media-thumbnail-image" src={"/media-thumbnail/#{item.id}"} alt="" loading="lazy" decoding="async" />
|
||||
<% else %>
|
||||
<span class="media-thumbnail-fallback"><%= media_thumbnail_glyph(item.mime_type) %></span>
|
||||
<% end %>
|
||||
</span>
|
||||
<span class="media-item-info">
|
||||
<span class="media-item-name"><%= item.title %></span>
|
||||
<span class="media-item-size"><%= item.meta %></span>
|
||||
</span>
|
||||
</button>
|
||||
<div class="media-item-row" data-item-id={item.id}>
|
||||
<button
|
||||
class={["media-item", if(sidebar_item_selected?(@workbench, item.route, item.id), do: "selected")]}
|
||||
data-testid="sidebar-open-item"
|
||||
data-route={item.route}
|
||||
data-item-id={item.id}
|
||||
data-open-title={item.title}
|
||||
data-open-subtitle={item.meta}
|
||||
type="button"
|
||||
title={item.title}
|
||||
phx-click="open_sidebar_item"
|
||||
phx-value-route={item.route}
|
||||
phx-value-id={item.id}
|
||||
phx-value-title={item.title}
|
||||
phx-value-subtitle={item.meta}
|
||||
>
|
||||
<span class={media_thumbnail_class(item)}>
|
||||
<%= if image_media?(item) do %>
|
||||
<span class="media-thumbnail-fallback"><%= media_thumbnail_glyph(item.mime_type) %></span>
|
||||
<img class="media-thumbnail-image" src={"/media-thumbnail/#{item.id}"} alt="" loading="lazy" decoding="async" />
|
||||
<% else %>
|
||||
<span class="media-thumbnail-fallback"><%= media_thumbnail_glyph(item.mime_type) %></span>
|
||||
<% end %>
|
||||
</span>
|
||||
<span class="media-item-info">
|
||||
<span class="media-item-name"><%= item.title %></span>
|
||||
<span class="media-item-size"><%= item.meta %></span>
|
||||
</span>
|
||||
</button>
|
||||
<%= if sidebar_deletable?(item.route) do %>
|
||||
<button
|
||||
class="sidebar-delete-button"
|
||||
data-testid={sidebar_delete_testid(item.route)}
|
||||
data-item-id={item.id}
|
||||
type="button"
|
||||
title={sidebar_delete_title(item.route)}
|
||||
phx-click="confirm_sidebar_delete"
|
||||
phx-value-route={item.route}
|
||||
phx-value-id={item.id}
|
||||
phx-value-title={item.title}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
@@ -353,7 +387,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
|
||||
<%= if Enum.any?(Map.get(@sidebar_data, :items, [])) do %>
|
||||
<div class={if(template_sidebar?(@sidebar_data), do: "chat-list-items", else: "settings-nav-list")}>
|
||||
<%= for item <- Map.get(@sidebar_data, :items, []) do %>
|
||||
<%= if item.route in ["templates", "chat"] do %>
|
||||
<%= if sidebar_deletable?(item.route) do %>
|
||||
<div
|
||||
class={["chat-list-item", if(sidebar_item_selected?(@workbench, item.route, item.id), do: "active")]}
|
||||
data-item-id={item.id}
|
||||
@@ -378,13 +412,15 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="chat-item-delete"
|
||||
data-testid={if(item.route == "chat", do: "sidebar-delete-chat", else: "sidebar-delete-template")}
|
||||
class="sidebar-delete-button"
|
||||
data-testid={sidebar_delete_testid(item.route)}
|
||||
data-item-id={item.id}
|
||||
type="button"
|
||||
title={if(item.route == "chat", do: translated("sidebar.chat.deleteConversation"), else: translated("Delete") <> " " <> translated("Template"))}
|
||||
phx-click={if(item.route == "chat", do: "delete_sidebar_chat", else: "delete_sidebar_template")}
|
||||
title={sidebar_delete_title(item.route)}
|
||||
phx-click="confirm_sidebar_delete"
|
||||
phx-value-route={item.route}
|
||||
phx-value-id={item.id}
|
||||
phx-value-title={item.title}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
@@ -466,6 +502,24 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
|
||||
defp translated(text, bindings \\ %{}),
|
||||
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||
|
||||
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: translated("sidebar.chat.deleteConversation")
|
||||
defp sidebar_delete_title("post"), do: translated("Delete") <> " " <> translated("Post")
|
||||
defp sidebar_delete_title("media"), do: translated("Delete") <> " " <> translated("Media")
|
||||
defp sidebar_delete_title("scripts"), do: translated("Delete") <> " " <> translated("Script")
|
||||
defp sidebar_delete_title("templates"), do: translated("Delete") <> " " <> translated("Template")
|
||||
defp sidebar_delete_title("import"), do: translated("Delete") <> " " <> translated("Import")
|
||||
defp sidebar_delete_title(_route), do: translated("Delete")
|
||||
|
||||
defp template_sidebar?(sidebar_data), do: Map.get(sidebar_data, :title) == "Templates"
|
||||
|
||||
defp group_year_month_counts(entries) do
|
||||
|
||||
@@ -6819,6 +6819,11 @@ button svg * {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-item-row {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.sidebar-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@@ -6921,6 +6926,12 @@ button svg * {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.media-item-row {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.media-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -7004,6 +7015,12 @@ button svg * {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
.sidebar-item-row .sidebar-item,
|
||||
.media-item-row .media-item {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sidebar-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
@@ -8197,27 +8214,38 @@ button.import-taxonomy-pill {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.chat-item-delete {
|
||||
.sidebar-delete-button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
padding: 0 4px;
|
||||
padding: 0 6px;
|
||||
flex-shrink: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.chat-list-item:hover .chat-item-delete,
|
||||
.chat-list-item.active .chat-item-delete {
|
||||
.sidebar-item-row:hover .sidebar-delete-button,
|
||||
.sidebar-item-row .sidebar-item.selected ~ .sidebar-delete-button,
|
||||
.media-item-row:hover .sidebar-delete-button,
|
||||
.media-item-row .media-item.selected ~ .sidebar-delete-button,
|
||||
.chat-list-item:hover .sidebar-delete-button,
|
||||
.chat-list-item.active .sidebar-delete-button {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.chat-item-delete:hover {
|
||||
.sidebar-delete-button:hover {
|
||||
color: var(--vscode-errorForeground);
|
||||
}
|
||||
|
||||
.sidebar-item-row .sidebar-item.selected ~ .sidebar-delete-button,
|
||||
.media-item-row .media-item.selected ~ .sidebar-delete-button {
|
||||
background-color: var(--vscode-list-activeSelectionBackground);
|
||||
color: var(--vscode-list-activeSelectionForeground);
|
||||
}
|
||||
|
||||
.settings-nav-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -289,6 +289,12 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
|> element("[data-testid='sidebar-delete-chat'][data-item-id='#{created_chat.id}']")
|
||||
|> render_click()
|
||||
|
||||
assert Repo.get(BDS.AI.ChatConversation, created_chat.id)
|
||||
assert html =~ "confirm-delete-modal"
|
||||
assert html =~ created_chat.title
|
||||
|
||||
html = render_click(view, "overlay_confirm", %{})
|
||||
|
||||
refute Repo.get(BDS.AI.ChatConversation, created_chat.id)
|
||||
refute html =~ ~s(data-tab-id="#{created_chat.id}")
|
||||
|
||||
@@ -324,6 +330,122 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
assert html =~ ~s(data-settings-scroll-target="settings-section-ai")
|
||||
end
|
||||
|
||||
test "database-backed sidebar entries require confirmation before deletion", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
assert {:ok, post} =
|
||||
Posts.create_post(%{
|
||||
project_id: project.id,
|
||||
title: "Sidebar Delete Post",
|
||||
content: "delete me"
|
||||
})
|
||||
|
||||
media_source_path = Path.join(temp_dir, "sidebar-delete-media.txt")
|
||||
File.write!(media_source_path, "media body")
|
||||
|
||||
assert {:ok, media} =
|
||||
Media.import_media(%{
|
||||
project_id: project.id,
|
||||
source_path: media_source_path,
|
||||
title: "Sidebar Delete Media"
|
||||
})
|
||||
|
||||
assert {:ok, script} =
|
||||
Scripts.create_script(%{
|
||||
project_id: project.id,
|
||||
title: "Sidebar Delete Script",
|
||||
kind: :utility,
|
||||
content: "print(\"delete\")",
|
||||
entrypoint: "main",
|
||||
enabled: true
|
||||
})
|
||||
|
||||
assert {:ok, template} =
|
||||
Templates.create_template(%{
|
||||
project_id: project.id,
|
||||
title: "Sidebar Delete Template",
|
||||
kind: :post,
|
||||
content: "<article>{{ post.content }}</article>",
|
||||
enabled: true
|
||||
})
|
||||
|
||||
assert {:ok, conversation} = AI.start_chat(%{title: "Sidebar Delete Chat"})
|
||||
|
||||
assert {:ok, definition} =
|
||||
ImportDefinitions.create_definition(%{
|
||||
project_id: project.id,
|
||||
name: "Sidebar Delete Import"
|
||||
})
|
||||
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
cases = [
|
||||
%{
|
||||
view: "posts",
|
||||
id: post.id,
|
||||
title: post.title,
|
||||
testid: "sidebar-delete-post",
|
||||
exists?: fn -> Repo.get(Post, post.id) != nil end
|
||||
},
|
||||
%{
|
||||
view: "media",
|
||||
id: media.id,
|
||||
title: media.title,
|
||||
testid: "sidebar-delete-media",
|
||||
exists?: fn -> Repo.get(BDS.Media.Media, media.id) != nil end
|
||||
},
|
||||
%{
|
||||
view: "scripts",
|
||||
id: script.id,
|
||||
title: script.title,
|
||||
testid: "sidebar-delete-script",
|
||||
exists?: fn -> Repo.get(BDS.Scripts.Script, script.id) != nil end
|
||||
},
|
||||
%{
|
||||
view: "templates",
|
||||
id: template.id,
|
||||
title: template.title,
|
||||
testid: "sidebar-delete-template",
|
||||
exists?: fn -> Repo.get(BDS.Templates.Template, template.id) != nil end
|
||||
},
|
||||
%{
|
||||
view: "chat",
|
||||
id: conversation.id,
|
||||
title: conversation.title,
|
||||
testid: "sidebar-delete-chat",
|
||||
exists?: fn -> Repo.get(BDS.AI.ChatConversation, conversation.id) != nil end
|
||||
},
|
||||
%{
|
||||
view: "import",
|
||||
id: definition.id,
|
||||
title: definition.name,
|
||||
testid: "sidebar-delete-import",
|
||||
exists?: fn -> Repo.get(ImportDefinitions.ImportDefinition, definition.id) != nil end
|
||||
}
|
||||
]
|
||||
|
||||
Enum.each(cases, fn sidebar_case ->
|
||||
html = render_click(view, "select_view", %{"view" => sidebar_case.view})
|
||||
|
||||
assert html =~ ~s(data-testid="#{sidebar_case.testid}")
|
||||
|
||||
html =
|
||||
view
|
||||
|> element("[data-testid='#{sidebar_case.testid}'][data-item-id='#{sidebar_case.id}']")
|
||||
|> render_click()
|
||||
|
||||
assert sidebar_case.exists?.()
|
||||
assert html =~ "confirm-delete-modal"
|
||||
assert html =~ sidebar_case.title
|
||||
|
||||
html = render_click(view, "overlay_confirm", %{})
|
||||
|
||||
refute sidebar_case.exists?.()
|
||||
refute html =~ sidebar_case.title
|
||||
end)
|
||||
end
|
||||
|
||||
test "shell live refreshes the posts sidebar when the CLI watcher broadcasts an entity change",
|
||||
%{project: project} do
|
||||
{:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
@@ -3292,6 +3414,12 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
|> element("[data-testid='sidebar-delete-template'][data-item-id='#{template.id}']")
|
||||
|> render_click()
|
||||
|
||||
assert BDS.Repo.get(BDS.Templates.Template, template.id)
|
||||
assert html =~ "confirm-delete-modal"
|
||||
assert html =~ template.title
|
||||
|
||||
html = render_click(view, "overlay_confirm", %{})
|
||||
|
||||
assert BDS.Repo.get(BDS.Templates.Template, template.id) == nil
|
||||
refute html =~ "Sidebar Template"
|
||||
refute html =~ ~s(data-tab-type="templates")
|
||||
|
||||
Reference in New Issue
Block a user