189 lines
4.8 KiB
JavaScript
189 lines
4.8 KiB
JavaScript
// SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
const DEBUG_MODE = /localhost/.test(window.location.href) ? 0 : 0;
|
|
|
|
import defer from 'lodash.defer';
|
|
|
|
class Overhang {
|
|
colSpan;
|
|
rowSpan;
|
|
cell;
|
|
|
|
constructor(colSpan, rowSpan, cell) {
|
|
this.colSpan = colSpan;
|
|
this.rowSpan = rowSpan;
|
|
this.cell = cell;
|
|
|
|
if (new.target === Overhang)
|
|
Object.freeze(this);
|
|
}
|
|
|
|
nextLine() {
|
|
return new Overhang(this.colSpan, Math.max(0, this.rowSpan - 1), this.cell);
|
|
}
|
|
|
|
reduceCol(n) {
|
|
if (this.colSpan > n)
|
|
return new Overhang(this.colSpan - n, this.rowSpan, this.cell);
|
|
else
|
|
return null;
|
|
}
|
|
|
|
isHole() {
|
|
return this.rowSpan <= 0;
|
|
}
|
|
}
|
|
|
|
const instanceCache = new Map();
|
|
|
|
export class TableIndices {
|
|
_table;
|
|
|
|
_cellToIndices = new Map();
|
|
_indicesToCell = new Array();
|
|
|
|
colSpan = cell => cell ? Math.max(1, cell.colSpan || 1) : 1;
|
|
rowSpan = cell => cell ? Math.max(1, cell.rowSpan || 1) : 1;
|
|
|
|
maxRow = 0;
|
|
maxCol = 0;
|
|
|
|
constructor(table, overrides) {
|
|
const prev = instanceCache.get(table);
|
|
if ( prev?.instance &&
|
|
overrides?.colSpan === prev.overrides?.colSpan &&
|
|
overrides?.rowSpan === prev.overrides?.rowSpan
|
|
) {
|
|
if (DEBUG_MODE > 0)
|
|
console.log('Reusing existing TableIndices', table, overrides, prev);
|
|
|
|
return prev.instance;
|
|
}
|
|
|
|
if (overrides && overrides.colSpan)
|
|
this.colSpan = overrides.colSpan;
|
|
if (overrides && overrides.rowSpan)
|
|
this.rowSpan = overrides.rowSpan;
|
|
|
|
this._table = table;
|
|
|
|
|
|
let currentOverhangs = new Array();
|
|
let currentRow = 0;
|
|
|
|
for (const rowParent of this._table.rows) {
|
|
let newOverhangs = new Array();
|
|
|
|
let cellBefore = 0;
|
|
|
|
for (const cell of rowParent.cells) {
|
|
let i;
|
|
|
|
for (i = 0; i < currentOverhangs.length; i++) {
|
|
const overhang = currentOverhangs[i];
|
|
|
|
if (overhang.isHole())
|
|
break;
|
|
else
|
|
newOverhangs.push(overhang.nextLine());
|
|
|
|
if (DEBUG_MODE > 1)
|
|
console.log('From overhang', overhang);
|
|
|
|
cellBefore += overhang.colSpan;
|
|
}
|
|
|
|
currentOverhangs = currentOverhangs.slice(i);
|
|
let remCols = this.colSpan(cell);
|
|
while (remCols > 0 && currentOverhangs[0]) {
|
|
let firstOverhang = currentOverhangs[0].reduceCol(this.colSpan(cell));
|
|
if (firstOverhang) {
|
|
if (DEBUG_MODE > 1)
|
|
console.log('Replace first overhang', remCols, currentOverhangs[0], firstOverhang);
|
|
currentOverhangs[0] = firstOverhang;
|
|
break;
|
|
} else {
|
|
if (DEBUG_MODE > 1)
|
|
console.log('Drop first overhang', remCols, currentOverhangs[0], firstOverhang);
|
|
remCols -= currentOverhangs[0].colSpan;
|
|
currentOverhangs.shift();
|
|
}
|
|
}
|
|
|
|
this._cellToIndices.set(cell, { row: currentRow, col: cellBefore });
|
|
|
|
let rows = range(currentRow, currentRow + this.rowSpan(cell));
|
|
let columns = range(cellBefore, cellBefore + this.colSpan(cell));
|
|
|
|
if (DEBUG_MODE > 1) {
|
|
console.log('Result', rows, columns);
|
|
|
|
cell.dataset.rows = JSON.stringify(rows);
|
|
cell.dataset.columns = JSON.stringify(columns);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
for (const col of columns) {
|
|
if (!this._indicesToCell[row])
|
|
this._indicesToCell[row] = new Array();
|
|
|
|
this._indicesToCell[row][col] = cell;
|
|
|
|
this.maxRow = Math.max(row, this.maxRow);
|
|
this.maxCol = Math.max(col, this.maxCol);
|
|
}
|
|
}
|
|
|
|
newOverhangs.push(new Overhang(this.colSpan(cell), this.rowSpan(cell) - 1, cell));
|
|
|
|
if (DEBUG_MODE > 1)
|
|
console.log('From current cell', this.colSpan(cell));
|
|
cellBefore += this.colSpan(cell);
|
|
}
|
|
|
|
currentOverhangs = Array.from(newOverhangs);
|
|
currentRow++;
|
|
}
|
|
|
|
if (DEBUG_MODE > 1) {
|
|
console.log(this._cellToIndices);
|
|
console.table(this._indicesToCell);
|
|
}
|
|
|
|
instanceCache.set(table, { overrides: overrides, instance: this });
|
|
defer(() => { instanceCache.delete(table); } );
|
|
}
|
|
|
|
colIndex(cell) {
|
|
return this.getIndices(cell)?.col;
|
|
}
|
|
|
|
rowIndex(cell) {
|
|
return this.getIndices(cell)?.row;
|
|
}
|
|
|
|
getIndices(cell) {
|
|
const res = this._cellToIndices.get(cell);
|
|
|
|
if (DEBUG_MODE > 2)
|
|
console.log('getIndices', cell, res);
|
|
|
|
return res;
|
|
}
|
|
|
|
getCell(row, col) {
|
|
const cell = this._indicesToCell[row]?.[col];
|
|
|
|
if (DEBUG_MODE > 2)
|
|
console.log('getCell', row, col, cell);
|
|
|
|
return cell;
|
|
}
|
|
}
|
|
|
|
function range(from, to) {
|
|
return [...Array(to - from).keys()].map(n => n + from);
|
|
}
|