2024-08-08 03:32:54 +08:00
|
|
|
import {CustomTableNode} from "../nodes/custom-table";
|
2024-08-09 18:24:25 +08:00
|
|
|
import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell";
|
2024-08-08 03:32:54 +08:00
|
|
|
import {$isTableRowNode} from "@lexical/table";
|
|
|
|
|
|
|
|
export class TableMap {
|
|
|
|
|
|
|
|
rowCount: number = 0;
|
|
|
|
columnCount: number = 0;
|
|
|
|
|
|
|
|
// Represents an array (rows*columns in length) of cell nodes from top-left to
|
|
|
|
// bottom right. Cells may repeat where merged and covering multiple spaces.
|
|
|
|
cells: CustomTableCellNode[] = [];
|
|
|
|
|
|
|
|
constructor(table: CustomTableNode) {
|
|
|
|
this.buildCellMap(table);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected buildCellMap(table: CustomTableNode) {
|
|
|
|
const rowsAndCells: CustomTableCellNode[][] = [];
|
|
|
|
const setCell = (x: number, y: number, cell: CustomTableCellNode) => {
|
|
|
|
if (typeof rowsAndCells[y] === 'undefined') {
|
|
|
|
rowsAndCells[y] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
rowsAndCells[y][x] = cell;
|
|
|
|
};
|
|
|
|
const cellFilled = (x: number, y: number): boolean => !!(rowsAndCells[y] && rowsAndCells[y][x]);
|
|
|
|
|
|
|
|
const rowNodes = table.getChildren().filter(r => $isTableRowNode(r));
|
|
|
|
for (let rowIndex = 0; rowIndex < rowNodes.length; rowIndex++) {
|
|
|
|
const rowNode = rowNodes[rowIndex];
|
|
|
|
const cellNodes = rowNode.getChildren().filter(c => $isCustomTableCellNode(c));
|
|
|
|
let targetColIndex: number = 0;
|
|
|
|
for (let cellIndex = 0; cellIndex < cellNodes.length; cellIndex++) {
|
|
|
|
const cellNode = cellNodes[cellIndex];
|
|
|
|
const colspan = cellNode.getColSpan() || 1;
|
|
|
|
const rowSpan = cellNode.getRowSpan() || 1;
|
|
|
|
for (let x = targetColIndex; x < targetColIndex + colspan; x++) {
|
|
|
|
for (let y = rowIndex; y < rowIndex + rowSpan; y++) {
|
|
|
|
while (cellFilled(x, y)) {
|
|
|
|
targetColIndex += 1;
|
|
|
|
x += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
setCell(x, y, cellNode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
targetColIndex += colspan;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.rowCount = rowsAndCells.length;
|
|
|
|
this.columnCount = Math.max(...rowsAndCells.map(r => r.length));
|
|
|
|
|
|
|
|
const cells = [];
|
|
|
|
let lastCell: CustomTableCellNode = rowsAndCells[0][0];
|
|
|
|
for (let y = 0; y < this.rowCount; y++) {
|
|
|
|
for (let x = 0; x < this.columnCount; x++) {
|
|
|
|
if (!rowsAndCells[y] || !rowsAndCells[y][x]) {
|
|
|
|
cells.push(lastCell);
|
|
|
|
} else {
|
|
|
|
cells.push(rowsAndCells[y][x]);
|
|
|
|
lastCell = rowsAndCells[y][x];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.cells = cells;
|
|
|
|
}
|
|
|
|
|
|
|
|
public getCellAtPosition(x: number, y: number): CustomTableCellNode {
|
|
|
|
const position = (y * this.columnCount) + x;
|
|
|
|
if (position >= this.cells.length) {
|
|
|
|
throw new Error(`TableMap Error: Attempted to get cell ${position+1} of ${this.cells.length}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.cells[position];
|
|
|
|
}
|
|
|
|
|
|
|
|
public getCellsInRange(fromX: number, fromY: number, toX: number, toY: number): CustomTableCellNode[] {
|
|
|
|
const minX = Math.max(Math.min(fromX, toX), 0);
|
|
|
|
const maxX = Math.min(Math.max(fromX, toX), this.columnCount - 1);
|
|
|
|
const minY = Math.max(Math.min(fromY, toY), 0);
|
|
|
|
const maxY = Math.min(Math.max(fromY, toY), this.rowCount - 1);
|
|
|
|
|
|
|
|
const cells = new Set<CustomTableCellNode>();
|
|
|
|
|
|
|
|
for (let y = minY; y <= maxY; y++) {
|
|
|
|
for (let x = minX; x <= maxX; x++) {
|
|
|
|
cells.add(this.getCellAtPosition(x, y));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return [...cells.values()];
|
|
|
|
}
|
|
|
|
}
|