feat(ui): migrar a tokens + primitivos del design system
Dashboard ahora usa: - fn_tokens::apply_dark_theme() al primer frame (colors + spacing + radius + rounding consistentes con @fn_library / Mantine). - page_header_begin/end para el header (en vez de Text + Separator + Button manual). Subtítulo con conteos de entidades. - empty_state en las 4 tablas cuando están vacías — mensaje amable con acción sugerida en lugar de tabla vacía silenciosa. Nuevas deps de compilación (.cpp fuentes añadidas al CMakeLists): tokens.cpp, badge.cpp, empty_state.cpp, page_header.cpp. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,11 @@ add_imgui_app(registry_dashboard
|
|||||||
${CMAKE_SOURCE_DIR}/functions/core/dashboard_grid.cpp
|
${CMAKE_SOURCE_DIR}/functions/core/dashboard_grid.cpp
|
||||||
${CMAKE_SOURCE_DIR}/functions/core/fps_overlay.cpp
|
${CMAKE_SOURCE_DIR}/functions/core/fps_overlay.cpp
|
||||||
${CMAKE_SOURCE_DIR}/functions/core/fullscreen_window.cpp
|
${CMAKE_SOURCE_DIR}/functions/core/fullscreen_window.cpp
|
||||||
|
# Design tokens + primitives (fase 1 y 2 del plan del dashboard)
|
||||||
|
${CMAKE_SOURCE_DIR}/functions/core/tokens.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/functions/core/badge.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/functions/core/empty_state.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/functions/core/page_header.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(registry_dashboard PRIVATE
|
target_include_directories(registry_dashboard PRIVATE
|
||||||
|
|||||||
@@ -11,7 +11,12 @@
|
|||||||
#include "core/dashboard_grid.h"
|
#include "core/dashboard_grid.h"
|
||||||
#include "core/fps_overlay.h"
|
#include "core/fps_overlay.h"
|
||||||
#include "core/fullscreen_window.h"
|
#include "core/fullscreen_window.h"
|
||||||
|
#include "core/tokens.h"
|
||||||
|
#include "core/page_header.h"
|
||||||
|
#include "core/empty_state.h"
|
||||||
|
#include "core/badge.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
static std::vector<const char*> to_cstr(const std::vector<std::string>& v) {
|
static std::vector<const char*> to_cstr(const std::vector<std::string>& v) {
|
||||||
@@ -87,6 +92,11 @@ void draw_charts(RegistryData& data, float height) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void draw_recent_functions(const std::vector<FunctionRow>& funcs) {
|
void draw_recent_functions(const std::vector<FunctionRow>& funcs) {
|
||||||
|
if (funcs.empty()) {
|
||||||
|
empty_state("( no data )", "No functions yet",
|
||||||
|
"Run 'fn index' to populate the registry");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const char* headers[] = {"Name", "Lang", "Domain", "Kind", "Purity", "Tested", "Created"};
|
const char* headers[] = {"Name", "Lang", "Domain", "Kind", "Purity", "Tested", "Created"};
|
||||||
constexpr int cols = 7;
|
constexpr int cols = 7;
|
||||||
std::vector<std::string> cell_strings;
|
std::vector<std::string> cell_strings;
|
||||||
@@ -105,6 +115,11 @@ void draw_recent_functions(const std::vector<FunctionRow>& funcs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void draw_apps_list(const std::vector<AppRow>& apps) {
|
void draw_apps_list(const std::vector<AppRow>& apps) {
|
||||||
|
if (apps.empty()) {
|
||||||
|
empty_state("( no data )", "No apps registered",
|
||||||
|
"Clone apps with 'fn app clone <id>' or run 'fn sync'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const char* headers[] = {"Name", "Lang", "Domain", "Framework", "Description"};
|
const char* headers[] = {"Name", "Lang", "Domain", "Framework", "Description"};
|
||||||
constexpr int cols = 5;
|
constexpr int cols = 5;
|
||||||
std::vector<std::string> cell_strings;
|
std::vector<std::string> cell_strings;
|
||||||
@@ -121,6 +136,11 @@ void draw_apps_list(const std::vector<AppRow>& apps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void draw_analysis_list(const std::vector<AnalysisRow>& analyses) {
|
void draw_analysis_list(const std::vector<AnalysisRow>& analyses) {
|
||||||
|
if (analyses.empty()) {
|
||||||
|
empty_state("( no data )", "No analysis yet",
|
||||||
|
"Create one with 'fn run init_jupyter_analysis <name>'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const char* headers[] = {"Name", "Domain", "Description"};
|
const char* headers[] = {"Name", "Domain", "Description"};
|
||||||
constexpr int cols = 3;
|
constexpr int cols = 3;
|
||||||
std::vector<std::string> cell_strings;
|
std::vector<std::string> cell_strings;
|
||||||
@@ -135,6 +155,11 @@ void draw_analysis_list(const std::vector<AnalysisRow>& analyses) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void draw_types_list(const std::vector<TypeRow>& types) {
|
void draw_types_list(const std::vector<TypeRow>& types) {
|
||||||
|
if (types.empty()) {
|
||||||
|
empty_state("( no data )", "No types yet",
|
||||||
|
"Types are indexed from the registry alongside functions");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const char* headers[] = {"Name", "Lang", "Domain", "Algebraic", "Description"};
|
const char* headers[] = {"Name", "Lang", "Domain", "Algebraic", "Description"};
|
||||||
constexpr int cols = 5;
|
constexpr int cols = 5;
|
||||||
std::vector<std::string> cell_strings;
|
std::vector<std::string> cell_strings;
|
||||||
@@ -151,31 +176,44 @@ void draw_types_list(const std::vector<TypeRow>& types) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void draw_dashboard(RegistryData& data) {
|
void draw_dashboard(RegistryData& data) {
|
||||||
|
// Aplicar tema una sola vez por vida de la app.
|
||||||
|
static bool theme_applied = false;
|
||||||
|
if (!theme_applied) {
|
||||||
|
fn_tokens::apply_dark_theme();
|
||||||
|
theme_applied = true;
|
||||||
|
}
|
||||||
|
|
||||||
fps_overlay();
|
fps_overlay();
|
||||||
fullscreen_window_begin("##dashboard");
|
fullscreen_window_begin("##dashboard");
|
||||||
|
|
||||||
float win_h = ImGui::GetContentRegionAvail().y;
|
// Subtitle con conteos — contexto rápido para el usuario
|
||||||
|
char subtitle[128];
|
||||||
|
std::snprintf(subtitle, sizeof(subtitle),
|
||||||
|
"%d functions · %d types · %d apps · %d analyses",
|
||||||
|
data.stats.total_functions, data.stats.total_types,
|
||||||
|
data.stats.total_apps, data.stats.total_analysis);
|
||||||
|
|
||||||
// Row 1: Header + KPIs (~80px)
|
// Header con acción Reload a la derecha
|
||||||
ImGui::Text("fn_registry Dashboard");
|
page_header_begin("fn_registry Dashboard", subtitle);
|
||||||
ImGui::SameLine(ImGui::GetWindowWidth() - 100);
|
ImGui::SameLine(ImGui::GetWindowWidth() - 120.0f);
|
||||||
if (ImGui::Button("Reload")) {
|
if (ImGui::Button("Reload")) {
|
||||||
ImGui::GetIO().UserData = reinterpret_cast<void*>(1);
|
ImGui::GetIO().UserData = reinterpret_cast<void*>(1);
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
page_header_end();
|
||||||
|
|
||||||
|
// KPIs
|
||||||
draw_kpi_row(data.stats);
|
draw_kpi_row(data.stats);
|
||||||
ImGui::Separator();
|
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md));
|
||||||
|
|
||||||
// Row 2: Charts — take 35% of remaining height
|
// Charts — 35% de la altura restante
|
||||||
float remaining = ImGui::GetContentRegionAvail().y;
|
float remaining = ImGui::GetContentRegionAvail().y;
|
||||||
float chart_h = remaining * 0.35f;
|
float chart_h = remaining * 0.35f;
|
||||||
if (chart_h < 150.0f) chart_h = 150.0f;
|
if (chart_h < 150.0f) chart_h = 150.0f;
|
||||||
|
|
||||||
draw_charts(data, chart_h);
|
draw_charts(data, chart_h);
|
||||||
ImGui::Separator();
|
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md));
|
||||||
|
|
||||||
// Row 3: Tables — fill the rest
|
// Tables
|
||||||
if (ImGui::BeginTabBar("##tables")) {
|
if (ImGui::BeginTabBar("##tables")) {
|
||||||
if (ImGui::BeginTabItem("Recent Functions")) {
|
if (ImGui::BeginTabItem("Recent Functions")) {
|
||||||
draw_recent_functions(data.recent_funcs);
|
draw_recent_functions(data.recent_funcs);
|
||||||
|
|||||||
Reference in New Issue
Block a user