diff --git a/main.cpp b/main.cpp index 6c264d5..caec24b 100644 --- a/main.cpp +++ b/main.cpp @@ -264,6 +264,9 @@ static bool load_input() { std::fprintf(stdout, "[graph_explorer] table counts refreshed: %zu tables, %lld total rows\n", 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. @@ -420,6 +423,28 @@ static void render_context_menu() { ImGui::TextDisabled("%s", lbl && *lbl ? lbl : "(unnamed)"); 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")) { // Construye un set ordenado y deduplicado: tipos del grafo + defaults. // Asi evitamos colisiones de ID en ImGui ("person" en grafo y default). @@ -614,6 +639,7 @@ static void render() { load_input(); } + // ---- Type Editor (issue 0007) ---- if (g_app.want_types_save) { g_app.want_types_save = false; @@ -705,6 +731,9 @@ static void render() { // Refresh Table node counts (issue 0010). 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). std::vector snap; if (ge::entity_list_rows(g_input.uri, &snap)) { @@ -802,6 +831,132 @@ static void render() { 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 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 ---- { const auto& sel = g_viewport.selection; @@ -1001,6 +1156,10 @@ static void render() { ImGui::SetNextWindowSize(ImVec2(820.0f, 520.0f), ImGuiCond_FirstUseEver); 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; }