defmodule BDS.UI.ShellPage do
@moduledoc false
alias BDS.I18n
alias BDS.Projects
alias BDS.UI.Dashboard
alias BDS.UI.MenuBar
alias BDS.UI.Registry
alias BDS.UI.Sidebar
alias BDS.UI.Session
alias BDS.UI.Workbench
def render do
bootstrap = bootstrap()
ui_language = get_in(bootstrap, [:i18n, :ui_language]) || "en"
[
"",
"",
"
",
" ",
" ",
" Blogging Desktop Server ",
" ",
"",
"",
" ",
"
",
"
",
"
",
" ",
"
",
"
",
" ",
" ",
" ",
" ",
"
",
"
",
"
",
" ",
" ",
"",
""
]
|> Enum.join("\n")
end
defp bootstrap do
workbench = Workbench.new()
task_status = BDS.Tasks.status_snapshot()
ui_language = I18n.current_ui_locale()
projects = project_snapshot()
dashboard = dashboard_content(projects.active_project_id)
%{
title: Application.get_env(:bds, :desktop)[:title] || "Blogging Desktop Server",
i18n: %{
ui_language: ui_language,
catalogs:
Enum.into(I18n.supported_languages(), %{}, fn language ->
{language.code, I18n.get_ui_translations(language.code)}
end),
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),
editor_routes: Enum.map(Registry.editor_routes(), &encode_editor_route/1),
default_sidebar_view: Atom.to_string(Registry.default_sidebar_view())
},
menu_groups: Enum.map(MenuBar.default_groups(), &encode_menu_group/1),
projects: projects,
session: Session.serialize(workbench),
task_status: task_status,
content: %{
sidebar: sidebar_content(projects.active_project_id),
dashboard: dashboard,
assistant_cards: assistant_cards(),
editor_meta: editor_meta(task_status)
},
status:
Workbench.status_bar(workbench,
post_count: dashboard.post_stats.total_posts,
media_count: dashboard.media_stats.media_count,
theme_badge: "desktop-shell",
ui_language: ui_language,
offline_mode: true,
running_task_message: task_status.running_task_message,
running_task_overflow: task_status.running_task_overflow,
git_badge_count: 3
)
}
end
defp encode_sidebar_view(view) do
%{
id: Atom.to_string(view.id),
label: normalize_view_label(view.id, view.label),
activity_group: Atom.to_string(view.activity_group),
editor_route: Atom.to_string(view.editor_route),
entity_tab: Map.get(view, :entity_tab, false),
singleton: Map.get(view, :singleton, false)
}
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),
singleton: route.singleton,
entity_tab: route.entity_tab,
title: route.title
}
end
defp encode_menu_group(group) do
%{
id: Atom.to_string(group.id),
label: humanize(group.id),
items:
group.items
|> Enum.reject(&Map.get(&1, :separator, false))
|> Enum.map(fn item ->
%{id: Atom.to_string(item.id), label: humanize(item.id)}
end)
}
end
defp sidebar_content(project_id) do
Sidebar.snapshot(project_id)
rescue
error in [Exqlite.Error, DBConnection.OwnershipError] ->
if match?(%Exqlite.Error{}, error) and not String.contains?(Exception.message(error), "no such table") do
reraise error, __STACKTRACE__
end
Sidebar.empty_snapshot()
end
defp dashboard_content(project_id) do
Dashboard.snapshot(project_id)
rescue
error in [Exqlite.Error, DBConnection.OwnershipError] ->
if match?(%Exqlite.Error{}, error) and not String.contains?(Exception.message(error), "no such table") do
reraise error, __STACKTRACE__
end
Dashboard.empty_snapshot()
end
defp assistant_cards do
[
%{label: "Offline Gate", text: "Automatic AI actions stay gated by airplane mode."},
%{label: "Filesystem Sync", text: "Metadata flush, diffing, and rebuild hooks still need editor wiring."},
%{label: "Desktop Runtime", text: "The app window is now served from the Elixir shell renderer."}
]
end
defp editor_meta(task_status) do
%{
dashboard: [
%{label: "Status", value: task_status.running_task_message || "Idle"},
%{label: "Mode", value: "Offline"},
%{label: "Main Language", value: "en"}
]
}
end
defp normalize_view_label(:chat, _label), do: "Chat"
defp normalize_view_label(:git, _label), do: "Git"
defp normalize_view_label(_id, label), do: label
defp humanize(value) when is_atom(value), do: value |> Atom.to_string() |> humanize()
defp humanize(value) when is_binary(value) do
value
|> String.replace("_", " ")
|> String.split(" ")
|> Enum.map(&String.capitalize/1)
|> Enum.join(" ")
end
end