feat: adding "+" buttons to sidebar titles

This commit is contained in:
2026-04-26 22:05:55 +02:00
parent 334ffe6f6a
commit 0d7a68bc0f
11 changed files with 342 additions and 27 deletions

View File

@@ -5,7 +5,7 @@ defmodule BDS.Desktop.ShellLive do
import Phoenix.HTML import Phoenix.HTML
alias BDS.Desktop.{FolderPicker, Overlay, ShellCommands, ShellData} alias BDS.Desktop.{FilePicker, FolderPicker, Overlay, ShellCommands, ShellData}
alias BDS.Desktop.ShellLive.{ChatEditor, CodeEntityEditor, MediaEditor, MiscEditor, SettingsEditor, TagsEditor} alias BDS.Desktop.ShellLive.{ChatEditor, CodeEntityEditor, MediaEditor, MiscEditor, SettingsEditor, TagsEditor}
alias BDS.Desktop.ShellLive.OverlayComponents, as: ShellOverlayComponents alias BDS.Desktop.ShellLive.OverlayComponents, as: ShellOverlayComponents
alias BDS.Desktop.ShellLive.PostEditor alias BDS.Desktop.ShellLive.PostEditor
@@ -13,11 +13,13 @@ defmodule BDS.Desktop.ShellLive do
alias BDS.Desktop.ShellLive.SidebarState, as: ShellSidebarState alias BDS.Desktop.ShellLive.SidebarState, as: ShellSidebarState
alias BDS.Desktop.MenuBar, as: DesktopMenuBar alias BDS.Desktop.MenuBar, as: DesktopMenuBar
alias BDS.Git alias BDS.Git
alias BDS.ImportDefinitions
alias BDS.Media.Media alias BDS.Media.Media
alias BDS.PostLinks alias BDS.PostLinks
alias BDS.Posts.Post alias BDS.Posts.Post
alias BDS.Projects alias BDS.Projects
alias BDS.Repo alias BDS.Repo
alias BDS.Scripts
alias BDS.Templates alias BDS.Templates
alias BDS.UI.{Commands, MenuBar, Registry, Session, Workbench} alias BDS.UI.{Commands, MenuBar, Registry, Session, Workbench}
@@ -282,6 +284,10 @@ defmodule BDS.Desktop.ShellLive do
|> reload_shell(socket.assigns.workbench)} |> reload_shell(socket.assigns.workbench)}
end end
def handle_event("create_sidebar_item", %{"kind" => kind}, socket) do
{:noreply, create_sidebar_item(socket, kind)}
end
def handle_event("shortcut", params, socket) do def handle_event("shortcut", params, socket) do
if ignore_shortcut?(params) do if ignore_shortcut?(params) do
{:noreply, socket} {:noreply, socket}
@@ -1345,6 +1351,102 @@ defmodule BDS.Desktop.ShellLive do
Map.get(params, :tag) in ["INPUT", "TEXTAREA", "SELECT"] Map.get(params, :tag) in ["INPUT", "TEXTAREA", "SELECT"]
end end
defp create_sidebar_item(socket, kind) do
case socket.assigns.projects.active_project_id do
project_id when is_binary(project_id) -> create_sidebar_item(socket, project_id, kind)
_other -> reload_shell(socket, socket.assigns.workbench)
end
end
defp create_sidebar_item(socket, project_id, "post") do
case BDS.Posts.create_post(%{project_id: project_id, title: "", content: "", tags: [], categories: []}) do
{:ok, _post} -> reload_shell(socket, socket.assigns.workbench)
{:error, reason} ->
socket
|> append_output_entry(translated("sidebar.newPost"), inspect(reason), nil, "error")
|> reload_shell(socket.assigns.workbench)
end
end
defp create_sidebar_item(socket, project_id, "media") do
case FilePicker.choose_file(translated("sidebar.importMedia")) do
{:ok, source_path} ->
case BDS.Media.import_media(%{project_id: project_id, source_path: source_path}) do
{:ok, _media} -> reload_shell(socket, socket.assigns.workbench)
{:error, reason} ->
socket
|> append_output_entry(translated("sidebar.importMedia"), inspect(reason), nil, "error")
|> reload_shell(socket.assigns.workbench)
end
:cancel ->
reload_shell(socket, socket.assigns.workbench)
{:error, %{message: message}} ->
socket
|> append_output_entry(translated("sidebar.importMedia"), message, nil, "error")
|> reload_shell(socket.assigns.workbench)
{:error, reason} ->
socket
|> append_output_entry(translated("sidebar.importMedia"), inspect(reason), nil, "error")
|> reload_shell(socket.assigns.workbench)
end
end
defp create_sidebar_item(socket, project_id, "script") do
case Scripts.create_script(%{
project_id: project_id,
title: translated("sidebar.scripts.newScript"),
kind: :utility,
content: "print(\"new script\")",
entrypoint: "main",
enabled: true
}) do
{:ok, script} ->
open_sidebar_item(socket, %{"route" => "scripts", "id" => script.id, "title" => script.title, "subtitle" => "Automation helpers"}, :pin)
{:error, reason} ->
socket
|> append_output_entry(translated("sidebar.scripts.newScript"), inspect(reason), nil, "error")
|> reload_shell(socket.assigns.workbench)
end
end
defp create_sidebar_item(socket, project_id, "template") do
case Templates.create_template(%{
project_id: project_id,
title: translated("sidebar.templates.newTemplate"),
kind: :post,
content: "",
enabled: true
}) do
{:ok, template} ->
open_sidebar_item(socket, %{"route" => "templates", "id" => template.id, "title" => template.title, "subtitle" => "Site rendering"}, :pin)
{:error, reason} ->
socket
|> append_output_entry(translated("sidebar.templates.newTemplate"), inspect(reason), nil, "error")
|> reload_shell(socket.assigns.workbench)
end
end
defp create_sidebar_item(socket, project_id, "import") do
case ImportDefinitions.create_definition(%{project_id: project_id, name: translated("sidebar.import.newDefinition")}) do
{:ok, definition} ->
open_sidebar_item(socket, %{"route" => "import", "id" => definition.id, "title" => definition.name, "subtitle" => "Import definitions"}, :pin)
{:error, reason} ->
socket
|> append_output_entry(translated("sidebar.import.newDefinition"), inspect(reason), nil, "error")
|> reload_shell(socket.assigns.workbench)
end
end
defp create_sidebar_item(socket, _project_id, _kind), do: reload_shell(socket, socket.assigns.workbench)
defp open_sidebar_item(socket, params, intent) do defp open_sidebar_item(socket, params, intent) do
route_atom = sidebar_route_atom(Map.fetch!(params, "route")) route_atom = sidebar_route_atom(Map.fetch!(params, "route"))
tab_id = tab_id_for_route(route_atom, Map.fetch!(params, "id")) tab_id = tab_id_for_route(route_atom, Map.fetch!(params, "id"))
@@ -1364,6 +1466,13 @@ defmodule BDS.Desktop.ShellLive do
|> reload_shell(workbench) |> reload_shell(workbench)
end end
defp sidebar_create_action(:posts), do: %{kind: "post", label: "sidebar.newPost"}
defp sidebar_create_action(:media), do: %{kind: "media", label: "sidebar.importMedia"}
defp sidebar_create_action(:scripts), do: %{kind: "script", label: "sidebar.scripts.newScript"}
defp sidebar_create_action(:templates), do: %{kind: "template", label: "sidebar.templates.newTemplate"}
defp sidebar_create_action(:import), do: %{kind: "import", label: "sidebar.import.newDefinition"}
defp sidebar_create_action(_view), do: nil
defp set_page_language(socket, language) do defp set_page_language(socket, language) do
codes = Enum.map(socket.assigns[:supported_ui_languages] || ShellData.supported_ui_languages(), & &1.code) codes = Enum.map(socket.assigns[:supported_ui_languages] || ShellData.supported_ui_languages(), & &1.code)

View File

@@ -164,25 +164,44 @@
<div class="sidebar" data-region="sidebar"> <div class="sidebar" data-region="sidebar">
<div id="sidebar-content" class="sidebar-content sidebar-body" phx-hook="SidebarInteractions"> <div id="sidebar-content" class="sidebar-content sidebar-body" phx-hook="SidebarInteractions">
<div class="sidebar-section"> <div class="sidebar-section">
<% create_action = sidebar_create_action(@workbench.active_view) %>
<div class="sidebar-section-header"> <div class="sidebar-section-header">
<span><%= String.upcase(sidebar_header_label(@sidebar_header)) %></span> <span><%= String.upcase(sidebar_header_label(@sidebar_header)) %></span>
<%= if ShellSidebarComponents.filters_enabled?(@sidebar_data) do %> <%= if create_action || ShellSidebarComponents.filters_enabled?(@sidebar_data) do %>
<div class="sidebar-actions"> <div class="sidebar-actions">
<button <%= if ShellSidebarComponents.filters_enabled?(@sidebar_data) do %>
class={[ <button
"sidebar-action", class={[
if(ShellSidebarComponents.filters_visible?(@sidebar_data), do: "active") "sidebar-action",
]} if(ShellSidebarComponents.filters_visible?(@sidebar_data), do: "active")
data-testid="sidebar-filter-toggle" ]}
type="button" data-testid="sidebar-filter-toggle"
phx-click="toggle_sidebar_filters" type="button"
aria-label={translated(Map.get(@sidebar_data.filters, :toggle_filters_label))} phx-click="toggle_sidebar_filters"
title={translated(Map.get(@sidebar_data.filters, :toggle_filters_label))} aria-label={translated(Map.get(@sidebar_data.filters, :toggle_filters_label))}
> title={translated(Map.get(@sidebar_data.filters, :toggle_filters_label))}
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> >
<path d="M6 12v-1h4v1H6zM4 8v-1h8v1H4zm-2-4v-1h12v1H2z"/> <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
</svg> <path d="M6 12v-1h4v1H6zM4 8v-1h8v1H4zm-2-4v-1h12v1H2z"/>
</button> </svg>
</button>
<% end %>
<%= if create_action do %>
<button
class="sidebar-action"
data-testid="sidebar-create-action"
data-sidebar-action={create_action.kind}
type="button"
phx-click="create_sidebar_item"
phx-value-kind={create_action.kind}
aria-label={translated(create_action.label)}
title={translated(create_action.label)}
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M14 7v1H8v6H7V8H1V7h6V1h1v6h6z"/>
</svg>
</button>
<% end %>
</div> </div>
<% end %> <% end %>
</div> </div>

View File

@@ -0,0 +1,37 @@
defmodule BDS.ImportDefinitions do
@moduledoc false
import Ecto.Query
alias BDS.ImportDefinitions.ImportDefinition
alias BDS.Persistence
alias BDS.Repo
def create_definition(attrs) do
now = Persistence.now_ms()
%ImportDefinition{}
|> ImportDefinition.changeset(%{
id: Ecto.UUID.generate(),
project_id: attr(attrs, :project_id),
name: attr(attrs, :name) || "",
wxr_file_path: attr(attrs, :wxr_file_path),
uploads_folder_path: attr(attrs, :uploads_folder_path),
last_analysis_result: attr(attrs, :last_analysis_result),
created_at: now,
updated_at: now
})
|> Repo.insert()
end
def list_definitions(project_id) do
Repo.all(
from definition in ImportDefinition,
where: definition.project_id == ^project_id,
order_by: [desc: definition.updated_at, desc: definition.created_at],
select: %{id: definition.id, title: definition.name, updated_at: definition.updated_at}
)
end
defp attr(attrs, key), do: Map.get(attrs, key) || Map.get(attrs, Atom.to_string(key))
end

View File

@@ -0,0 +1,25 @@
defmodule BDS.ImportDefinitions.ImportDefinition do
@moduledoc false
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :string, autogenerate: false}
schema "import_definitions" do
field :project_id, :string
field :name, :string
field :wxr_file_path, :string
field :uploads_folder_path, :string
field :last_analysis_result, :string
field :created_at, :integer
field :updated_at, :integer
end
def changeset(definition, attrs) do
definition
|> cast(attrs, [:id, :project_id, :name, :wxr_file_path, :uploads_folder_path, :last_analysis_result, :created_at, :updated_at])
|> validate_required([:id, :project_id, :name, :created_at, :updated_at])
end
end

View File

@@ -4,6 +4,7 @@ defmodule BDS.UI.Sidebar do
import Ecto.Query import Ecto.Query
alias BDS.AI.ChatConversation alias BDS.AI.ChatConversation
alias BDS.ImportDefinitions
alias BDS.Media.Media alias BDS.Media.Media
alias BDS.Posts.Post alias BDS.Posts.Post
alias BDS.Posts.Translation alias BDS.Posts.Translation
@@ -26,7 +27,7 @@ defmodule BDS.UI.Sidebar do
"templates" => view(project_id, "templates"), "templates" => view(project_id, "templates"),
"tags" => view(project_id, "tags"), "tags" => view(project_id, "tags"),
"chat" => view(project_id, "chat"), "chat" => view(project_id, "chat"),
"import" => entity_list_view("Import", "Import definitions", "import", []), "import" => entity_list_view("Import", "Import definitions", "import", list_import_definitions(project_id)),
"git" => git_view(), "git" => git_view(),
"settings" => settings_nav_view() "settings" => settings_nav_view()
} }
@@ -47,7 +48,7 @@ defmodule BDS.UI.Sidebar do
"templates" -> entity_list_view("Templates", "Site rendering", "templates", list_templates(project_id)) "templates" -> entity_list_view("Templates", "Site rendering", "templates", list_templates(project_id))
"tags" -> tags_nav_view(list_tags(project_id)) "tags" -> tags_nav_view(list_tags(project_id))
"chat" -> entity_list_view("Chat", "AI conversations", "chat", list_conversations()) "chat" -> entity_list_view("Chat", "AI conversations", "chat", list_conversations())
"import" -> entity_list_view("Import", "Import definitions", "import", []) "import" -> entity_list_view("Import", "Import definitions", "import", list_import_definitions(project_id))
"git" -> git_view() "git" -> git_view()
"settings" -> settings_nav_view() "settings" -> settings_nav_view()
_other -> empty_view(normalized_view) _other -> empty_view(normalized_view)
@@ -359,6 +360,10 @@ defmodule BDS.UI.Sidebar do
) )
end end
defp list_import_definitions(project_id) do
ImportDefinitions.list_definitions(project_id)
end
defp list_tags(project_id) do defp list_tags(project_id) do
Repo.all( Repo.all(
from tag in Tag, from tag in Tag,

View File

@@ -57,6 +57,11 @@
"sidebar.searchPagesPlaceholder": "Seiten durchsuchen...", "sidebar.searchPagesPlaceholder": "Seiten durchsuchen...",
"sidebar.searchMediaPlaceholder": "Medien durchsuchen...", "sidebar.searchMediaPlaceholder": "Medien durchsuchen...",
"sidebar.toggleFilters": "Filter umschalten", "sidebar.toggleFilters": "Filter umschalten",
"sidebar.newPost": "Neuer Beitrag",
"sidebar.importMedia": "Medien importieren",
"sidebar.import.newDefinition": "Neue Importdefinition",
"sidebar.scripts.newScript": "Neues Skript",
"sidebar.templates.newTemplate": "Neue Vorlage",
"sidebar.results": "%{count} Ergebnisse", "sidebar.results": "%{count} Ergebnisse",
"sidebar.resultsFor": "%{count} Ergebnisse für \"%{query}\"", "sidebar.resultsFor": "%{count} Ergebnisse für \"%{query}\"",
"sidebar.clearFilters": "Filter löschen", "sidebar.clearFilters": "Filter löschen",

View File

@@ -57,6 +57,11 @@
"sidebar.searchPagesPlaceholder": "Search pages...", "sidebar.searchPagesPlaceholder": "Search pages...",
"sidebar.searchMediaPlaceholder": "Search media...", "sidebar.searchMediaPlaceholder": "Search media...",
"sidebar.toggleFilters": "Toggle Filters", "sidebar.toggleFilters": "Toggle Filters",
"sidebar.newPost": "New Post",
"sidebar.importMedia": "Import media",
"sidebar.import.newDefinition": "New Import Definition",
"sidebar.scripts.newScript": "New Script",
"sidebar.templates.newTemplate": "New Template",
"sidebar.results": "%{count} results", "sidebar.results": "%{count} results",
"sidebar.resultsFor": "%{count} results for \"%{query}\"", "sidebar.resultsFor": "%{count} results for \"%{query}\"",
"sidebar.clearFilters": "Clear filters", "sidebar.clearFilters": "Clear filters",

View File

@@ -57,6 +57,11 @@
"sidebar.searchPagesPlaceholder": "Buscar páginas...", "sidebar.searchPagesPlaceholder": "Buscar páginas...",
"sidebar.searchMediaPlaceholder": "Buscar medios...", "sidebar.searchMediaPlaceholder": "Buscar medios...",
"sidebar.toggleFilters": "Alternar filtros", "sidebar.toggleFilters": "Alternar filtros",
"sidebar.newPost": "Nueva entrada",
"sidebar.importMedia": "Importar medios",
"sidebar.import.newDefinition": "Nueva definición",
"sidebar.scripts.newScript": "Nuevo script",
"sidebar.templates.newTemplate": "Nueva plantilla",
"sidebar.results": "%{count} resultados", "sidebar.results": "%{count} resultados",
"sidebar.resultsFor": "%{count} resultados para \"%{query}\"", "sidebar.resultsFor": "%{count} resultados para \"%{query}\"",
"sidebar.clearFilters": "Limpiar filtros", "sidebar.clearFilters": "Limpiar filtros",

View File

@@ -57,6 +57,11 @@
"sidebar.searchPagesPlaceholder": "Rechercher des pages...", "sidebar.searchPagesPlaceholder": "Rechercher des pages...",
"sidebar.searchMediaPlaceholder": "Rechercher des médias...", "sidebar.searchMediaPlaceholder": "Rechercher des médias...",
"sidebar.toggleFilters": "Afficher/masquer les filtres", "sidebar.toggleFilters": "Afficher/masquer les filtres",
"sidebar.newPost": "Nouvel article",
"sidebar.importMedia": "Importer des médias",
"sidebar.import.newDefinition": "Nouvelle définition",
"sidebar.scripts.newScript": "Nouveau script",
"sidebar.templates.newTemplate": "Nouveau modèle",
"sidebar.results": "%{count} résultats", "sidebar.results": "%{count} résultats",
"sidebar.resultsFor": "%{count} résultats pour \"%{query}\"", "sidebar.resultsFor": "%{count} résultats pour \"%{query}\"",
"sidebar.clearFilters": "Effacer les filtres", "sidebar.clearFilters": "Effacer les filtres",

View File

@@ -57,6 +57,11 @@
"sidebar.searchPagesPlaceholder": "Cerca pagine...", "sidebar.searchPagesPlaceholder": "Cerca pagine...",
"sidebar.searchMediaPlaceholder": "Cerca media...", "sidebar.searchMediaPlaceholder": "Cerca media...",
"sidebar.toggleFilters": "Mostra/nascondi filtri", "sidebar.toggleFilters": "Mostra/nascondi filtri",
"sidebar.newPost": "Nuovo post",
"sidebar.importMedia": "Importa media",
"sidebar.import.newDefinition": "Nuova definizione",
"sidebar.scripts.newScript": "Nuovo script",
"sidebar.templates.newTemplate": "Nuovo modello",
"sidebar.results": "%{count} risultati", "sidebar.results": "%{count} risultati",
"sidebar.resultsFor": "%{count} risultati per \"%{query}\"", "sidebar.resultsFor": "%{count} risultati per \"%{query}\"",
"sidebar.clearFilters": "Cancella filtri", "sidebar.clearFilters": "Cancella filtri",

View File

@@ -15,6 +15,7 @@ defmodule BDS.Desktop.ShellLiveTest do
alias BDS.Scripts alias BDS.Scripts
alias BDS.Templates alias BDS.Templates
alias BDS.Tags alias BDS.Tags
alias BDS.ImportDefinitions
alias BDS.UI.{Session, Workbench} alias BDS.UI.{Session, Workbench}
@endpoint BDS.Desktop.Endpoint @endpoint BDS.Desktop.Endpoint
@@ -51,6 +52,106 @@ defmodule BDS.Desktop.ShellLiveTest do
%{project: project, temp_dir: temp_dir} %{project: project, temp_dir: temp_dir}
end end
test "sidebar headers expose old-app create actions for posts, media, scripts, templates, and imports" do
{:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
assert html =~ ~s(data-testid="sidebar-create-action")
assert html =~ ~s(data-sidebar-action="post")
assert html =~ ~s(data-testid="sidebar-filter-toggle")
html = render_click(view, "select_view", %{"view" => "media"})
assert html =~ ~s(data-sidebar-action="media")
assert html =~ ~s(data-testid="sidebar-filter-toggle")
_html =
view
|> element("[data-testid='activity-button'][data-view='scripts']")
|> render_click()
assert html =~ ~s(data-sidebar-action="script")
_html =
view
|> element("[data-testid='activity-button'][data-view='templates']")
|> render_click()
assert html =~ ~s(data-sidebar-action="template")
_html =
view
|> element("[data-testid='activity-button'][data-view='import']")
|> render_click()
assert html =~ ~s(data-sidebar-action="import")
end
test "sidebar create actions follow the old-app post, script, template, and import flows", %{project: project} do
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
post_count_before = Repo.aggregate(Post, :count, :id)
script_count_before = Repo.aggregate(BDS.Scripts.Script, :count, :id)
template_count_before = Repo.aggregate(BDS.Templates.Template, :count, :id)
import_count_before = Repo.aggregate(ImportDefinitions.ImportDefinition, :count, :id)
html =
view
|> element("[data-testid='sidebar-create-action'][data-sidebar-action='post']")
|> render_click()
assert Repo.aggregate(Post, :count, :id) == post_count_before + 1
created_post = Repo.one!(Post)
assert created_post.project_id == project.id
assert created_post.title == ""
assert created_post.content == ""
refute html =~ ~s(data-tab-type="post")
html = render_click(view, "select_view", %{"view" => "scripts"})
html =
view
|> element("[data-testid='sidebar-create-action'][data-sidebar-action='script']")
|> render_click()
assert Repo.aggregate(BDS.Scripts.Script, :count, :id) == script_count_before + 1
created_script = Repo.one!(BDS.Scripts.Script)
assert created_script.project_id == project.id
assert created_script.title == "New Script"
assert created_script.entrypoint == "main"
assert created_script.content == "print(\"new script\")"
assert html =~ ~s(data-tab-type="scripts")
assert html =~ ~s(data-tab-id="#{created_script.id}")
html = render_click(view, "select_view", %{"view" => "templates"})
html =
view
|> element("[data-testid='sidebar-create-action'][data-sidebar-action='template']")
|> render_click()
assert Repo.aggregate(BDS.Templates.Template, :count, :id) == template_count_before + 1
created_template = Repo.get_by!(BDS.Templates.Template, title: "New Template")
assert created_template.project_id == project.id
assert created_template.title == "New Template"
assert created_template.content == ""
assert html =~ ~s(data-tab-type="templates")
assert html =~ ~s(data-tab-id="#{created_template.id}")
html = render_click(view, "select_view", %{"view" => "import"})
render_click(view, "select_view", %{"view" => "scripts"})
assert Repo.aggregate(ImportDefinitions.ImportDefinition, :count, :id) == import_count_before + 1
created_definition = Repo.one!(ImportDefinitions.ImportDefinition)
assert created_definition.project_id == project.id
assert created_definition.name == "New Import Definition"
assert html =~ ~s(data-tab-type="import")
assert html =~ ~s(data-tab-id="#{created_definition.id}")
end
test "shell live owns pane visibility and activity selection on the server" do test "shell live owns pane visibility and activity selection on the server" do
{:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive) {:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
@@ -63,17 +164,11 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(data-view="media") assert html =~ ~s(data-view="media")
assert html =~ ~s(aria-label="Posts") assert html =~ ~s(aria-label="Posts")
html = render_click(view, "select_view", %{"view" => "templates"})
view
|> element("[data-testid='toggle-sidebar']")
|> render_click()
assert html =~ ~s(class="sidebar-shell is-hidden") assert html =~ ~s(class="sidebar-shell is-hidden")
html = render_click(view, "select_view", %{"view" => "import"})
view
|> element("[data-testid='toggle-panel']")
|> render_click()
assert html =~ ~s(data-region="panel") assert html =~ ~s(data-region="panel")
refute html =~ ~s(class="panel-shell is-hidden") refute html =~ ~s(class="panel-shell is-hidden")