feat: more UI cleanup
This commit is contained in:
@@ -25,6 +25,7 @@ defmodule BDS.Application do
|
||||
def start(_type, _args) do
|
||||
children = [
|
||||
BDS.Repo,
|
||||
BDS.RepoBootstrap,
|
||||
BDS.Tasks,
|
||||
BDS.Preview,
|
||||
BDS.Publishing,
|
||||
|
||||
@@ -36,6 +36,21 @@ defmodule BDS.Desktop.Router do
|
||||
|> Plug.Conn.send_resp(200, BDS.Desktop.ShellController.task_status_json())
|
||||
end
|
||||
|
||||
get "/api/projects" do
|
||||
conn
|
||||
|> Plug.Conn.put_resp_content_type("application/json")
|
||||
|> Plug.Conn.send_resp(200, BDS.Desktop.ShellController.projects_json())
|
||||
end
|
||||
|
||||
post "/api/projects" do
|
||||
{:ok, body, conn} = Plug.Conn.read_body(conn)
|
||||
payload = if body == "", do: %{}, else: Jason.decode!(body)
|
||||
|
||||
conn
|
||||
|> Plug.Conn.put_resp_content_type("application/json")
|
||||
|> Plug.Conn.send_resp(200, BDS.Desktop.ShellController.upsert_project_json(payload))
|
||||
end
|
||||
|
||||
post "/api/commands" do
|
||||
{:ok, body, conn} = Plug.Conn.read_body(conn)
|
||||
payload = if body == "", do: %{}, else: Jason.decode!(body)
|
||||
|
||||
@@ -9,6 +9,25 @@ defmodule BDS.Desktop.ShellController do
|
||||
Jason.encode!(BDS.Tasks.status_snapshot())
|
||||
end
|
||||
|
||||
def projects_json do
|
||||
Jason.encode!(BDS.Projects.shell_snapshot())
|
||||
rescue
|
||||
error in [Exqlite.Error] ->
|
||||
if String.contains?(Exception.message(error), "no such table: projects") do
|
||||
Jason.encode!(default_project_snapshot())
|
||||
else
|
||||
reraise error, __STACKTRACE__
|
||||
end
|
||||
end
|
||||
|
||||
def upsert_project_json(payload) when is_map(payload) do
|
||||
case normalize_project_request(payload) do
|
||||
{:create, attrs} -> create_project_json(attrs)
|
||||
{:select, project_id} -> select_project_json(project_id)
|
||||
:error -> Jason.encode!(%{status: "error", error: %{message: "Missing project name or project_id"}})
|
||||
end
|
||||
end
|
||||
|
||||
def command_json(payload) when is_map(payload) do
|
||||
action = Map.get(payload, "action") || Map.get(payload, :action)
|
||||
params = Map.get(payload, "params") || Map.get(payload, :params) || %{}
|
||||
@@ -21,4 +40,72 @@ defmodule BDS.Desktop.ShellController do
|
||||
|
||||
defp normalize_error(error) when is_map(error), do: error
|
||||
defp normalize_error(error), do: %{message: inspect(error)}
|
||||
|
||||
defp normalize_project_request(payload) do
|
||||
cond do
|
||||
present?(Map.get(payload, "name") || Map.get(payload, :name)) ->
|
||||
{:create,
|
||||
%{
|
||||
name: String.trim(Map.get(payload, "name") || Map.get(payload, :name)),
|
||||
data_path: blank_to_nil(Map.get(payload, "data_path") || Map.get(payload, :data_path))
|
||||
}}
|
||||
|
||||
present?(Map.get(payload, "project_id") || Map.get(payload, :project_id)) ->
|
||||
{:select, Map.get(payload, "project_id") || Map.get(payload, :project_id)}
|
||||
|
||||
true ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp create_project_json(attrs) do
|
||||
with {:ok, project} <- BDS.Projects.create_project(attrs),
|
||||
{:ok, active_project} <- BDS.Projects.set_active_project(project.id) do
|
||||
Jason.encode!(%{status: "ok", project: project_response(active_project), active_project_id: active_project.id})
|
||||
else
|
||||
{:error, error} -> Jason.encode!(%{status: "error", error: normalize_error(error)})
|
||||
end
|
||||
end
|
||||
|
||||
defp select_project_json(project_id) do
|
||||
case BDS.Projects.set_active_project(project_id) do
|
||||
{:ok, project} ->
|
||||
Jason.encode!(%{status: "ok", project: project_response(project), active_project_id: project.id})
|
||||
|
||||
{:error, :not_found} ->
|
||||
Jason.encode!(%{status: "error", error: %{message: "Project not found"}})
|
||||
|
||||
{:error, error} ->
|
||||
Jason.encode!(%{status: "error", error: normalize_error(error)})
|
||||
end
|
||||
end
|
||||
|
||||
defp project_response(project) do
|
||||
%{id: project.id, name: project.name, slug: project.slug, data_path: project.data_path, is_active: project.is_active}
|
||||
end
|
||||
|
||||
defp default_project_snapshot do
|
||||
%{
|
||||
active_project_id: "default",
|
||||
projects: [
|
||||
%{
|
||||
id: "default",
|
||||
name: "My Blog",
|
||||
slug: "my-blog",
|
||||
data_path: nil,
|
||||
is_active: true
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
defp present?(value) when is_binary(value), do: String.trim(value) != ""
|
||||
defp present?(_value), do: false
|
||||
|
||||
defp blank_to_nil(value) when is_binary(value) do
|
||||
trimmed = String.trim(value)
|
||||
if trimmed == "", do: nil, else: trimmed
|
||||
end
|
||||
|
||||
defp blank_to_nil(_value), do: nil
|
||||
end
|
||||
|
||||
@@ -21,6 +21,17 @@ defmodule BDS.Projects do
|
||||
Repo.one(from project in Project, where: project.is_active == true, limit: 1)
|
||||
end
|
||||
|
||||
def shell_snapshot do
|
||||
_ = ensure_default_project()
|
||||
projects = list_projects()
|
||||
active_project = Enum.find(projects, & &1.is_active)
|
||||
|
||||
%{
|
||||
active_project_id: active_project && active_project.id,
|
||||
projects: Enum.map(projects, &project_summary/1)
|
||||
}
|
||||
end
|
||||
|
||||
def get_project(id), do: Repo.get(Project, id)
|
||||
def get_project!(id), do: Repo.get!(Project, id)
|
||||
|
||||
@@ -150,6 +161,16 @@ defmodule BDS.Projects do
|
||||
end
|
||||
end
|
||||
|
||||
defp project_summary(%Project{} = project) do
|
||||
%{
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
slug: project.slug,
|
||||
data_path: project.data_path,
|
||||
is_active: project.is_active
|
||||
}
|
||||
end
|
||||
|
||||
defp unique_slug(base_slug) do
|
||||
normalized = if base_slug in [nil, ""], do: "project", else: base_slug
|
||||
|
||||
|
||||
43
lib/bds/repo_bootstrap.ex
Normal file
43
lib/bds/repo_bootstrap.ex
Normal file
@@ -0,0 +1,43 @@
|
||||
defmodule BDS.RepoBootstrap do
|
||||
@moduledoc false
|
||||
|
||||
use GenServer
|
||||
|
||||
def start_link(opts \\ []) do
|
||||
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
|
||||
end
|
||||
|
||||
def init(opts) do
|
||||
:ok = ensure_ready(opts)
|
||||
{:ok, %{}}
|
||||
end
|
||||
|
||||
def ensure_ready(opts \\ []) do
|
||||
repo = Keyword.get(opts, :repo, BDS.Repo)
|
||||
|
||||
if Keyword.get(opts, :migrate?, true) do
|
||||
:ok = ensure_schema(Keyword.put(opts, :repo, repo))
|
||||
end
|
||||
|
||||
if repo == BDS.Repo do
|
||||
case BDS.Projects.ensure_default_project() do
|
||||
{:ok, _project} -> :ok
|
||||
{:error, reason} -> raise "failed to ensure default project: #{inspect(reason)}"
|
||||
end
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_schema(opts \\ []) do
|
||||
repo = Keyword.get(opts, :repo, BDS.Repo)
|
||||
migrations_path = Keyword.get(opts, :migrations_path, migrations_path())
|
||||
|
||||
_versions = Ecto.Migrator.run(repo, migrations_path, :up, all: true)
|
||||
:ok
|
||||
end
|
||||
|
||||
def migrations_path do
|
||||
Path.expand("../../priv/repo/migrations", __DIR__)
|
||||
end
|
||||
end
|
||||
@@ -2,6 +2,7 @@ defmodule BDS.UI.ShellPage do
|
||||
@moduledoc false
|
||||
|
||||
alias BDS.I18n
|
||||
alias BDS.Projects
|
||||
alias BDS.UI.MenuBar
|
||||
alias BDS.UI.Registry
|
||||
alias BDS.UI.Session
|
||||
@@ -57,7 +58,13 @@ defmodule BDS.UI.ShellPage do
|
||||
title: Application.get_env(:bds, :desktop)[:title] || "Blogging Desktop Server",
|
||||
i18n: %{
|
||||
ui_language: ui_language,
|
||||
supported_ui_languages: Enum.map(I18n.supported_languages(), &Map.take(&1, [:code, :flag]))
|
||||
supported_ui_languages:
|
||||
Enum.map(I18n.supported_languages(), fn language ->
|
||||
%{
|
||||
code: language.code,
|
||||
flag: I18n.flag(language.code)
|
||||
}
|
||||
end)
|
||||
},
|
||||
registry: %{
|
||||
sidebar_views: Enum.map(Registry.sidebar_views(), &encode_sidebar_view/1),
|
||||
@@ -65,6 +72,7 @@ defmodule BDS.UI.ShellPage do
|
||||
default_sidebar_view: Atom.to_string(Registry.default_sidebar_view())
|
||||
},
|
||||
menu_groups: Enum.map(MenuBar.default_groups(), &encode_menu_group/1),
|
||||
projects: project_snapshot(),
|
||||
session: Session.serialize(workbench),
|
||||
task_status: task_status,
|
||||
content: %{
|
||||
@@ -98,6 +106,32 @@ defmodule BDS.UI.ShellPage do
|
||||
}
|
||||
end
|
||||
|
||||
defp project_snapshot do
|
||||
Projects.shell_snapshot()
|
||||
rescue
|
||||
error in [Exqlite.Error, DBConnection.OwnershipError] ->
|
||||
if match?(%Exqlite.Error{}, error) and not String.contains?(Exception.message(error), "no such table: projects") do
|
||||
reraise error, __STACKTRACE__
|
||||
end
|
||||
|
||||
default_project_snapshot()
|
||||
end
|
||||
|
||||
defp default_project_snapshot do
|
||||
%{
|
||||
active_project_id: "default",
|
||||
projects: [
|
||||
%{
|
||||
id: "default",
|
||||
name: "My Blog",
|
||||
slug: "my-blog",
|
||||
data_path: nil,
|
||||
is_active: true
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
defp encode_editor_route(route) do
|
||||
%{
|
||||
id: Atom.to_string(route.id),
|
||||
|
||||
Reference in New Issue
Block a user