chore: converted scripts and templates to live components
This commit is contained in:
@@ -11,13 +11,14 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
|
|
||||||
alias BDS.Desktop.ShellLive.{
|
alias BDS.Desktop.ShellLive.{
|
||||||
ChatEditor,
|
ChatEditor,
|
||||||
CodeEntityEditor,
|
|
||||||
ImportEditor,
|
ImportEditor,
|
||||||
MediaEditor,
|
MediaEditor,
|
||||||
MenuEditor,
|
MenuEditor,
|
||||||
MiscEditor,
|
MiscEditor,
|
||||||
|
ScriptEditor,
|
||||||
SettingsEditor,
|
SettingsEditor,
|
||||||
TagsEditor
|
TagsEditor,
|
||||||
|
TemplateEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
alias BDS.Desktop.ShellLive.OverlayComponents, as: ShellOverlayComponents
|
alias BDS.Desktop.ShellLive.OverlayComponents, as: ShellOverlayComponents
|
||||||
@@ -175,8 +176,6 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
|> assign(:media_editor_post_picker_queries, %{})
|
|> assign(:media_editor_post_picker_queries, %{})
|
||||||
|> assign(:media_editor_save_states, %{})
|
|> assign(:media_editor_save_states, %{})
|
||||||
|> assign(:media_editor_translation_forms, %{})
|
|> assign(:media_editor_translation_forms, %{})
|
||||||
|> assign(:script_editor_drafts, %{})
|
|
||||||
|> assign(:template_editor_drafts, %{})
|
|
||||||
|> assign(:chat_editor_inputs, %{})
|
|> assign(:chat_editor_inputs, %{})
|
||||||
|> assign(:chat_model_selectors_open, %{})
|
|> assign(:chat_model_selectors_open, %{})
|
||||||
|> assign(:chat_editor_requests, %{})
|
|> assign(:chat_editor_requests, %{})
|
||||||
@@ -527,53 +526,6 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
{:noreply, apply_shell_command(socket, action)}
|
{:noreply, apply_shell_command(socket, action)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("change_script_editor", %{"script_editor" => params}, socket) do
|
|
||||||
{:noreply, CodeEntityEditor.update_script(socket, params, &reload_shell/2)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("save_script_editor", _params, socket) do
|
|
||||||
{:noreply, CodeEntityEditor.save_script(socket, &reload_shell/2, &append_output_entry/5)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("publish_script_editor", %{"id" => _script_id}, socket) do
|
|
||||||
{:noreply,
|
|
||||||
CodeEntityEditor.publish_script(socket, &reload_shell/2, &append_output_entry/5)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("run_script_editor", _params, socket) do
|
|
||||||
{:noreply, CodeEntityEditor.run_script(socket, &reload_shell/2, &append_output_entry/5)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("check_script_editor", _params, socket) do
|
|
||||||
{:noreply, CodeEntityEditor.check_script(socket, &reload_shell/2, &append_output_entry/5)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("delete_script_editor", _params, socket) do
|
|
||||||
{:noreply, CodeEntityEditor.delete_script(socket, &reload_shell/2, &append_output_entry/5)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("change_template_editor", %{"template_editor" => params}, socket) do
|
|
||||||
{:noreply, CodeEntityEditor.update_template(socket, params, &reload_shell/2)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("save_template_editor", _params, socket) do
|
|
||||||
{:noreply, CodeEntityEditor.save_template(socket, &reload_shell/2, &append_output_entry/5)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("publish_template_editor", %{"id" => _template_id}, socket) do
|
|
||||||
{:noreply,
|
|
||||||
CodeEntityEditor.publish_template(socket, &reload_shell/2, &append_output_entry/5)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("validate_template_editor", _params, socket) do
|
|
||||||
{:noreply,
|
|
||||||
CodeEntityEditor.validate_template(socket, &reload_shell/2, &append_output_entry/5)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("delete_template_editor", _params, socket) do
|
|
||||||
{:noreply, CodeEntityEditor.delete_template(socket, &reload_shell/2, &append_output_entry/5)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("change_chat_editor_input", %{"message" => message}, socket) do
|
def handle_event("change_chat_editor_input", %{"message" => message}, socket) do
|
||||||
{:noreply, ChatEditor.update_input(socket, message, &reload_shell/2)}
|
{:noreply, ChatEditor.update_input(socket, message, &reload_shell/2)}
|
||||||
end
|
end
|
||||||
@@ -1312,6 +1264,22 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
{:noreply, append_output_entry(socket, title, message, nil, level)}
|
{:noreply, append_output_entry(socket, title, message, nil, level)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_info({:script_editor_output, title, message, level}, socket) do
|
||||||
|
{:noreply, append_output_entry(socket, title, message, nil, level)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({:template_editor_output, title, message, level}, socket) do
|
||||||
|
{:noreply, append_output_entry(socket, title, message, nil, level)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info(:reload_shell, socket) do
|
||||||
|
{:noreply, reload_shell(socket, socket.assigns.workbench)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({:close_tab, type, id}, socket) do
|
||||||
|
{:noreply, reload_shell(socket, BDS.UI.Workbench.close_tab(socket.assigns.workbench, type, id))}
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
UILocale.put(assigns.page_language)
|
UILocale.put(assigns.page_language)
|
||||||
@@ -1382,7 +1350,6 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
|> assign(:current_tab, current_tab(workbench))
|
|> assign(:current_tab, current_tab(workbench))
|
||||||
|> assign_post_editor()
|
|> assign_post_editor()
|
||||||
|> assign_media_editor()
|
|> assign_media_editor()
|
||||||
|> assign_code_entity_editor()
|
|
||||||
|> assign_chat_editor()
|
|> assign_chat_editor()
|
||||||
|> assign_import_editor()
|
|> assign_import_editor()
|
||||||
|> assign_misc_editor()
|
|> assign_misc_editor()
|
||||||
@@ -1435,10 +1402,6 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
MediaEditor.assign_socket(socket)
|
MediaEditor.assign_socket(socket)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp assign_code_entity_editor(socket) do
|
|
||||||
CodeEntityEditor.assign_socket(socket)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp assign_chat_editor(socket) do
|
defp assign_chat_editor(socket) do
|
||||||
ChatEditor.assign_socket(socket)
|
ChatEditor.assign_socket(socket)
|
||||||
end
|
end
|
||||||
@@ -1621,12 +1584,14 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
defp save_current_tab(%{assigns: %{current_tab: %{type: :scripts}}} = socket) do
|
defp save_current_tab(%{assigns: %{current_tab: %{type: :scripts, id: script_id}}} = socket) do
|
||||||
CodeEntityEditor.save_script(socket, &reload_shell/2, &append_output_entry/5)
|
send_update(ScriptEditor, id: "script-editor-#{script_id}", action: :save)
|
||||||
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
defp save_current_tab(%{assigns: %{current_tab: %{type: :templates}}} = socket) do
|
defp save_current_tab(%{assigns: %{current_tab: %{type: :templates, id: template_id}}} = socket) do
|
||||||
CodeEntityEditor.save_template(socket, &reload_shell/2, &append_output_entry/5)
|
send_update(TemplateEditor, id: "template-editor-#{template_id}", action: :save)
|
||||||
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
defp save_current_tab(socket), do: reload_shell(socket, socket.assigns.workbench)
|
defp save_current_tab(socket), do: reload_shell(socket, socket.assigns.workbench)
|
||||||
@@ -1729,7 +1694,6 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
socket
|
socket
|
||||||
|> assign(:shell_overlay, nil)
|
|> assign(:shell_overlay, nil)
|
||||||
|> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {:scripts, script_id}))
|
|> 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)
|
|> reload_shell(workbench)
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
@@ -1748,10 +1712,6 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
socket
|
socket
|
||||||
|> assign(:shell_overlay, nil)
|
|> assign(:shell_overlay, nil)
|
||||||
|> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {:templates, template_id}))
|
|> 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)
|
|> reload_shell(workbench)
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
|
|||||||
@@ -1,397 +0,0 @@
|
|||||||
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/*")
|
|
||||||
|
|
||||||
@spec assign_socket(term()) :: term()
|
|
||||||
def assign_socket(socket) do
|
|
||||||
socket
|
|
||||||
|> assign(:script_editor, build_script(socket.assigns))
|
|
||||||
|> assign(:template_editor, build_template(socket.assigns))
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec update_script(term(), term(), term()) :: term()
|
|
||||||
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
|
|
||||||
|
|
||||||
@spec save_script(term(), term(), term()) :: term()
|
|
||||||
def save_script(socket, reload, append_output) do
|
|
||||||
persist_script(socket, :save, reload, append_output)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec publish_script(term(), term(), term()) :: term()
|
|
||||||
def publish_script(socket, reload, append_output) do
|
|
||||||
persist_script(socket, :publish, reload, append_output)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec check_script(term(), term(), term()) :: term()
|
|
||||||
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
|
|
||||||
|
|
||||||
@spec run_script(term(), term(), term()) :: term()
|
|
||||||
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
|
|
||||||
|
|
||||||
@spec delete_script(term(), term(), term()) :: term()
|
|
||||||
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
|
|
||||||
|
|
||||||
@spec update_template(term(), term(), term()) :: term()
|
|
||||||
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
|
|
||||||
|
|
||||||
@spec save_template(term(), term(), term()) :: term()
|
|
||||||
def save_template(socket, reload, append_output) do
|
|
||||||
persist_template(socket, :save, reload, append_output)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec publish_template(term(), term(), term()) :: term()
|
|
||||||
def publish_template(socket, reload, append_output) do
|
|
||||||
persist_template(socket, :publish, reload, append_output)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec validate_template(term(), term(), term()) :: term()
|
|
||||||
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
|
|
||||||
|
|
||||||
@spec delete_template(term(), term(), term()) :: term()
|
|
||||||
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
|
|
||||||
|
|
||||||
@spec build_script(term()) :: term()
|
|
||||||
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"]),
|
|
||||||
status: script.status || :draft,
|
|
||||||
can_publish?: script.status == :draft,
|
|
||||||
created_at: script.created_at,
|
|
||||||
updated_at: script.updated_at
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_script(_assigns), do: nil
|
|
||||||
|
|
||||||
@spec build_template(term()) :: term()
|
|
||||||
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"],
|
|
||||||
status: template.status || :draft,
|
|
||||||
can_publish?: template.status == :draft,
|
|
||||||
created_at: template.created_at,
|
|
||||||
updated_at: template.updated_at
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_template(_assigns), do: nil
|
|
||||||
|
|
||||||
@spec status_label(term()) :: term()
|
|
||||||
def status_label(status), do: ShellData.dashboard_status_label(status)
|
|
||||||
|
|
||||||
@spec translated(term(), term()) :: term()
|
|
||||||
def translated(text, bindings \\ %{}),
|
|
||||||
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
|
||||||
|
|
||||||
@spec format_timestamp(term()) :: term()
|
|
||||||
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: BDS.BoundedAtoms.script_kind(draft["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 persist_script(socket, action, 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))
|
|
||||||
|> maybe_publish_script(script.id, action) 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
|
|
||||||
|
|
||||||
defp persist_template(socket, action, 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))
|
|
||||||
|> maybe_publish_template(template.id, action) 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
|
|
||||||
|
|
||||||
defp maybe_publish_script({:ok, _script}, script_id, :publish), do: Scripts.publish_script(script_id)
|
|
||||||
defp maybe_publish_script(result, _script_id, _action), do: result
|
|
||||||
|
|
||||||
defp maybe_publish_template({:ok, _template}, template_id, :publish),
|
|
||||||
do: Templates.publish_template(template_id)
|
|
||||||
|
|
||||||
defp maybe_publish_template(result, _template_id, _action), do: result
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
<div class="scripts-view-shell editor" data-testid="script-editor">
|
|
||||||
<div class="editor-header scripts-header">
|
|
||||||
<div class="editor-tabs"><div class="editor-tab active"><span class="editor-tab-title"><%= @script_editor.title %></span></div></div>
|
|
||||||
<div class="editor-actions">
|
|
||||||
<span class={[
|
|
||||||
"status-badge",
|
|
||||||
"status-#{@script_editor.status}"
|
|
||||||
]} data-testid="script-status-badge"><%= status_label(@script_editor.status) %></span>
|
|
||||||
<%= if @script_editor.can_publish? do %>
|
|
||||||
<button class="success" data-testid="script-publish-button" type="button" phx-click="publish_script_editor" phx-value-id={@script_editor.id}><%= translated("Publish") %></button>
|
|
||||||
<% end %>
|
|
||||||
<button class="secondary scripts-save-button" type="button" phx-click="save_script_editor"><%= translated("Save") %></button>
|
|
||||||
<button class="secondary scripts-run-button" type="button" phx-click="run_script_editor"><%= translated("Run") %></button>
|
|
||||||
<button class="secondary scripts-check-button" type="button" phx-click="check_script_editor"><%= translated("Check Syntax") %></button>
|
|
||||||
<button class="secondary danger" type="button" phx-click="delete_script_editor"><%= translated("Delete") %></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form class="editor-content scripts-view" phx-change="change_script_editor">
|
|
||||||
<div class="editor-header-row scripts-meta-row">
|
|
||||||
<div class="editor-meta">
|
|
||||||
<div class="editor-field-row">
|
|
||||||
<div class="editor-field"><label><%= translated("Title") %></label><input type="text" name="script_editor[title]" value={@script_editor.title} /></div>
|
|
||||||
<div class="editor-field"><label><%= translated("Slug") %></label><input type="text" name="script_editor[slug]" value={@script_editor.slug} /></div>
|
|
||||||
</div>
|
|
||||||
<div class="editor-field-row">
|
|
||||||
<div class="editor-field"><label><%= translated("Kind") %></label><select name="script_editor[kind]"><option value="utility" selected={@script_editor.kind == "utility"}>utility</option><option value="macro" selected={@script_editor.kind == "macro"}>macro</option><option value="transform" selected={@script_editor.kind == "transform"}>transform</option></select></div>
|
|
||||||
<div class="editor-field"><label><%= translated("Entrypoint") %></label><select name="script_editor[entrypoint]"><%= for entrypoint <- @script_editor.entrypoints do %><option value={entrypoint} selected={entrypoint == @script_editor.entrypoint}><%= entrypoint %></option><% end %></select></div>
|
|
||||||
<div class="editor-field scripts-enabled-field"><label><input type="checkbox" name="script_editor[enabled]" checked={@script_editor.enabled} /> <%= translated("Enabled") %></label></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="editor-body scripts-editor">
|
|
||||||
<div class="editor-toolbar scripts-toolbar"><div class="editor-toolbar-left"><label><%= translated("Content") %></label></div></div>
|
|
||||||
<div
|
|
||||||
id={"script-editor-monaco-shell-#{@script_editor.id}"}
|
|
||||||
class="scripts-monaco monaco-editor-shell"
|
|
||||||
phx-hook="MonacoEditor"
|
|
||||||
data-monaco-editor-id={@script_editor.id}
|
|
||||||
data-monaco-input-id={"script-editor-content-#{@script_editor.id}"}
|
|
||||||
data-monaco-language="lua"
|
|
||||||
data-monaco-word-wrap="on"
|
|
||||||
>
|
|
||||||
<div id={"script-editor-monaco-#{@script_editor.id}"} class="monaco-editor-instance" phx-update="ignore"></div>
|
|
||||||
<textarea id={"script-editor-content-#{@script_editor.id}"} class="monaco-editor-input code-editor-textarea" name="script_editor[content]" spellcheck="false"><%= @script_editor.content %></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="editor-footer"><span class="text-muted text-small"><%= translated("Created") %>: <%= format_timestamp(@script_editor.created_at) %></span><span class="text-muted text-small"><%= translated("Updated") %>: <%= format_timestamp(@script_editor.updated_at) %></span></div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
<div class="templates-view-shell editor" data-testid="template-editor">
|
|
||||||
<div class="editor-header templates-header">
|
|
||||||
<div class="editor-tabs"><div class="editor-tab active"><span class="editor-tab-title"><%= @template_editor.title %></span></div></div>
|
|
||||||
<div class="editor-actions">
|
|
||||||
<span class={[
|
|
||||||
"status-badge",
|
|
||||||
"status-#{@template_editor.status}"
|
|
||||||
]} data-testid="template-status-badge"><%= status_label(@template_editor.status) %></span>
|
|
||||||
<%= if @template_editor.can_publish? do %>
|
|
||||||
<button class="success" data-testid="template-publish-button" type="button" phx-click="publish_template_editor" phx-value-id={@template_editor.id}><%= translated("Publish") %></button>
|
|
||||||
<% end %>
|
|
||||||
<button class="secondary templates-save-button" type="button" phx-click="save_template_editor"><%= translated("Save") %></button>
|
|
||||||
<button class="secondary templates-validate-button" type="button" phx-click="validate_template_editor"><%= translated("Validate") %></button>
|
|
||||||
<button class="secondary danger" type="button" phx-click="delete_template_editor"><%= translated("Delete") %></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form class="editor-content templates-view" phx-change="change_template_editor">
|
|
||||||
<div class="editor-header-row templates-meta-row">
|
|
||||||
<div class="editor-meta">
|
|
||||||
<div class="editor-field-row">
|
|
||||||
<div class="editor-field"><label><%= translated("Title") %></label><input type="text" name="template_editor[title]" value={@template_editor.title} /></div>
|
|
||||||
<div class="editor-field"><label><%= translated("Slug") %></label><input type="text" name="template_editor[slug]" value={@template_editor.slug} /></div>
|
|
||||||
</div>
|
|
||||||
<div class="editor-field-row">
|
|
||||||
<div class="editor-field"><label><%= translated("Kind") %></label><select name="template_editor[kind]"><option value="post" selected={@template_editor.kind == :post or @template_editor.kind == "post"}>post</option><option value="list" selected={@template_editor.kind == :list or @template_editor.kind == "list"}>list</option><option value="not-found" selected={@template_editor.kind == :"not-found" or @template_editor.kind == "not-found"}>not-found</option><option value="partial" selected={@template_editor.kind == :partial or @template_editor.kind == "partial"}>partial</option></select></div>
|
|
||||||
<div class="editor-field templates-enabled-field"><label><input type="checkbox" name="template_editor[enabled]" checked={@template_editor.enabled} /> <%= translated("Enabled") %></label></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="editor-body templates-editor">
|
|
||||||
<div class="editor-toolbar templates-toolbar"><div class="editor-toolbar-left"><label><%= translated("Content") %></label></div></div>
|
|
||||||
<div
|
|
||||||
id={"template-editor-monaco-shell-#{@template_editor.id}"}
|
|
||||||
class="templates-monaco monaco-editor-shell"
|
|
||||||
phx-hook="MonacoEditor"
|
|
||||||
data-monaco-editor-id={@template_editor.id}
|
|
||||||
data-monaco-input-id={"template-editor-content-#{@template_editor.id}"}
|
|
||||||
data-monaco-language="liquid"
|
|
||||||
data-monaco-word-wrap="on"
|
|
||||||
>
|
|
||||||
<div id={"template-editor-monaco-#{@template_editor.id}"} class="monaco-editor-instance" phx-update="ignore"></div>
|
|
||||||
<textarea id={"template-editor-content-#{@template_editor.id}"} class="monaco-editor-input code-editor-textarea" name="template_editor[content]" spellcheck="false"><%= @template_editor.content %></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="editor-footer"><span class="text-muted text-small"><%= translated("Created") %>: <%= format_timestamp(@template_editor.created_at) %></span><span class="text-muted text-small"><%= translated("Updated") %>: <%= format_timestamp(@template_editor.updated_at) %></span></div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
@@ -409,11 +409,11 @@
|
|||||||
<% @current_tab.type == :tags and @current_project -> %>
|
<% @current_tab.type == :tags and @current_project -> %>
|
||||||
<.live_component module={TagsEditor} id="tags-editor" project_id={@current_project.id} current_tab={@current_tab} tab_meta={@tab_meta} />
|
<.live_component module={TagsEditor} id="tags-editor" project_id={@current_project.id} current_tab={@current_tab} tab_meta={@tab_meta} />
|
||||||
|
|
||||||
<% @current_tab.type == :scripts and @script_editor -> %>
|
<% @current_tab.type == :scripts -> %>
|
||||||
<CodeEntityEditor.script_editor script_editor={@script_editor} />
|
<.live_component module={ScriptEditor} id={"script-editor-#{@current_tab.id}"} current_tab={@current_tab} />
|
||||||
|
|
||||||
<% @current_tab.type == :templates and @template_editor -> %>
|
<% @current_tab.type == :templates -> %>
|
||||||
<CodeEntityEditor.template_editor template_editor={@template_editor} />
|
<.live_component module={TemplateEditor} id={"template-editor-#{@current_tab.id}"} current_tab={@current_tab} />
|
||||||
|
|
||||||
<% @current_tab.type == :chat and @chat_editor -> %>
|
<% @current_tab.type == :chat and @chat_editor -> %>
|
||||||
<ChatEditor.chat_editor chat_editor={@chat_editor} />
|
<ChatEditor.chat_editor chat_editor={@chat_editor} />
|
||||||
|
|||||||
293
lib/bds/desktop/shell_live/script_editor.ex
Normal file
293
lib/bds/desktop/shell_live/script_editor.ex
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
defmodule BDS.Desktop.ShellLive.ScriptEditor do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
use Phoenix.LiveComponent
|
||||||
|
|
||||||
|
alias BDS.{Scripts, Scripting}
|
||||||
|
alias BDS.Desktop.ShellData
|
||||||
|
alias BDS.Scripts.Script
|
||||||
|
|
||||||
|
embed_templates("script_editor_html/*")
|
||||||
|
|
||||||
|
@spec update(map(), Phoenix.LiveView.Socket.t()) :: {:ok, Phoenix.LiveView.Socket.t()}
|
||||||
|
@impl true
|
||||||
|
def update(%{action: :save} = assigns, socket) do
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> assign(Map.drop(assigns, [:action]))
|
||||||
|
|> do_save()
|
||||||
|
|
||||||
|
{:ok, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(assigns, socket) do
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> assign(assigns)
|
||||||
|
|> build_data()
|
||||||
|
|
||||||
|
{:ok, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec render(map()) :: Phoenix.LiveView.Rendered.t()
|
||||||
|
@impl true
|
||||||
|
def render(%{script_editor: nil} = assigns), do: ~H""
|
||||||
|
|
||||||
|
def render(assigns) do
|
||||||
|
script_editor(assigns)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec handle_event(String.t(), map(), Phoenix.LiveView.Socket.t()) ::
|
||||||
|
{:noreply, Phoenix.LiveView.Socket.t()}
|
||||||
|
@impl true
|
||||||
|
def handle_event("change_script_editor", %{"script_editor" => params}, socket) do
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> assign(:draft, normalize_params(params))
|
||||||
|
|> build_data()
|
||||||
|
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("save_script_editor", _params, socket) do
|
||||||
|
{:noreply, do_save(socket)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("publish_script_editor", _params, socket) do
|
||||||
|
{:noreply, do_publish(socket)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("run_script_editor", _params, socket) do
|
||||||
|
{:noreply, do_run(socket)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("check_script_editor", _params, socket) do
|
||||||
|
{:noreply, do_check(socket)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("delete_script_editor", _params, socket) do
|
||||||
|
{:noreply, do_delete(socket)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_data(socket) do
|
||||||
|
script_id = socket.assigns.current_tab.id
|
||||||
|
|
||||||
|
case Scripts.get_script(script_id) do
|
||||||
|
nil ->
|
||||||
|
assign(socket, :script_editor, nil)
|
||||||
|
|
||||||
|
%Script{} = script ->
|
||||||
|
draft = current_draft(socket.assigns, script)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
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"]),
|
||||||
|
status: script.status || :draft,
|
||||||
|
can_publish?: script.status == :draft,
|
||||||
|
created_at: script.created_at,
|
||||||
|
updated_at: script.updated_at
|
||||||
|
}
|
||||||
|
|
||||||
|
assign(socket, :script_editor, data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_save(socket) do
|
||||||
|
script_id = socket.assigns.current_tab.id
|
||||||
|
|
||||||
|
case Scripts.get_script(script_id) do
|
||||||
|
nil ->
|
||||||
|
socket
|
||||||
|
|
||||||
|
%Script{} = script ->
|
||||||
|
draft = current_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(:draft, nil)
|
||||||
|
|> build_data()
|
||||||
|
|> notify_output(translated("Scripts"), translated("Script saved"))
|
||||||
|
|> notify_reload()
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
socket
|
||||||
|
|> notify_output(translated("Scripts"), inspect(reason), "error")
|
||||||
|
|> notify_reload()
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
socket
|
||||||
|
|> notify_output(translated("Scripts"), inspect(reason), "error")
|
||||||
|
|> notify_reload()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_publish(socket) do
|
||||||
|
script_id = socket.assigns.current_tab.id
|
||||||
|
|
||||||
|
case Scripts.get_script(script_id) do
|
||||||
|
nil ->
|
||||||
|
socket
|
||||||
|
|
||||||
|
%Script{} = script ->
|
||||||
|
draft = current_draft(socket.assigns, script)
|
||||||
|
|
||||||
|
case Scripting.validate(draft["content"] || "") do
|
||||||
|
:ok ->
|
||||||
|
case Scripts.update_script(script.id, script_attrs(draft)) do
|
||||||
|
{:ok, _updated} ->
|
||||||
|
case Scripts.publish_script(script.id) do
|
||||||
|
{:ok, _published} ->
|
||||||
|
socket
|
||||||
|
|> assign(:draft, nil)
|
||||||
|
|> build_data()
|
||||||
|
|> notify_output(translated("Scripts"), translated("Script published"))
|
||||||
|
|> notify_reload()
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
socket
|
||||||
|
|> notify_output(translated("Scripts"), inspect(reason), "error")
|
||||||
|
|> notify_reload()
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
socket
|
||||||
|
|> notify_output(translated("Scripts"), inspect(reason), "error")
|
||||||
|
|> notify_reload()
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
socket
|
||||||
|
|> notify_output(translated("Scripts"), inspect(reason), "error")
|
||||||
|
|> notify_reload()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_check(socket) do
|
||||||
|
script_id = socket.assigns.current_tab.id
|
||||||
|
|
||||||
|
case Scripts.get_script(script_id) do
|
||||||
|
nil ->
|
||||||
|
socket
|
||||||
|
|
||||||
|
%Script{} = script ->
|
||||||
|
case Scripting.validate(current_draft(socket.assigns, script)["content"] || "") do
|
||||||
|
:ok ->
|
||||||
|
notify_output(socket, translated("Scripts"), translated("Syntax is valid"))
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
notify_output(socket, translated("Scripts"), inspect(reason), "error")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_run(socket) do
|
||||||
|
script_id = socket.assigns.current_tab.id
|
||||||
|
|
||||||
|
case Scripts.get_script(script_id) do
|
||||||
|
nil ->
|
||||||
|
socket
|
||||||
|
|
||||||
|
%Script{} = script ->
|
||||||
|
draft = current_draft(socket.assigns, script)
|
||||||
|
|
||||||
|
case Scripting.execute_project_script(
|
||||||
|
script.project_id,
|
||||||
|
draft["content"] || "",
|
||||||
|
draft["entrypoint"] || "main",
|
||||||
|
[]
|
||||||
|
) do
|
||||||
|
{:ok, result} ->
|
||||||
|
notify_output(socket, translated("Scripts"), inspect(result))
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
notify_output(socket, translated("Scripts"), inspect(reason), "error")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_delete(socket) do
|
||||||
|
script_id = socket.assigns.current_tab.id
|
||||||
|
|
||||||
|
case Scripts.delete_script(script_id) do
|
||||||
|
{:ok, _deleted} ->
|
||||||
|
send(self(), {:close_tab, :scripts, script_id})
|
||||||
|
socket
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
socket
|
||||||
|
|> notify_output(translated("Scripts"), inspect(reason), "error")
|
||||||
|
|> notify_reload()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp current_draft(%{draft: draft}, _script) when is_map(draft), do: draft
|
||||||
|
|
||||||
|
defp current_draft(_assigns, %Script{} = script) do
|
||||||
|
%{
|
||||||
|
"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 normalize_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 script_attrs(draft) do
|
||||||
|
%{
|
||||||
|
title: draft["title"],
|
||||||
|
slug: draft["slug"],
|
||||||
|
kind: BDS.BoundedAtoms.script_kind(draft["kind"], :utility),
|
||||||
|
entrypoint: draft["entrypoint"],
|
||||||
|
enabled: draft["enabled"],
|
||||||
|
content: draft["content"]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
defp notify_output(socket, title, message, level \\ "info") do
|
||||||
|
send(self(), {:script_editor_output, title, message, level})
|
||||||
|
socket
|
||||||
|
end
|
||||||
|
|
||||||
|
defp notify_reload(socket) do
|
||||||
|
send(self(), :reload_shell)
|
||||||
|
socket
|
||||||
|
end
|
||||||
|
|
||||||
|
defp translated(text, bindings \\ %{}),
|
||||||
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
|
end
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<div class="scripts-view-shell editor" data-testid="script-editor">
|
||||||
|
<div class="editor-header scripts-header">
|
||||||
|
<div class="editor-tabs"><div class="editor-tab active"><span class="editor-tab-title"><%= @script_editor.title %></span></div></div>
|
||||||
|
<div class="editor-actions">
|
||||||
|
<span class={[
|
||||||
|
"status-badge",
|
||||||
|
"status-#{@script_editor.status}"
|
||||||
|
]} data-testid="script-status-badge"><%= BDS.Desktop.ShellData.dashboard_status_label(@script_editor.status) %></span>
|
||||||
|
<%= if @script_editor.can_publish? do %>
|
||||||
|
<button class="success" data-testid="script-publish-button" type="button" phx-click="publish_script_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Publish", %{}, BDS.Desktop.UILocale.current()) %></button>
|
||||||
|
<% end %>
|
||||||
|
<button class="secondary scripts-save-button" type="button" phx-click="save_script_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Save", %{}, BDS.Desktop.UILocale.current()) %></button>
|
||||||
|
<button class="secondary scripts-run-button" type="button" phx-click="run_script_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Run", %{}, BDS.Desktop.UILocale.current()) %></button>
|
||||||
|
<button class="secondary scripts-check-button" type="button" phx-click="check_script_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Check Syntax", %{}, BDS.Desktop.UILocale.current()) %></button>
|
||||||
|
<button class="secondary danger" type="button" phx-click="delete_script_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Delete", %{}, BDS.Desktop.UILocale.current()) %></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form class="editor-content scripts-view" phx-change="change_script_editor" phx-target={@myself}>
|
||||||
|
<div class="editor-header-row scripts-meta-row">
|
||||||
|
<div class="editor-meta">
|
||||||
|
<div class="editor-field-row">
|
||||||
|
<div class="editor-field"><label><%= BDS.Desktop.ShellData.translate("Title", %{}, BDS.Desktop.UILocale.current()) %></label><input type="text" name="script_editor[title]" value={@script_editor.title} /></div>
|
||||||
|
<div class="editor-field"><label><%= BDS.Desktop.ShellData.translate("Slug", %{}, BDS.Desktop.UILocale.current()) %></label><input type="text" name="script_editor[slug]" value={@script_editor.slug} /></div>
|
||||||
|
</div>
|
||||||
|
<div class="editor-field-row">
|
||||||
|
<div class="editor-field"><label><%= BDS.Desktop.ShellData.translate("Kind", %{}, BDS.Desktop.UILocale.current()) %></label><select name="script_editor[kind]"><option value="utility" selected={@script_editor.kind == "utility"}>utility</option><option value="macro" selected={@script_editor.kind == "macro"}>macro</option><option value="transform" selected={@script_editor.kind == "transform"}>transform</option></select></div>
|
||||||
|
<div class="editor-field"><label><%= BDS.Desktop.ShellData.translate("Entrypoint", %{}, BDS.Desktop.UILocale.current()) %></label><select name="script_editor[entrypoint]"><%= for entrypoint <- @script_editor.entrypoints do %><option value={entrypoint} selected={entrypoint == @script_editor.entrypoint}><%= entrypoint %></option><% end %></select></div>
|
||||||
|
<div class="editor-field scripts-enabled-field"><label><input type="checkbox" name="script_editor[enabled]" checked={@script_editor.enabled} /> <%= BDS.Desktop.ShellData.translate("Enabled", %{}, BDS.Desktop.UILocale.current()) %></label></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="editor-body scripts-editor">
|
||||||
|
<div class="editor-toolbar scripts-toolbar"><div class="editor-toolbar-left"><label><%= BDS.Desktop.ShellData.translate("Content", %{}, BDS.Desktop.UILocale.current()) %></label></div></div>
|
||||||
|
<div
|
||||||
|
id={"script-editor-monaco-shell-#{@script_editor.id}"}
|
||||||
|
class="scripts-monaco monaco-editor-shell"
|
||||||
|
phx-hook="MonacoEditor"
|
||||||
|
data-monaco-editor-id={@script_editor.id}
|
||||||
|
data-monaco-input-id={"script-editor-content-#{@script_editor.id}"}
|
||||||
|
data-monaco-language="lua"
|
||||||
|
data-monaco-word-wrap="on"
|
||||||
|
>
|
||||||
|
<div id={"script-editor-monaco-#{@script_editor.id}"} class="monaco-editor-instance" phx-update="ignore"></div>
|
||||||
|
<textarea id={"script-editor-content-#{@script_editor.id}"} class="monaco-editor-input code-editor-textarea" name="script_editor[content]" spellcheck="false"><%= @script_editor.content %></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="editor-footer"><span class="text-muted text-small"><%= BDS.Desktop.ShellData.translate("Created", %{}, BDS.Desktop.UILocale.current()) %>: <%= BDS.Persistence.timestamp_to_iso8601(@script_editor.created_at) %></span><span class="text-muted text-small"><%= BDS.Desktop.ShellData.translate("Updated", %{}, BDS.Desktop.UILocale.current()) %>: <%= BDS.Persistence.timestamp_to_iso8601(@script_editor.updated_at) %></span></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
241
lib/bds/desktop/shell_live/template_editor.ex
Normal file
241
lib/bds/desktop/shell_live/template_editor.ex
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
defmodule BDS.Desktop.ShellLive.TemplateEditor do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
use Phoenix.LiveComponent
|
||||||
|
|
||||||
|
alias BDS.{MCP, Templates}
|
||||||
|
alias BDS.Desktop.ShellData
|
||||||
|
alias BDS.Templates.Template
|
||||||
|
|
||||||
|
embed_templates("template_editor_html/*")
|
||||||
|
|
||||||
|
@spec update(map(), Phoenix.LiveView.Socket.t()) :: {:ok, Phoenix.LiveView.Socket.t()}
|
||||||
|
@impl true
|
||||||
|
def update(%{action: :save} = assigns, socket) do
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> assign(Map.drop(assigns, [:action]))
|
||||||
|
|> do_save()
|
||||||
|
|
||||||
|
{:ok, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(assigns, socket) do
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> assign(assigns)
|
||||||
|
|> build_data()
|
||||||
|
|
||||||
|
{:ok, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec render(map()) :: Phoenix.LiveView.Rendered.t()
|
||||||
|
@impl true
|
||||||
|
def render(%{template_editor: nil} = assigns), do: ~H""
|
||||||
|
|
||||||
|
def render(assigns) do
|
||||||
|
template_editor(assigns)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec handle_event(String.t(), map(), Phoenix.LiveView.Socket.t()) ::
|
||||||
|
{:noreply, Phoenix.LiveView.Socket.t()}
|
||||||
|
@impl true
|
||||||
|
def handle_event("change_template_editor", %{"template_editor" => params}, socket) do
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> assign(:draft, normalize_params(params))
|
||||||
|
|> build_data()
|
||||||
|
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("save_template_editor", _params, socket) do
|
||||||
|
{:noreply, do_save(socket)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("publish_template_editor", _params, socket) do
|
||||||
|
{:noreply, do_publish(socket)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("validate_template_editor", _params, socket) do
|
||||||
|
{:noreply, do_validate(socket)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("delete_template_editor", _params, socket) do
|
||||||
|
{:noreply, do_delete(socket)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_data(socket) do
|
||||||
|
template_id = socket.assigns.current_tab.id
|
||||||
|
|
||||||
|
case Templates.get_template(template_id) do
|
||||||
|
nil ->
|
||||||
|
assign(socket, :template_editor, nil)
|
||||||
|
|
||||||
|
%Template{} = template ->
|
||||||
|
draft = current_draft(socket.assigns, template)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
id: template.id,
|
||||||
|
title: draft["title"],
|
||||||
|
slug: draft["slug"],
|
||||||
|
kind: draft["kind"],
|
||||||
|
enabled: draft["enabled"],
|
||||||
|
content: draft["content"],
|
||||||
|
status: template.status || :draft,
|
||||||
|
can_publish?: template.status == :draft,
|
||||||
|
created_at: template.created_at,
|
||||||
|
updated_at: template.updated_at
|
||||||
|
}
|
||||||
|
|
||||||
|
assign(socket, :template_editor, data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_save(socket) do
|
||||||
|
template_id = socket.assigns.current_tab.id
|
||||||
|
|
||||||
|
case Templates.get_template(template_id) do
|
||||||
|
nil ->
|
||||||
|
socket
|
||||||
|
|
||||||
|
%Template{} = template ->
|
||||||
|
draft = current_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(:draft, nil)
|
||||||
|
|> build_data()
|
||||||
|
|> notify_output(translated("Templates"), translated("Template saved"))
|
||||||
|
|> notify_reload()
|
||||||
|
else
|
||||||
|
{:ok, %{valid: false, errors: errors}} ->
|
||||||
|
socket
|
||||||
|
|> notify_output(translated("Templates"), Enum.join(errors, "; "), "error")
|
||||||
|
|> notify_reload()
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
socket
|
||||||
|
|> notify_output(translated("Templates"), inspect(reason), "error")
|
||||||
|
|> notify_reload()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_publish(socket) do
|
||||||
|
template_id = socket.assigns.current_tab.id
|
||||||
|
|
||||||
|
case Templates.get_template(template_id) do
|
||||||
|
nil ->
|
||||||
|
socket
|
||||||
|
|
||||||
|
%Template{} = template ->
|
||||||
|
draft = current_draft(socket.assigns, template)
|
||||||
|
|
||||||
|
with {:ok, %{valid: true}} <- MCP.validate_template(draft["content"] || ""),
|
||||||
|
{:ok, _updated} <- Templates.update_template(template.id, template_attrs(draft)),
|
||||||
|
{:ok, _published} <- Templates.publish_template(template.id) do
|
||||||
|
socket
|
||||||
|
|> assign(:draft, nil)
|
||||||
|
|> build_data()
|
||||||
|
|> notify_output(translated("Templates"), translated("Template published"))
|
||||||
|
|> notify_reload()
|
||||||
|
else
|
||||||
|
{:ok, %{valid: false, errors: errors}} ->
|
||||||
|
socket
|
||||||
|
|> notify_output(translated("Templates"), Enum.join(errors, "; "), "error")
|
||||||
|
|> notify_reload()
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
socket
|
||||||
|
|> notify_output(translated("Templates"), inspect(reason), "error")
|
||||||
|
|> notify_reload()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_validate(socket) do
|
||||||
|
template_id = socket.assigns.current_tab.id
|
||||||
|
|
||||||
|
case Templates.get_template(template_id) do
|
||||||
|
nil ->
|
||||||
|
socket
|
||||||
|
|
||||||
|
%Template{} = template ->
|
||||||
|
case MCP.validate_template(current_draft(socket.assigns, template)["content"] || "") do
|
||||||
|
{:ok, %{valid: true}} ->
|
||||||
|
notify_output(socket, translated("Templates"), translated("Template syntax is valid"))
|
||||||
|
|
||||||
|
{:ok, %{valid: false, errors: errors}} ->
|
||||||
|
notify_output(socket, translated("Templates"), Enum.join(errors, "; "), "error")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_delete(socket) do
|
||||||
|
template_id = socket.assigns.current_tab.id
|
||||||
|
|
||||||
|
case Templates.delete_template(template_id, force: true) do
|
||||||
|
{:ok, _deleted} ->
|
||||||
|
send(self(), {:close_tab, :templates, template_id})
|
||||||
|
socket
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
socket
|
||||||
|
|> notify_output(translated("Templates"), inspect(reason), "error")
|
||||||
|
|> notify_reload()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp current_draft(%{draft: draft}, _template) when is_map(draft), do: draft
|
||||||
|
|
||||||
|
defp current_draft(_assigns, %Template{} = template) do
|
||||||
|
%{
|
||||||
|
"title" => template.title || "",
|
||||||
|
"slug" => template.slug || "",
|
||||||
|
"kind" => to_string(template.kind || :post),
|
||||||
|
"enabled" => template.enabled != false,
|
||||||
|
"content" => template.content || ""
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_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 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 notify_output(socket, title, message, level \\ "info") do
|
||||||
|
send(self(), {:template_editor_output, title, message, level})
|
||||||
|
socket
|
||||||
|
end
|
||||||
|
|
||||||
|
defp notify_reload(socket) do
|
||||||
|
send(self(), :reload_shell)
|
||||||
|
socket
|
||||||
|
end
|
||||||
|
|
||||||
|
defp translated(text, bindings \\ %{}),
|
||||||
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
|
end
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<div class="templates-view-shell editor" data-testid="template-editor">
|
||||||
|
<div class="editor-header templates-header">
|
||||||
|
<div class="editor-tabs"><div class="editor-tab active"><span class="editor-tab-title"><%= @template_editor.title %></span></div></div>
|
||||||
|
<div class="editor-actions">
|
||||||
|
<span class={[
|
||||||
|
"status-badge",
|
||||||
|
"status-#{@template_editor.status}"
|
||||||
|
]} data-testid="template-status-badge"><%= BDS.Desktop.ShellData.dashboard_status_label(@template_editor.status) %></span>
|
||||||
|
<%= if @template_editor.can_publish? do %>
|
||||||
|
<button class="success" data-testid="template-publish-button" type="button" phx-click="publish_template_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Publish", %{}, BDS.Desktop.UILocale.current()) %></button>
|
||||||
|
<% end %>
|
||||||
|
<button class="secondary templates-save-button" type="button" phx-click="save_template_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Save", %{}, BDS.Desktop.UILocale.current()) %></button>
|
||||||
|
<button class="secondary templates-validate-button" type="button" phx-click="validate_template_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Validate", %{}, BDS.Desktop.UILocale.current()) %></button>
|
||||||
|
<button class="secondary danger" type="button" phx-click="delete_template_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Delete", %{}, BDS.Desktop.UILocale.current()) %></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form class="editor-content templates-view" phx-change="change_template_editor" phx-target={@myself}>
|
||||||
|
<div class="editor-header-row templates-meta-row">
|
||||||
|
<div class="editor-meta">
|
||||||
|
<div class="editor-field-row">
|
||||||
|
<div class="editor-field"><label><%= BDS.Desktop.ShellData.translate("Title", %{}, BDS.Desktop.UILocale.current()) %></label><input type="text" name="template_editor[title]" value={@template_editor.title} /></div>
|
||||||
|
<div class="editor-field"><label><%= BDS.Desktop.ShellData.translate("Slug", %{}, BDS.Desktop.UILocale.current()) %></label><input type="text" name="template_editor[slug]" value={@template_editor.slug} /></div>
|
||||||
|
</div>
|
||||||
|
<div class="editor-field-row">
|
||||||
|
<div class="editor-field"><label><%= BDS.Desktop.ShellData.translate("Kind", %{}, BDS.Desktop.UILocale.current()) %></label><select name="template_editor[kind]"><option value="post" selected={@template_editor.kind == :post or @template_editor.kind == "post"}>post</option><option value="list" selected={@template_editor.kind == :list or @template_editor.kind == "list"}>list</option><option value="not-found" selected={@template_editor.kind == :"not-found" or @template_editor.kind == "not-found"}>not-found</option><option value="partial" selected={@template_editor.kind == :partial or @template_editor.kind == "partial"}>partial</option></select></div>
|
||||||
|
<div class="editor-field templates-enabled-field"><label><input type="checkbox" name="template_editor[enabled]" checked={@template_editor.enabled} /> <%= BDS.Desktop.ShellData.translate("Enabled", %{}, BDS.Desktop.UILocale.current()) %></label></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="editor-body templates-editor">
|
||||||
|
<div class="editor-toolbar templates-toolbar"><div class="editor-toolbar-left"><label><%= BDS.Desktop.ShellData.translate("Content", %{}, BDS.Desktop.UILocale.current()) %></label></div></div>
|
||||||
|
<div
|
||||||
|
id={"template-editor-monaco-shell-#{@template_editor.id}"}
|
||||||
|
class="templates-monaco monaco-editor-shell"
|
||||||
|
phx-hook="MonacoEditor"
|
||||||
|
data-monaco-editor-id={@template_editor.id}
|
||||||
|
data-monaco-input-id={"template-editor-content-#{@template_editor.id}"}
|
||||||
|
data-monaco-language="liquid"
|
||||||
|
data-monaco-word-wrap="on"
|
||||||
|
>
|
||||||
|
<div id={"template-editor-monaco-#{@template_editor.id}"} class="monaco-editor-instance" phx-update="ignore"></div>
|
||||||
|
<textarea id={"template-editor-content-#{@template_editor.id}"} class="monaco-editor-input code-editor-textarea" name="template_editor[content]" spellcheck="false"><%= @template_editor.content %></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="editor-footer"><span class="text-muted text-small"><%= BDS.Desktop.ShellData.translate("Created", %{}, BDS.Desktop.UILocale.current()) %>: <%= BDS.Persistence.timestamp_to_iso8601(@template_editor.created_at) %></span><span class="text-muted text-small"><%= BDS.Desktop.ShellData.translate("Updated", %{}, BDS.Desktop.UILocale.current()) %>: <%= BDS.Persistence.timestamp_to_iso8601(@template_editor.updated_at) %></span></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -2344,7 +2344,12 @@ defmodule BDS.Desktop.ShellLiveTest do
|
|||||||
|
|
||||||
assert draft_script_html =~ ~s(data-testid="script-publish-button")
|
assert draft_script_html =~ ~s(data-testid="script-publish-button")
|
||||||
assert draft_script_html =~ ~s(class="success")
|
assert draft_script_html =~ ~s(class="success")
|
||||||
draft_script_html = render_click(view, "publish_script_editor", %{"id" => draft_script.id})
|
|
||||||
|
draft_script_html =
|
||||||
|
view
|
||||||
|
|> element("[data-testid='script-publish-button']")
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
assert Scripts.get_script(draft_script.id).status == :published
|
assert Scripts.get_script(draft_script.id).status == :published
|
||||||
refute draft_script_html =~ ~s(data-testid="script-publish-button")
|
refute draft_script_html =~ ~s(data-testid="script-publish-button")
|
||||||
assert draft_script_html =~ ~s(data-testid="script-status-badge")
|
assert draft_script_html =~ ~s(data-testid="script-status-badge")
|
||||||
@@ -2360,7 +2365,12 @@ defmodule BDS.Desktop.ShellLiveTest do
|
|||||||
|
|
||||||
assert draft_template_html =~ ~s(data-testid="template-publish-button")
|
assert draft_template_html =~ ~s(data-testid="template-publish-button")
|
||||||
assert draft_template_html =~ ~s(class="success")
|
assert draft_template_html =~ ~s(class="success")
|
||||||
draft_template_html = render_click(view, "publish_template_editor", %{"id" => draft_template.id})
|
|
||||||
|
draft_template_html =
|
||||||
|
view
|
||||||
|
|> element("[data-testid='template-publish-button']")
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
assert Templates.get_template(draft_template.id).status == :published
|
assert Templates.get_template(draft_template.id).status == :published
|
||||||
refute draft_template_html =~ ~s(data-testid="template-publish-button")
|
refute draft_template_html =~ ~s(data-testid="template-publish-button")
|
||||||
assert draft_template_html =~ ~s(data-testid="template-status-badge")
|
assert draft_template_html =~ ~s(data-testid="template-status-badge")
|
||||||
|
|||||||
Reference in New Issue
Block a user