merge: quick/table-node-visual-and-edges — edge CONTAINS_ROW + cuadrado real
This commit is contained in:
@@ -880,6 +880,7 @@ static void render() {
|
|||||||
g_app.promote_table_id.c_str(), &m)) {
|
g_app.promote_table_id.c_str(), &m)) {
|
||||||
char new_id[128] = {};
|
char new_id[128] = {};
|
||||||
if (ge::tableview_promote_row(g_input_path.c_str(),
|
if (ge::tableview_promote_row(g_input_path.c_str(),
|
||||||
|
g_app.promote_table_id.c_str(),
|
||||||
m.duckdb_path_abs.c_str(),
|
m.duckdb_path_abs.c_str(),
|
||||||
m.table_name.c_str(),
|
m.table_name.c_str(),
|
||||||
g_app.promote_row_id.c_str(),
|
g_app.promote_row_id.c_str(),
|
||||||
|
|||||||
@@ -573,6 +573,7 @@ bool find_existing_promotion(const char* ops_db, const char* duckdb_path,
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool tableview_promote_row(const char* ops_db,
|
bool tableview_promote_row(const char* ops_db,
|
||||||
|
const char* table_entity_id,
|
||||||
const char* duckdb_path,
|
const char* duckdb_path,
|
||||||
const char* duck_table,
|
const char* duck_table,
|
||||||
const char* row_id,
|
const char* row_id,
|
||||||
@@ -682,6 +683,30 @@ bool tableview_promote_row(const char* ops_db,
|
|||||||
sqlite3_bind_text(st, 4, meta.c_str(), -1, SQLITE_TRANSIENT);
|
sqlite3_bind_text(st, 4, meta.c_str(), -1, SQLITE_TRANSIENT);
|
||||||
bool ok = sqlite3_step(st) == SQLITE_DONE;
|
bool ok = sqlite3_step(st) == SQLITE_DONE;
|
||||||
sqlite3_finalize(st);
|
sqlite3_finalize(st);
|
||||||
|
|
||||||
|
// Inserta tambien la relacion CONTAINS_ROW de la tabla a la fila
|
||||||
|
// promovida — el viewport pintara la arista de pertenencia.
|
||||||
|
// Idempotente via INSERT OR IGNORE sobre id estable.
|
||||||
|
if (ok && table_entity_id && *table_entity_id) {
|
||||||
|
std::string rel_id = "rel_contains_" + sanitize_id_part(table_entity_id)
|
||||||
|
+ "_" + sanitize_id_part(entity_id.c_str());
|
||||||
|
const char* rins =
|
||||||
|
"INSERT OR IGNORE INTO relations("
|
||||||
|
" id, name, from_entity, to_entity, status, tags, "
|
||||||
|
" created_at, updated_at) "
|
||||||
|
"VALUES (?, 'CONTAINS_ROW', ?, ?, 'implemented', '[]', "
|
||||||
|
" strftime('%Y-%m-%dT%H:%M:%fZ','now'), "
|
||||||
|
" strftime('%Y-%m-%dT%H:%M:%fZ','now'))";
|
||||||
|
sqlite3_stmt* rst = nullptr;
|
||||||
|
if (sqlite3_prepare_v2(db, rins, -1, &rst, nullptr) == SQLITE_OK) {
|
||||||
|
sqlite3_bind_text(rst, 1, rel_id.c_str(), -1, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_text(rst, 2, table_entity_id, -1, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_text(rst, 3, entity_id.c_str(), -1, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_step(rst);
|
||||||
|
sqlite3_finalize(rst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
if (ok && out_entity_id && out_id_n > 0) {
|
if (ok && out_entity_id && out_id_n > 0) {
|
||||||
std::snprintf(out_entity_id, out_id_n, "%s", entity_id.c_str());
|
std::snprintf(out_entity_id, out_id_n, "%s", entity_id.c_str());
|
||||||
|
|||||||
@@ -119,7 +119,12 @@ bool tableview_set_columns(const char* ops_db, const char* entity_id,
|
|||||||
// "prom_<sanitize(row_type)>_<sanitize(row_id)>", e inserta en ops.entities
|
// "prom_<sanitize(row_type)>_<sanitize(row_id)>", e inserta en ops.entities
|
||||||
// con type_ref=row_type, name = valor del label_column (o row_id si vacio),
|
// con type_ref=row_type, name = valor del label_column (o row_id si vacio),
|
||||||
// metadata = { source: {duckdb, table, row_id}, <columnas> }.
|
// metadata = { source: {duckdb, table, row_id}, <columnas> }.
|
||||||
|
//
|
||||||
|
// Si table_entity_id no es nulo/vacio, inserta tambien una relacion
|
||||||
|
// CONTAINS_ROW (idempotente) entre la tabla y la nueva entidad para que el
|
||||||
|
// viewport pinte la arista de pertenencia.
|
||||||
bool tableview_promote_row(const char* ops_db,
|
bool tableview_promote_row(const char* ops_db,
|
||||||
|
const char* table_entity_id,
|
||||||
const char* duckdb_path,
|
const char* duckdb_path,
|
||||||
const char* duck_table,
|
const char* duck_table,
|
||||||
const char* row_id,
|
const char* row_id,
|
||||||
|
|||||||
+7
-2
@@ -571,14 +571,19 @@ std::vector<uint16_t> apply_types_yaml(GraphData& graph, const ParsedTypes& type
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Regla de forma: todo nodo es circulo EXCEPTO el tipo "Table" (issue
|
// Regla de forma: todo nodo es circulo EXCEPTO el tipo "Table" (issue
|
||||||
// 0010 — nodo tabla rectangular contenedor). Sobreescribe lo que diga el
|
// 0010 — nodo tabla cuadrado contenedor). Sobreescribe lo que diga el
|
||||||
// yaml: se aplica en cada reload, por lo que ediciones futuras desde el
|
// yaml: se aplica en cada reload, por lo que ediciones futuras desde el
|
||||||
// Type Editor no rompen la convencion.
|
// Type Editor no rompen la convencion. Tambien forzamos un tamano
|
||||||
|
// notablemente mayor (32 px world) para que la diferencia visual con
|
||||||
|
// un nodo normal sea evidente.
|
||||||
for (int i = 0; i < graph.type_count; ++i) {
|
for (int i = 0; i < graph.type_count; ++i) {
|
||||||
EntityType& et = graph.types[i];
|
EntityType& et = graph.types[i];
|
||||||
bool is_table = et.name && (eq_ci(et.name, std::string("Table"))
|
bool is_table = et.name && (eq_ci(et.name, std::string("Table"))
|
||||||
|| eq_ci(et.name, std::string("table")));
|
|| eq_ci(et.name, std::string("table")));
|
||||||
et.shape = is_table ? SHAPE_SQUARE : SHAPE_CIRCLE;
|
et.shape = is_table ? SHAPE_SQUARE : SHAPE_CIRCLE;
|
||||||
|
if (is_table) {
|
||||||
|
et.default_size = 32.0f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < graph.rel_type_count; ++i) {
|
for (int i = 0; i < graph.rel_type_count; ++i) {
|
||||||
|
|||||||
@@ -1801,17 +1801,21 @@ void views_table_windows_sync(AppState& app, const char* ops_db) {
|
|||||||
if (live.find(it->first) == live.end()) it = app.table_windows.erase(it);
|
if (live.find(it->first) == live.end()) it = app.table_windows.erase(it);
|
||||||
else ++it;
|
else ++it;
|
||||||
}
|
}
|
||||||
// Anadir las nuevas o refrescar metadata.
|
// Anadir las nuevas o refrescar metadata. Tras cualquier sync forzamos
|
||||||
|
// page_dirty = true para que la siguiente iteracion del render relea
|
||||||
|
// la pagina contra DuckDB (se evita asi mostrar pages obsoletas tras
|
||||||
|
// promote/demote/import — donde el flag promoted de cada fila puede
|
||||||
|
// haber cambiado).
|
||||||
for (auto& kv : live) {
|
for (auto& kv : live) {
|
||||||
auto& w = app.table_windows[kv.first];
|
auto& w = app.table_windows[kv.first];
|
||||||
bool was_present = !w.meta.entity_id.empty();
|
bool was_present = !w.meta.entity_id.empty();
|
||||||
w.meta = std::move(kv.second);
|
w.meta = std::move(kv.second);
|
||||||
w.open = true;
|
w.open = true;
|
||||||
|
w.page_dirty = true;
|
||||||
if (!was_present) {
|
if (!was_present) {
|
||||||
w.offset = 0;
|
w.offset = 0;
|
||||||
w.page.clear();
|
w.page.clear();
|
||||||
w.total_rows = 0;
|
w.total_rows = 0;
|
||||||
w.page_dirty = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2019,6 +2023,9 @@ void views_table_overlay(AppState& app) {
|
|||||||
if (!dl) return;
|
if (!dl) return;
|
||||||
ImFont* font = ImGui::GetFont();
|
ImFont* font = ImGui::GetFont();
|
||||||
|
|
||||||
|
// El cuadrado lo pinta el GPU (apply_types_yaml fija shape=SQUARE +
|
||||||
|
// size=32 para tipos Table). Aqui solo añadimos un contador discreto
|
||||||
|
// BAJO el cuadrado: "1000 rows".
|
||||||
for (int i = 0; i < g.node_count; ++i) {
|
for (int i = 0; i < g.node_count; ++i) {
|
||||||
const GraphNode& n = g.nodes[i];
|
const GraphNode& n = g.nodes[i];
|
||||||
if (!(n.flags & NF_VISIBLE)) continue;
|
if (!(n.flags & NF_VISIBLE)) continue;
|
||||||
@@ -2026,44 +2033,38 @@ void views_table_overlay(AppState& app) {
|
|||||||
const EntityType& t = g.types[n.type_id];
|
const EntityType& t = g.types[n.type_id];
|
||||||
if (!t.name || std::strcmp(t.name, "Table") != 0) continue;
|
if (!t.name || std::strcmp(t.name, "Table") != 0) continue;
|
||||||
|
|
||||||
const float vx = (n.x - app.viewport->cam_x) * app.viewport->zoom + cx;
|
const float zoom = app.viewport->zoom;
|
||||||
const float vy = (n.y - app.viewport->cam_y) * app.viewport->zoom + cy;
|
const float vx = (n.x - app.viewport->cam_x) * zoom + cx;
|
||||||
if (vx < wmin.x - 200 || vx > wmax.x + 200) continue;
|
const float vy = (n.y - app.viewport->cam_y) * zoom + cy;
|
||||||
|
if (vx < wmin.x - 100 || vx > wmax.x + 100) continue;
|
||||||
if (vy < wmin.y - 100 || vy > wmax.y + 100) continue;
|
if (vy < wmin.y - 100 || vy > wmax.y + 100) continue;
|
||||||
|
|
||||||
int64_t count = -1;
|
int64_t count = -1;
|
||||||
auto it = app.table_node_counts.find(n.user_data);
|
auto it = app.table_node_counts.find(n.user_data);
|
||||||
if (it != app.table_node_counts.end()) count = it->second;
|
if (it != app.table_node_counts.end()) count = it->second;
|
||||||
|
if (count < 0) continue;
|
||||||
|
|
||||||
char buf[96];
|
char buf[64];
|
||||||
if (count >= 0) std::snprintf(buf, sizeof(buf), TI_TABLE " Table %lld", (long long)count);
|
std::snprintf(buf, sizeof(buf), "%lld rows", (long long)count);
|
||||||
else std::snprintf(buf, sizeof(buf), TI_TABLE " Table");
|
|
||||||
|
|
||||||
const float font_size = 13.0f;
|
const float font_size = 12.0f;
|
||||||
ImVec2 ts = font ? font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, buf)
|
if (!font) continue;
|
||||||
: ImVec2(60.0f, 14.0f);
|
ImVec2 ts = font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, buf);
|
||||||
const float pad_x = 10.0f, pad_y = 6.0f;
|
|
||||||
const float w = std::max(96.0f, ts.x + pad_x * 2.0f);
|
|
||||||
const float h = ts.y + pad_y * 2.0f;
|
|
||||||
ImVec2 a(vx - w * 0.5f, vy - h * 0.5f);
|
|
||||||
ImVec2 b(vx + w * 0.5f, vy + h * 0.5f);
|
|
||||||
|
|
||||||
// Sombra ligera
|
// Posicion: bajo el cuadrado. La mitad del shape en pixeles depende
|
||||||
dl->AddRectFilled(ImVec2(a.x + 1, a.y + 2), ImVec2(b.x + 1, b.y + 2),
|
// del default_size del tipo y del zoom.
|
||||||
IM_COL32(0, 0, 0, 80), 6.0f);
|
const float half_h = (t.default_size * zoom) * 0.5f;
|
||||||
// Cuerpo
|
const float gap = 4.0f;
|
||||||
dl->AddRectFilled(a, b, IM_COL32(38, 56, 92, 240), 6.0f);
|
const float tx = vx - ts.x * 0.5f;
|
||||||
// Borde
|
const float ty = vy + half_h + gap;
|
||||||
uint32_t border = (n.flags & NF_SELECTED)
|
|
||||||
? IM_COL32(180, 200, 255, 255)
|
|
||||||
: IM_COL32(120, 160, 220, 220);
|
|
||||||
dl->AddRect(a, b, border, 6.0f, 0, (n.flags & NF_SELECTED) ? 2.0f : 1.5f);
|
|
||||||
|
|
||||||
if (font) {
|
// Pequeño bg semitransparente para que el texto sea legible sobre
|
||||||
dl->AddText(font, font_size,
|
// grafos densos, sin parecer un chip.
|
||||||
ImVec2(vx - ts.x * 0.5f, vy - ts.y * 0.5f),
|
dl->AddRectFilled(ImVec2(tx - 4, ty - 1),
|
||||||
IM_COL32(230, 240, 255, 255), buf);
|
ImVec2(tx + ts.x + 4, ty + ts.y + 1),
|
||||||
}
|
IM_COL32(20, 25, 35, 180), 3.0f);
|
||||||
|
dl->AddText(font, font_size, ImVec2(tx, ty),
|
||||||
|
IM_COL32(200, 220, 240, 230), buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user