feat(main): wire 0011 — context menu + triggers + sync de windows
- Context menu del viewport detecta type_ref='Table' y anade Expand/ Collapse table. Toggle escribe metadata.expanded en BD y resincroniza table_windows. - Triggers want_promote_row -> tableview_promote_row + reload + focus inspector con la entidad recien creada. - want_demote_entity -> tableview_demote_row + reload. - want_focus_entity: resuelve entity_id -> node_idx via FNV1a, centra camara, abre inspector. - want_import -> tableview_ingest_file + tableview_create + reload. - Loop por table_windows page_dirty -> tableview_count + (si columns vacios) descubre+persiste columnas + tableview_page. - Cierre via X de ventana detectado leyendo open=false; bajamos expanded en BD y borramos del mapa. - Sync de table_windows tras load_input y reload_after_mutation. - views_table_window + views_import_dataset_modal llamados en render().
This commit is contained in:
@@ -264,6 +264,9 @@ static bool load_input() {
|
|||||||
std::fprintf(stdout,
|
std::fprintf(stdout,
|
||||||
"[graph_explorer] table counts refreshed: %zu tables, %lld total rows\n",
|
"[graph_explorer] table counts refreshed: %zu tables, %lld total rows\n",
|
||||||
g_app.table_node_counts.size(), (long long)total_rows);
|
g_app.table_node_counts.size(), (long long)total_rows);
|
||||||
|
// Sync de windows expandidas (issue 0011) — reabre las que el
|
||||||
|
// usuario tenia abiertas en la sesion previa (metadata.expanded=true).
|
||||||
|
ge::views_table_windows_sync(g_app, g_input.uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache de la vista tabla (issue 0004) — pull bulk + neighbors desde grafo.
|
// Cache de la vista tabla (issue 0004) — pull bulk + neighbors desde grafo.
|
||||||
@@ -420,6 +423,28 @@ static void render_context_menu() {
|
|||||||
ImGui::TextDisabled("%s", lbl && *lbl ? lbl : "(unnamed)");
|
ImGui::TextDisabled("%s", lbl && *lbl ? lbl : "(unnamed)");
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Detectar si el nodo es Table y resolver entity_id para opciones tabla.
|
||||||
|
bool is_table = false;
|
||||||
|
if (n.type_id < (uint16_t)g_graph.type_count) {
|
||||||
|
const EntityType& t = g_graph.types[n.type_id];
|
||||||
|
if (t.name && std::strcmp(t.name, "Table") == 0) is_table = true;
|
||||||
|
}
|
||||||
|
const char* sql_id = ge::entity_index_lookup(g_idx, n.user_data);
|
||||||
|
|
||||||
|
if (is_table && sql_id) {
|
||||||
|
// Determinar estado expanded actual sin ir a BD: mira table_windows.
|
||||||
|
bool currently_expanded =
|
||||||
|
g_app.table_windows.find(sql_id) != g_app.table_windows.end();
|
||||||
|
const char* lbl_exp = currently_expanded
|
||||||
|
? TI_X " Collapse table"
|
||||||
|
: TI_TABLE " Expand table";
|
||||||
|
if (ImGui::MenuItem(lbl_exp)) {
|
||||||
|
g_app.want_toggle_expanded = true;
|
||||||
|
g_app.toggle_expanded_id = sql_id;
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui::BeginMenu("Change type")) {
|
if (ImGui::BeginMenu("Change type")) {
|
||||||
// Construye un set ordenado y deduplicado: tipos del grafo + defaults.
|
// Construye un set ordenado y deduplicado: tipos del grafo + defaults.
|
||||||
// Asi evitamos colisiones de ID en ImGui ("person" en grafo y default).
|
// Asi evitamos colisiones de ID en ImGui ("person" en grafo y default).
|
||||||
@@ -614,6 +639,7 @@ static void render() {
|
|||||||
load_input();
|
load_input();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---- Type Editor (issue 0007) ----
|
// ---- Type Editor (issue 0007) ----
|
||||||
if (g_app.want_types_save) {
|
if (g_app.want_types_save) {
|
||||||
g_app.want_types_save = false;
|
g_app.want_types_save = false;
|
||||||
@@ -705,6 +731,9 @@ static void render() {
|
|||||||
// Refresh Table node counts (issue 0010).
|
// Refresh Table node counts (issue 0010).
|
||||||
ge::tableview_refresh_counts(g_input.uri, &g_app.table_node_counts);
|
ge::tableview_refresh_counts(g_input.uri, &g_app.table_node_counts);
|
||||||
|
|
||||||
|
// Sincroniza windows (issue 0011) por si una Table aparecio o desaparecio.
|
||||||
|
ge::views_table_windows_sync(g_app, g_input.uri);
|
||||||
|
|
||||||
// Refresh table cache (issue 0004).
|
// Refresh table cache (issue 0004).
|
||||||
std::vector<ge::EntityRowSnapshot> snap;
|
std::vector<ge::EntityRowSnapshot> snap;
|
||||||
if (ge::entity_list_rows(g_input.uri, &snap)) {
|
if (ge::entity_list_rows(g_input.uri, &snap)) {
|
||||||
@@ -802,6 +831,132 @@ static void render() {
|
|||||||
g_app.want_change_type = false;
|
g_app.want_change_type = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Table node UI fase 2 (issue 0011) ----
|
||||||
|
if (g_app.want_toggle_expanded && !g_app.toggle_expanded_id.empty()
|
||||||
|
&& !g_input_path.empty()) {
|
||||||
|
std::string id = g_app.toggle_expanded_id;
|
||||||
|
bool currently = g_app.table_windows.find(id) != g_app.table_windows.end();
|
||||||
|
ge::tableview_set_expanded(g_input_path.c_str(), id.c_str(), !currently);
|
||||||
|
ge::views_table_windows_sync(g_app, g_input_path.c_str());
|
||||||
|
g_app.want_toggle_expanded = false;
|
||||||
|
g_app.toggle_expanded_id.clear();
|
||||||
|
}
|
||||||
|
// Cierre via X de la ventana -> bajar expanded en BD.
|
||||||
|
for (auto it = g_app.table_windows.begin(); it != g_app.table_windows.end(); ) {
|
||||||
|
if (!it->second.open && !g_input_path.empty()) {
|
||||||
|
ge::tableview_set_expanded(g_input_path.c_str(),
|
||||||
|
it->first.c_str(), false);
|
||||||
|
it = g_app.table_windows.erase(it);
|
||||||
|
} else ++it;
|
||||||
|
}
|
||||||
|
// Refrescar la pagina si alguna window esta dirty.
|
||||||
|
for (auto& kv : g_app.table_windows) {
|
||||||
|
auto& w = kv.second;
|
||||||
|
if (!w.page_dirty) continue;
|
||||||
|
const auto& m = w.meta;
|
||||||
|
ge::tableview_count(m.duckdb_path_abs.c_str(), m.table_name.c_str(),
|
||||||
|
m.filter_sql.empty() ? nullptr : m.filter_sql.c_str(),
|
||||||
|
&w.total_rows);
|
||||||
|
if (m.columns.empty()) {
|
||||||
|
std::vector<std::string> cols;
|
||||||
|
if (ge::tableview_list_columns(m.duckdb_path_abs.c_str(),
|
||||||
|
m.table_name.c_str(), &cols)) {
|
||||||
|
ge::tableview_set_columns(g_input_path.c_str(),
|
||||||
|
m.entity_id.c_str(), cols);
|
||||||
|
w.meta.columns = cols;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ge::tableview_page(m.duckdb_path_abs.c_str(), m.table_name.c_str(),
|
||||||
|
m.id_column.c_str(), w.meta.columns,
|
||||||
|
m.filter_sql.empty() ? nullptr : m.filter_sql.c_str(),
|
||||||
|
g_input_path.c_str(), m.row_type.c_str(),
|
||||||
|
w.offset, 200, &w.page);
|
||||||
|
w.page_dirty = false;
|
||||||
|
}
|
||||||
|
if (g_app.want_promote_row && !g_app.promote_table_id.empty()
|
||||||
|
&& !g_input_path.empty()) {
|
||||||
|
ge::TableMetadata m;
|
||||||
|
if (ge::tableview_get_metadata(g_input_path.c_str(),
|
||||||
|
g_app.promote_table_id.c_str(), &m)) {
|
||||||
|
char new_id[128] = {};
|
||||||
|
if (ge::tableview_promote_row(g_input_path.c_str(),
|
||||||
|
m.duckdb_path_abs.c_str(),
|
||||||
|
m.table_name.c_str(),
|
||||||
|
g_app.promote_row_id.c_str(),
|
||||||
|
m.row_type.c_str(),
|
||||||
|
m.label_column.c_str(),
|
||||||
|
new_id, sizeof(new_id))) {
|
||||||
|
std::fprintf(stdout, "[promote] %s -> %s\n",
|
||||||
|
g_app.promote_row_id.c_str(), new_id);
|
||||||
|
auto it = g_app.table_windows.find(g_app.promote_table_id);
|
||||||
|
if (it != g_app.table_windows.end()) it->second.page_dirty = true;
|
||||||
|
reload_after_mutation();
|
||||||
|
g_app.want_focus_entity = true;
|
||||||
|
g_app.focus_entity_id = new_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_app.want_promote_row = false;
|
||||||
|
g_app.promote_table_id.clear();
|
||||||
|
g_app.promote_row_id.clear();
|
||||||
|
}
|
||||||
|
if (g_app.want_demote_entity && !g_app.demote_entity_id.empty()
|
||||||
|
&& !g_input_path.empty()) {
|
||||||
|
if (ge::tableview_demote_row(g_input_path.c_str(),
|
||||||
|
g_app.demote_entity_id.c_str())) {
|
||||||
|
std::fprintf(stdout, "[demote] %s\n", g_app.demote_entity_id.c_str());
|
||||||
|
for (auto& kv : g_app.table_windows) kv.second.page_dirty = true;
|
||||||
|
reload_after_mutation();
|
||||||
|
}
|
||||||
|
g_app.want_demote_entity = false;
|
||||||
|
g_app.demote_entity_id.clear();
|
||||||
|
}
|
||||||
|
if (g_app.want_focus_entity && !g_app.focus_entity_id.empty()) {
|
||||||
|
for (int i = 0; i < g_graph.node_count; ++i) {
|
||||||
|
const char* sid = ge::entity_index_lookup(
|
||||||
|
g_idx, g_graph.nodes[i].user_data);
|
||||||
|
if (sid && g_app.focus_entity_id == sid) {
|
||||||
|
g_app.filter_focus_target = i;
|
||||||
|
graph_viewport_clear_selection(g_graph, g_viewport);
|
||||||
|
graph_viewport_add_to_selection(g_graph, g_viewport, i);
|
||||||
|
g_app.panel_inspector = true;
|
||||||
|
ge::views_inspector_load_draft(g_app, i, sid);
|
||||||
|
g_app.insp_node_idx = i;
|
||||||
|
g_app.insp_entity_id = sid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_app.want_focus_entity = false;
|
||||||
|
g_app.focus_entity_id.clear();
|
||||||
|
}
|
||||||
|
if (g_app.want_import) {
|
||||||
|
g_app.want_import = false;
|
||||||
|
g_app.import_error.clear();
|
||||||
|
std::string duck_abs = ge::tableview_resolve_path(
|
||||||
|
g_input_path.c_str(), g_app.import_duckdb_buf);
|
||||||
|
std::string err;
|
||||||
|
if (!ge::tableview_ingest_file(duck_abs.c_str(),
|
||||||
|
g_app.import_path_buf,
|
||||||
|
g_app.import_table_buf,
|
||||||
|
ge::INGEST_AUTO, &err)) {
|
||||||
|
g_app.import_error = "Ingest failed: " + err;
|
||||||
|
} else {
|
||||||
|
char new_id[80] = {};
|
||||||
|
if (ge::tableview_create(g_input_path.c_str(),
|
||||||
|
g_app.import_table_buf,
|
||||||
|
g_app.import_duckdb_buf,
|
||||||
|
g_app.import_table_buf,
|
||||||
|
g_app.import_row_type_buf,
|
||||||
|
new_id, sizeof(new_id))) {
|
||||||
|
std::fprintf(stdout, "[import] %s -> %s\n",
|
||||||
|
g_app.import_path_buf, new_id);
|
||||||
|
g_app.show_import_modal = false;
|
||||||
|
reload_after_mutation();
|
||||||
|
} else {
|
||||||
|
g_app.import_error = "Tabla DuckDB creada pero no se pudo registrar el nodo.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Inspector (issue 0008): sync draft con seleccion + save/discard ----
|
// ---- Inspector (issue 0008): sync draft con seleccion + save/discard ----
|
||||||
{
|
{
|
||||||
const auto& sel = g_viewport.selection;
|
const auto& sel = g_viewport.selection;
|
||||||
@@ -1001,6 +1156,10 @@ static void render() {
|
|||||||
ImGui::SetNextWindowSize(ImVec2(820.0f, 520.0f), ImGuiCond_FirstUseEver);
|
ImGui::SetNextWindowSize(ImVec2(820.0f, 520.0f), ImGuiCond_FirstUseEver);
|
||||||
ge::views_table(g_app);
|
ge::views_table(g_app);
|
||||||
|
|
||||||
|
// Table node windows (issue 0011) — una por Table expandida.
|
||||||
|
ge::views_table_window(g_app);
|
||||||
|
ge::views_import_dataset_modal(g_app);
|
||||||
|
|
||||||
g_first_render = false;
|
g_first_render = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user