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