refactor(tables): FASE 4 - Add DataSource adapters
Add MemoryDataSource for AdvancedTable (in-memory ArrayList) and PagedDataSource for VirtualAdvancedTable (paged DB data). Both implement TableDataSource interface from table_core.zig, enabling unified rendering patterns while respecting memory ownership. New files: - advanced_table/datasource.zig - MemoryDataSource - virtual_advanced_table/paged_datasource.zig - PagedDataSource 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
473bbdb648
commit
cf2f91f8bc
4 changed files with 316 additions and 0 deletions
|
|
@ -47,6 +47,10 @@ pub const state = @import("state.zig");
|
|||
pub const AdvancedTableState = state.AdvancedTableState;
|
||||
pub const AdvancedTableResult = state.AdvancedTableResult;
|
||||
|
||||
// Re-export datasource
|
||||
pub const datasource = @import("datasource.zig");
|
||||
pub const MemoryDataSource = datasource.MemoryDataSource;
|
||||
|
||||
// Re-export table_core types
|
||||
pub const NavigateDirection = table_core.NavigateDirection;
|
||||
|
||||
|
|
|
|||
126
src/widgets/advanced_table/datasource.zig
Normal file
126
src/widgets/advanced_table/datasource.zig
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
//! MemoryDataSource - Adaptador de TableDataSource para datos en memoria
|
||||
//!
|
||||
//! Wrappea AdvancedTableState + columnas para implementar la interfaz
|
||||
//! TableDataSource de table_core. Permite que AdvancedTable use el mismo
|
||||
//! patrón de renderizado que VirtualAdvancedTable.
|
||||
|
||||
const std = @import("std");
|
||||
const table_core = @import("../table_core.zig");
|
||||
const state_mod = @import("state.zig");
|
||||
const schema_mod = @import("schema.zig");
|
||||
const types = @import("types.zig");
|
||||
|
||||
const AdvancedTableState = state_mod.AdvancedTableState;
|
||||
const ColumnDef = schema_mod.ColumnDef;
|
||||
const CellValue = types.CellValue;
|
||||
const Row = types.Row;
|
||||
const TableDataSource = table_core.TableDataSource;
|
||||
|
||||
// =============================================================================
|
||||
// MemoryDataSource
|
||||
// =============================================================================
|
||||
|
||||
/// Adaptador que implementa TableDataSource para datos en memoria (ArrayList)
|
||||
/// Usa el patrón getCellValueInto para escritura segura en buffer proporcionado
|
||||
pub const MemoryDataSource = struct {
|
||||
/// Referencia al estado de la tabla
|
||||
state: *AdvancedTableState,
|
||||
|
||||
/// Definiciones de columnas (para mapear índice -> nombre)
|
||||
columns: []const ColumnDef,
|
||||
|
||||
/// Buffer interno para formateo (evita allocaciones)
|
||||
format_buffer: [256]u8 = undefined,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
/// Crea un nuevo MemoryDataSource
|
||||
pub fn init(state: *AdvancedTableState, columns: []const ColumnDef) Self {
|
||||
return .{
|
||||
.state = state,
|
||||
.columns = columns,
|
||||
};
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Implementación de TableDataSource
|
||||
// =========================================================================
|
||||
|
||||
/// Retorna el número total de filas
|
||||
pub fn getRowCount(self: *Self) usize {
|
||||
return self.state.getRowCount();
|
||||
}
|
||||
|
||||
/// Escribe el valor de una celda en el buffer proporcionado
|
||||
/// Retorna slice del buffer con el contenido
|
||||
pub fn getCellValueInto(self: *Self, row: usize, col: usize, buf: []u8) []const u8 {
|
||||
// Validar índices
|
||||
if (col >= self.columns.len) return "";
|
||||
|
||||
// Obtener fila
|
||||
const row_data = self.state.getRowConst(row) orelse return "";
|
||||
|
||||
// Obtener nombre de columna
|
||||
const col_name = self.columns[col].name;
|
||||
|
||||
// Obtener valor de celda
|
||||
const cell_value = row_data.get(col_name);
|
||||
|
||||
// Formatear en el buffer proporcionado
|
||||
return cell_value.format(buf);
|
||||
}
|
||||
|
||||
/// Retorna el ID único de una fila
|
||||
/// Para datos en memoria, usamos el índice como ID
|
||||
/// TODO: Si Row tuviera campo 'id', lo usaríamos
|
||||
pub fn getRowId(self: *Self, row: usize) i64 {
|
||||
_ = self;
|
||||
// Por ahora usamos el índice como ID
|
||||
// En el futuro, Row podría tener un campo 'id' explícito
|
||||
return @intCast(row);
|
||||
}
|
||||
|
||||
/// Verifica si una celda es editable
|
||||
pub fn isCellEditable(self: *Self, row: usize, col: usize) bool {
|
||||
_ = row;
|
||||
|
||||
// Verificar configuración de columna
|
||||
if (col >= self.columns.len) return false;
|
||||
return self.columns[col].editable;
|
||||
}
|
||||
|
||||
/// Invalida cache (no aplica para datos en memoria)
|
||||
pub fn invalidate(self: *Self) void {
|
||||
_ = self;
|
||||
// No hay cache que invalidar en datos en memoria
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Conversión a TableDataSource
|
||||
// =========================================================================
|
||||
|
||||
/// Crea TableDataSource vtable para este adaptador
|
||||
pub fn toDataSource(self: *Self) TableDataSource {
|
||||
return table_core.makeTableDataSource(Self, self);
|
||||
}
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Tests
|
||||
// =============================================================================
|
||||
|
||||
test "MemoryDataSource basic" {
|
||||
// Test básico de que compila y los tipos son correctos
|
||||
const columns = [_]ColumnDef{
|
||||
.{ .name = "id", .title = "ID", .width = 50 },
|
||||
.{ .name = "name", .title = "Name", .width = 200 },
|
||||
};
|
||||
|
||||
var state = AdvancedTableState.init(std.testing.allocator);
|
||||
defer state.deinit();
|
||||
|
||||
var ds = MemoryDataSource.init(&state, &columns);
|
||||
const tds = ds.toDataSource();
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 0), tds.getRowCount());
|
||||
}
|
||||
184
src/widgets/virtual_advanced_table/paged_datasource.zig
Normal file
184
src/widgets/virtual_advanced_table/paged_datasource.zig
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
//! PagedDataSource - Adaptador de TableDataSource para datos paginados
|
||||
//!
|
||||
//! Wrappea VirtualAdvancedTableState + DataProvider para implementar la interfaz
|
||||
//! TableDataSource de table_core. Permite unificar el pattern de renderizado
|
||||
//! entre AdvancedTable (memoria) y VirtualAdvancedTable (paginado).
|
||||
|
||||
const std = @import("std");
|
||||
const table_core = @import("../table_core.zig");
|
||||
const state_mod = @import("state.zig");
|
||||
const types = @import("types.zig");
|
||||
const data_provider_mod = @import("data_provider.zig");
|
||||
|
||||
const VirtualAdvancedTableState = state_mod.VirtualAdvancedTableState;
|
||||
const ColumnDef = types.ColumnDef;
|
||||
const RowData = types.RowData;
|
||||
const DataProvider = data_provider_mod.DataProvider;
|
||||
const TableDataSource = table_core.TableDataSource;
|
||||
|
||||
// =============================================================================
|
||||
// PagedDataSource
|
||||
// =============================================================================
|
||||
|
||||
/// Adaptador que implementa TableDataSource para datos paginados (ventana virtual).
|
||||
/// Lee valores desde current_window en el state, que es propiedad del DataProvider.
|
||||
pub const PagedDataSource = struct {
|
||||
/// Referencia al estado de la tabla (contiene current_window)
|
||||
state: *VirtualAdvancedTableState,
|
||||
|
||||
/// DataProvider subyacente (para getRowId cuando fuera de ventana)
|
||||
provider: ?DataProvider,
|
||||
|
||||
/// Definiciones de columnas (para validar índices)
|
||||
columns: []const ColumnDef,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
/// Crea un nuevo PagedDataSource
|
||||
pub fn init(
|
||||
state: *VirtualAdvancedTableState,
|
||||
columns: []const ColumnDef,
|
||||
provider: ?DataProvider,
|
||||
) Self {
|
||||
return .{
|
||||
.state = state,
|
||||
.columns = columns,
|
||||
.provider = provider,
|
||||
};
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Implementación de TableDataSource
|
||||
// =========================================================================
|
||||
|
||||
/// Retorna el número total de filas (filtered count)
|
||||
pub fn getRowCount(self: *Self) usize {
|
||||
// Usar conteo filtrado si está disponible
|
||||
const count_info = self.state.getDisplayCount();
|
||||
return count_info.value;
|
||||
}
|
||||
|
||||
/// Escribe el valor de una celda en el buffer proporcionado.
|
||||
/// El row es índice global, se convierte a índice de ventana.
|
||||
/// Retorna slice del buffer con el contenido.
|
||||
pub fn getCellValueInto(self: *Self, row: usize, col: usize, buf: []u8) []const u8 {
|
||||
// Validar columna
|
||||
if (col >= self.columns.len) return "";
|
||||
|
||||
// Convertir índice global a índice de ventana
|
||||
const window_idx = self.state.globalToWindowIndex(row) orelse {
|
||||
// Fila fuera de ventana actual - retornar vacío
|
||||
// (el caller debería asegurarse de pedir solo filas visibles)
|
||||
return "";
|
||||
};
|
||||
|
||||
// Obtener fila de la ventana
|
||||
if (window_idx >= self.state.current_window.len) return "";
|
||||
const row_data = self.state.current_window[window_idx];
|
||||
|
||||
// Obtener valor de la columna
|
||||
if (col >= row_data.values.len) return "";
|
||||
const value = row_data.values[col];
|
||||
|
||||
// Copiar al buffer proporcionado
|
||||
const copy_len = @min(value.len, buf.len);
|
||||
@memcpy(buf[0..copy_len], value[0..copy_len]);
|
||||
return buf[0..copy_len];
|
||||
}
|
||||
|
||||
/// Retorna el ID único de una fila.
|
||||
/// Si está en ventana, usa window data. Si no, consulta al provider.
|
||||
pub fn getRowId(self: *Self, row: usize) i64 {
|
||||
// Intentar obtener de la ventana
|
||||
if (self.state.globalToWindowIndex(row)) |window_idx| {
|
||||
if (window_idx < self.state.current_window.len) {
|
||||
return self.state.current_window[window_idx].id;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback al provider si está disponible
|
||||
if (self.provider) |provider| {
|
||||
return provider.getRowId(row) orelse -1;
|
||||
}
|
||||
|
||||
// Si no hay provider, usar índice como fallback
|
||||
return @intCast(row);
|
||||
}
|
||||
|
||||
/// Verifica si una celda es editable.
|
||||
/// TODO: Podría usar metadata de columnas cuando esté disponible.
|
||||
pub fn isCellEditable(self: *Self, row: usize, col: usize) bool {
|
||||
_ = row;
|
||||
// Por ahora, todas las columnas son no editables por defecto
|
||||
// La edición se maneja a nivel de VirtualAdvancedTable
|
||||
if (col >= self.columns.len) return false;
|
||||
return false; // VirtualAdvancedTable maneja edición de forma específica
|
||||
}
|
||||
|
||||
/// Invalida cache.
|
||||
/// Delega al state para forzar refetch.
|
||||
pub fn invalidate(self: *Self) void {
|
||||
self.state.invalidateWindow();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Conversión a TableDataSource
|
||||
// =========================================================================
|
||||
|
||||
/// Crea TableDataSource vtable para este adaptador
|
||||
pub fn toDataSource(self: *Self) TableDataSource {
|
||||
return table_core.makeTableDataSource(Self, self);
|
||||
}
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Tests
|
||||
// =============================================================================
|
||||
|
||||
test "PagedDataSource basic" {
|
||||
// Test básico de que compila y los tipos son correctos
|
||||
const columns = [_]ColumnDef{
|
||||
.{ .name = "id", .title = "ID", .width = 50 },
|
||||
.{ .name = "name", .title = "Name", .width = 200 },
|
||||
};
|
||||
|
||||
var state = VirtualAdvancedTableState{};
|
||||
var ds = PagedDataSource.init(&state, &columns, null);
|
||||
const tds = ds.toDataSource();
|
||||
|
||||
// Sin datos, row count es 0
|
||||
try std.testing.expectEqual(@as(usize, 0), tds.getRowCount());
|
||||
}
|
||||
|
||||
test "PagedDataSource getCellValueInto" {
|
||||
const columns = [_]ColumnDef{
|
||||
.{ .name = "id", .title = "ID", .width = 50 },
|
||||
.{ .name = "name", .title = "Name", .width = 200 },
|
||||
};
|
||||
|
||||
// Crear datos de prueba
|
||||
const values1 = [_][]const u8{ "1", "Alice" };
|
||||
const values2 = [_][]const u8{ "2", "Bob" };
|
||||
const rows = [_]RowData{
|
||||
.{ .id = 100, .values = &values1 },
|
||||
.{ .id = 101, .values = &values2 },
|
||||
};
|
||||
|
||||
var state = VirtualAdvancedTableState{};
|
||||
state.current_window = &rows;
|
||||
state.window_start = 10; // Ventana empieza en índice global 10
|
||||
state.filtered_count = .{ .value = 100, .state = .ready };
|
||||
|
||||
var ds = PagedDataSource.init(&state, &columns, null);
|
||||
|
||||
// Buffer para valores
|
||||
var buf: [256]u8 = undefined;
|
||||
|
||||
// Fila en ventana (global 10 = window 0)
|
||||
const val1 = ds.getCellValueInto(10, 1, &buf);
|
||||
try std.testing.expectEqualStrings("Alice", val1);
|
||||
|
||||
// Fila fuera de ventana
|
||||
const val2 = ds.getCellValueInto(0, 0, &buf);
|
||||
try std.testing.expectEqualStrings("", val2);
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ pub const types = @import("types.zig");
|
|||
pub const data_provider = @import("data_provider.zig");
|
||||
pub const state_mod = @import("state.zig");
|
||||
pub const cell_editor = @import("cell_editor.zig");
|
||||
pub const paged_datasource = @import("paged_datasource.zig");
|
||||
|
||||
// Tipos principales
|
||||
pub const RowData = types.RowData;
|
||||
|
|
@ -41,6 +42,7 @@ pub const CellId = types.CellId;
|
|||
pub const CellGeometry = types.CellGeometry;
|
||||
|
||||
pub const DataProvider = data_provider.DataProvider;
|
||||
pub const PagedDataSource = paged_datasource.PagedDataSource;
|
||||
pub const CellEditorColors = cell_editor.CellEditorColors;
|
||||
pub const CellEditorResult = cell_editor.CellEditorResult;
|
||||
pub const NavigateDirection = cell_editor.NavigateDirection;
|
||||
|
|
|
|||
Loading…
Reference in a new issue