158 lines
3.9 KiB
JavaScript
158 lines
3.9 KiB
JavaScript
import * as F from '@efforting.tech/schema/field-configuration-factories';
|
|
import { parse_csv } from '@efforting.tech/data/string-utilities';
|
|
|
|
export const Row_Based_Table_Settings = new F.Schema({
|
|
|
|
rows: F.factory(() => [], 'Rows to initialize table with'),
|
|
row_names: F.typed_factory((v) => typeof v === 'string' ? parse_csv(v) : v, () => [], 'Names of rows'),
|
|
column_names: F.typed_factory((v) => typeof v === 'string' ? parse_csv(v) : v, () => [], 'Names of columns'),
|
|
|
|
}, 'Row based table settings');
|
|
|
|
|
|
export class Table_Row_Reference {
|
|
|
|
constructor(table, index, snapshot=null) {
|
|
Object.assign(this, { table, index, snapshot });
|
|
}
|
|
|
|
get value() {
|
|
const { table, index, snapshot } = this;
|
|
return snapshot ?? table.read_row(index);
|
|
}
|
|
|
|
get object() {
|
|
const { table, index, snapshot } = this;
|
|
const { column_names } = table;
|
|
const value = snapshot ?? table.read_row(index);
|
|
//TODO - check shape of column_names vs the value
|
|
|
|
return Object.fromEntries(value.map((cell, column_index) => [column_names[column_index], cell]));
|
|
}
|
|
|
|
|
|
update(updates) {
|
|
const { table, index, snapshot } = this;
|
|
if (snapshot) {
|
|
throw new Error('Can not update snapshot references, clear the snapshot to reuse this index'); //TODO - proper error
|
|
}
|
|
table.write_row(index, updates);
|
|
}
|
|
|
|
}
|
|
|
|
export class Row_Based_Table {
|
|
|
|
constructor(settings) {
|
|
const { rows, column_names, row_names } = Row_Based_Table_Settings.load(settings);
|
|
Object.assign(this, { rows });
|
|
this.set_column_names(...column_names);
|
|
this.set_row_names(...row_names);
|
|
}
|
|
|
|
// General operations
|
|
|
|
get width() {
|
|
const { rows, column_names } = this;
|
|
return rows[0]?.length || column_names.length || undefined;
|
|
}
|
|
|
|
get length() {
|
|
const { rows } = this;
|
|
return rows.length;
|
|
}
|
|
|
|
get size() {
|
|
const { width, length } = this;
|
|
return [width, length];
|
|
}
|
|
|
|
replace_all_cells(replacement_fn) {
|
|
|
|
for (const [row_index, row] of this.rows.entries()) {
|
|
const pending_row = [];
|
|
for (const [column_index, cell] of row.entries()) {
|
|
const row_name = this.row_names[row_index];
|
|
const column_name = this.column_names[column_index];
|
|
pending_row.push(replacement_fn({ row_name, column_name, row_index, column_index, row, cell }));
|
|
}
|
|
this.rows[row_index] = pending_row;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
// TODO: Make sure column and row operations covers the same uses
|
|
// TODO: Sub table operations
|
|
|
|
// Column operations
|
|
|
|
set_column_names(...names) {
|
|
this.column_names = names;
|
|
this.column_names_lut = Object.fromEntries(names.map((name, index) => [name, index]));
|
|
}
|
|
|
|
// Row operations
|
|
|
|
set_row_names(...names) {
|
|
this.row_names = names;
|
|
this.row_names_lut = Object.fromEntries(names.map((name, index) => [name, index]));
|
|
}
|
|
|
|
read_rows(...indices) {
|
|
const result = [];
|
|
for (const index of indices) {
|
|
result.push(new Table_Row_Reference(this, index));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
snapshot_rows(...indices) {
|
|
//NOTE: Snapshot doesn't include current layout or other settings, just the contents of the row at the time, and it currently is a shallow copy
|
|
const result = [];
|
|
for (const index of indices) {
|
|
result.push(new Table_Row_Reference(this, index, [...this.rows[index]]));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
push_rows(...rows) {
|
|
//TODO - verify shape
|
|
this.rows.push(...rows);
|
|
}
|
|
|
|
read_row(index) {
|
|
return this.rows[index];
|
|
}
|
|
|
|
write_row(index, row_data) {
|
|
const { rows, column_names_lut } = this;
|
|
if (Array.isArray(row_data)) {
|
|
//TODO - shape check
|
|
rows[index] = row_data;
|
|
} else {
|
|
//TODO - possibly allow array of array for using numerical indices
|
|
const work_row = rows[index];
|
|
for (const [key, value] of Object.entries(row_data)) {
|
|
const col_index = column_names_lut[key];
|
|
|
|
if (col_index === undefined) {
|
|
throw new Error(`Unknown column: ${key}`); //TODO - proper error
|
|
}
|
|
|
|
work_row[col_index] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
*[Symbol.iterator]() {
|
|
for (const index of this.rows.keys()) {
|
|
yield new Table_Row_Reference(this, index);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|