// SPDX-FileCopyrightText: 2022 Gregor Kleen ,Sarah Vaupel // // 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); }