Files
bDS2/lib/bds/desktop/shell_live/code_entity_editor.ex

269 lines
10 KiB
Elixir

defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
@moduledoc false
use Phoenix.Component
alias BDS.Desktop.ShellData
alias BDS.{MCP, Scripts, Scripting, Templates}
alias BDS.Scripts.Script
alias BDS.Templates.Template
embed_templates "code_entity_editor_html/*"
def assign_socket(socket) do
socket
|> assign(:script_editor, build_script(socket.assigns))
|> assign(:template_editor, build_template(socket.assigns))
end
def update_script(socket, params, reload) do
%{id: script_id} = socket.assigns.current_tab
socket
|> assign(:script_editor_drafts, Map.put(socket.assigns.script_editor_drafts, script_id, normalize_script_params(params)))
|> reload.(socket.assigns.workbench)
end
def save_script(socket, reload, append_output) do
%{id: script_id} = socket.assigns.current_tab
case Scripts.get_script(script_id) do
nil -> reload.(socket, socket.assigns.workbench)
%Script{} = script ->
draft = current_script_draft(socket.assigns, script)
case Scripting.validate(draft["content"] || "") do
:ok ->
case Scripts.update_script(script.id, script_attrs(draft)) do
{:ok, _updated} ->
socket
|> assign(:script_editor_drafts, Map.delete(socket.assigns.script_editor_drafts, script.id))
|> reload.(socket.assigns.workbench)
{:error, reason} ->
socket
|> append_output.(translated("Scripts"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench)
end
{:error, reason} ->
socket
|> append_output.(translated("Scripts"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench)
end
end
end
def check_script(socket, reload, append_output) do
%{id: script_id} = socket.assigns.current_tab
case Scripts.get_script(script_id) do
nil -> reload.(socket, socket.assigns.workbench)
%Script{} = script ->
case Scripting.validate(current_script_draft(socket.assigns, script)["content"] || "") do
:ok -> append_output.(socket, translated("Scripts"), translated("Syntax is valid")) |> reload.(socket.assigns.workbench)
{:error, reason} -> append_output.(socket, translated("Scripts"), inspect(reason), nil, "error") |> reload.(socket.assigns.workbench)
end
end
end
def run_script(socket, reload, append_output) do
%{id: script_id} = socket.assigns.current_tab
case Scripts.get_script(script_id) do
nil -> reload.(socket, socket.assigns.workbench)
%Script{} = script ->
draft = current_script_draft(socket.assigns, script)
case Scripting.execute_project_script(script.project_id, draft["content"] || "", draft["entrypoint"] || "main", []) do
{:ok, result} ->
socket
|> append_output.(translated("Scripts"), inspect(result))
|> reload.(socket.assigns.workbench)
{:error, reason} ->
socket
|> append_output.(translated("Scripts"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench)
end
end
end
def delete_script(socket, reload, append_output) do
%{id: script_id} = socket.assigns.current_tab
case Scripts.delete_script(script_id) do
{:ok, _deleted} -> reload.(socket, BDS.UI.Workbench.close_tab(socket.assigns.workbench, :scripts, script_id))
{:error, reason} -> append_output.(socket, translated("Scripts"), inspect(reason), nil, "error") |> reload.(socket.assigns.workbench)
end
end
def update_template(socket, params, reload) do
%{id: template_id} = socket.assigns.current_tab
socket
|> assign(:template_editor_drafts, Map.put(socket.assigns.template_editor_drafts, template_id, normalize_template_params(params)))
|> reload.(socket.assigns.workbench)
end
def save_template(socket, reload, append_output) do
%{id: template_id} = socket.assigns.current_tab
case Templates.get_template(template_id) do
nil -> reload.(socket, socket.assigns.workbench)
%Template{} = template ->
draft = current_template_draft(socket.assigns, template)
with {:ok, %{valid: true}} <- MCP.validate_template(draft["content"] || ""),
{:ok, _updated} <- Templates.update_template(template.id, template_attrs(draft)) do
socket
|> assign(:template_editor_drafts, Map.delete(socket.assigns.template_editor_drafts, template.id))
|> reload.(socket.assigns.workbench)
else
{:ok, %{valid: false, errors: errors}} -> append_output.(socket, translated("Templates"), Enum.join(errors, "; "), nil, "error") |> reload.(socket.assigns.workbench)
{:error, reason} -> append_output.(socket, translated("Templates"), inspect(reason), nil, "error") |> reload.(socket.assigns.workbench)
end
end
end
def validate_template(socket, reload, append_output) do
%{id: template_id} = socket.assigns.current_tab
case Templates.get_template(template_id) do
nil -> reload.(socket, socket.assigns.workbench)
%Template{} = template ->
case MCP.validate_template(current_template_draft(socket.assigns, template)["content"] || "") do
{:ok, %{valid: true}} -> append_output.(socket, translated("Templates"), translated("Template syntax is valid")) |> reload.(socket.assigns.workbench)
{:ok, %{valid: false, errors: errors}} -> append_output.(socket, translated("Templates"), Enum.join(errors, "; "), nil, "error") |> reload.(socket.assigns.workbench)
end
end
end
def delete_template(socket, reload, append_output) do
%{id: template_id} = socket.assigns.current_tab
case Templates.delete_template(template_id, force: true) do
{:ok, _deleted} -> reload.(socket, BDS.UI.Workbench.close_tab(socket.assigns.workbench, :templates, template_id))
{:error, reason} -> append_output.(socket, translated("Templates"), inspect(reason), nil, "error") |> reload.(socket.assigns.workbench)
end
end
def build_script(%{current_tab: %{type: :scripts, id: script_id}} = assigns) do
case Scripts.get_script(script_id) do
nil -> nil
%Script{} = script ->
draft = current_script_draft(assigns, script)
%{
id: script.id,
title: draft["title"],
slug: draft["slug"],
kind: draft["kind"],
entrypoint: draft["entrypoint"],
enabled: draft["enabled"],
content: draft["content"],
entrypoints: discover_entrypoints(draft["content"]),
created_at: script.created_at,
updated_at: script.updated_at
}
end
end
def build_script(_assigns), do: nil
def build_template(%{current_tab: %{type: :templates, id: template_id}} = assigns) do
case Templates.get_template(template_id) do
nil -> nil
%Template{} = template ->
draft = current_template_draft(assigns, template)
%{
id: template.id,
title: draft["title"],
slug: draft["slug"],
kind: draft["kind"],
enabled: draft["enabled"],
content: draft["content"],
created_at: template.created_at,
updated_at: template.updated_at
}
end
end
def build_template(_assigns), do: nil
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
def format_timestamp(nil), do: ""
def format_timestamp(timestamp), do: BDS.Persistence.timestamp_to_iso8601(timestamp)
defp normalize_script_params(params) do
%{
"title" => Map.get(params, "title", ""),
"slug" => Map.get(params, "slug", ""),
"kind" => Map.get(params, "kind", "utility"),
"entrypoint" => Map.get(params, "entrypoint", "main"),
"enabled" => Map.get(params, "enabled") in [true, "true", "on", "1", 1],
"content" => Map.get(params, "content", "")
}
end
defp normalize_template_params(params) do
%{
"title" => Map.get(params, "title", ""),
"slug" => Map.get(params, "slug", ""),
"kind" => Map.get(params, "kind", "post"),
"enabled" => Map.get(params, "enabled") in [true, "true", "on", "1", 1],
"content" => Map.get(params, "content", "")
}
end
defp current_script_draft(assigns, %Script{} = script) do
Map.get(assigns.script_editor_drafts, script.id, %{
"title" => script.title || "",
"slug" => script.slug || "",
"kind" => to_string(script.kind || :utility),
"entrypoint" => script.entrypoint || "main",
"enabled" => script.enabled != false,
"content" => script.content || ""
})
end
defp current_template_draft(assigns, %Template{} = template) do
Map.get(assigns.template_editor_drafts, template.id, %{
"title" => template.title || "",
"slug" => template.slug || "",
"kind" => to_string(template.kind || :post),
"enabled" => template.enabled != false,
"content" => template.content || ""
})
end
defp script_attrs(draft) do
%{
title: draft["title"],
slug: draft["slug"],
kind: String.to_existing_atom(draft["kind"]),
entrypoint: draft["entrypoint"],
enabled: draft["enabled"],
content: draft["content"]
}
rescue
_error -> %{title: draft["title"], slug: draft["slug"], kind: :utility, entrypoint: draft["entrypoint"], enabled: draft["enabled"], content: draft["content"]}
end
defp template_attrs(draft) do
%{title: draft["title"], slug: draft["slug"], kind: normalize_template_kind(draft["kind"]), enabled: draft["enabled"], content: draft["content"]}
end
defp normalize_template_kind("post"), do: :post
defp normalize_template_kind("list"), do: :list
defp normalize_template_kind("not-found"), do: :"not-found"
defp normalize_template_kind("partial"), do: :partial
defp normalize_template_kind(_kind), do: :post
defp discover_entrypoints(content) do
["main" | Regex.scan(~r/function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/, content || "", capture: :all_but_first)
|> List.flatten()
|> Enum.reject(&(&1 == "main"))]
end
end