parent
c87315006d
commit
eff273bf09
153
frontend/src/lib/table/table.js
Normal file
153
frontend/src/lib/table/table.js
Normal file
@ -0,0 +1,153 @@
|
||||
const DEBUG_MODE = /localhost/.test(window.location.href) ? 1 : 0;
|
||||
|
||||
import * as defer from 'lodash.defer';
|
||||
|
||||
class Overhang {
|
||||
colSpan;
|
||||
rowSpan;
|
||||
|
||||
constructor(colSpan, rowSpan) {
|
||||
this.colSpan = colSpan;
|
||||
this.rowSpan = rowSpan;
|
||||
|
||||
if (new.target === Overhang)
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
nextLine() {
|
||||
return new Overhang(this.colSpan, Math.max(0, this.rowSpan - 1));
|
||||
}
|
||||
|
||||
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 overhangs = new Array();
|
||||
let currentRow = 0;
|
||||
|
||||
for (const rowParent of this._table.rows) {
|
||||
let currentOverhangs = Array.from(overhangs);
|
||||
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());
|
||||
|
||||
cellBefore += overhang.colSpan;
|
||||
}
|
||||
|
||||
currentOverhangs = currentOverhangs.slice(i);
|
||||
|
||||
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 > 0) {
|
||||
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));
|
||||
cellBefore += this.colSpan(cell);
|
||||
}
|
||||
|
||||
overhangs = 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);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import * as toposort from 'toposort';
|
||||
|
||||
const DEBUG_MODE = /localhost/.test(window.location.href) ? 2 : 0;
|
||||
const DEBUG_MODE = /localhost/.test(window.location.href) ? 1 : 0;
|
||||
|
||||
export class UtilRegistry {
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import { TableIndices } from '../../lib/table/table';
|
||||
|
||||
const CHECKBOX_SELECTOR = '[type="checkbox"]';
|
||||
|
||||
@ -10,8 +11,10 @@ const CHECK_ALL_INITIALIZED_CLASS = 'check-all--initialized';
|
||||
export class CheckAll {
|
||||
_element;
|
||||
|
||||
_columns = [];
|
||||
_checkAllColumns = [];
|
||||
_columns = new Array();
|
||||
_checkAllColumns = new Array();
|
||||
|
||||
_tableIndices;
|
||||
|
||||
constructor(element, app) {
|
||||
if (!element) {
|
||||
@ -24,6 +27,8 @@ export class CheckAll {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._tableIndices = new TableIndices(this._element);
|
||||
|
||||
this._gatherColumns();
|
||||
this._findCheckboxColumns().forEach(columnId => this._checkAllColumns.push(new CheckAllColumn(this._element, app, this._columns[columnId])));
|
||||
|
||||
@ -32,28 +37,23 @@ export class CheckAll {
|
||||
}
|
||||
|
||||
_gatherColumns() {
|
||||
const rows = Array.from(this._element.rows);
|
||||
const cols = [];
|
||||
rows.forEach((tr) => {
|
||||
const cells = Array.from(tr.cells);
|
||||
cells.forEach(cell => {
|
||||
let i = 0;
|
||||
for (const sibling of cells.slice(0, cell.cellIndex))
|
||||
i += Math.max(1, sibling.colSpan) || 1;
|
||||
for (const rowIndex of new Array(this._tableIndices.maxRow + 1)) {
|
||||
for (const colIndex of new Array(this._tableIndices.maxCol + 1)) {
|
||||
const cell = this._tableIndices.getCell(rowIndex, colIndex);
|
||||
|
||||
for (let j = i; j < i + cell.colSpan; j++) {
|
||||
if (!cols[j]) {
|
||||
cols[j] = [];
|
||||
}
|
||||
cols[j].push(cell);
|
||||
}
|
||||
});
|
||||
});
|
||||
this._columns = cols;
|
||||
if (!cell)
|
||||
continue;
|
||||
|
||||
if (!this._columns[colIndex])
|
||||
this._columns[colIndex] = new Array();
|
||||
|
||||
this._columns[colIndex][rowIndex] = cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_findCheckboxColumns() {
|
||||
let checkboxColumnIds = [];
|
||||
let checkboxColumnIds = new Array();
|
||||
this._columns.forEach((col, i) => {
|
||||
if (this._isCheckboxColumn(col)) {
|
||||
checkboxColumnIds.push(i);
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-manager';
|
||||
import './hide-columns.sass';
|
||||
import * as memoize from 'lodash.memoize';
|
||||
|
||||
import { TableIndices } from '../../lib/table/table';
|
||||
|
||||
const HIDE_COLUMNS_CONTAINER_IDENT = 'uw-hide-columns';
|
||||
const TABLE_HEADER_IDENT = 'uw-hide-column-header';
|
||||
@ -37,6 +38,8 @@ export class HideColumns {
|
||||
|
||||
_mutationObserver;
|
||||
|
||||
_tableIndices;
|
||||
|
||||
headerToHider = new Map();
|
||||
hiderToHeader = new Map();
|
||||
|
||||
@ -60,6 +63,8 @@ export class HideColumns {
|
||||
|
||||
this._element = element;
|
||||
|
||||
this._tableIndices = new TableIndices(this._element);
|
||||
|
||||
const hideColumnsContainer = this._element.closest(`[${HIDE_COLUMNS_CONTAINER_IDENT}]`);
|
||||
if (!hideColumnsContainer) {
|
||||
throw new Error('Hide Columns utility needs to be setup on a table inside a hide columns container!');
|
||||
@ -250,8 +255,7 @@ export class HideColumns {
|
||||
|
||||
// console.debug('_tableMutated', { mutationList });
|
||||
|
||||
this._colIndexMemoized = undefined;
|
||||
this._getColMemoized = undefined;
|
||||
this._tableIndices = new TableIndices(this._element, { colSpan: this.colSpan.bind(this) });
|
||||
|
||||
Array.from(this._element.rows)
|
||||
.flatMap(row => Array.from(row.cells))
|
||||
@ -354,71 +358,12 @@ export class HideColumns {
|
||||
return originalColspan ? Math.max(colspan, originalColspan) : colspan;
|
||||
}
|
||||
|
||||
colIndexDirect(cell) {
|
||||
if (!cell)
|
||||
return 0;
|
||||
|
||||
const rowParent = cell.closest('tr');
|
||||
|
||||
if (!rowParent)
|
||||
return 0;
|
||||
|
||||
let rowBefore = 0;
|
||||
for (const cell of Array.from(rowParent.cells).slice(0, cell.cellIndex)) {
|
||||
if (!cell)
|
||||
continue;
|
||||
|
||||
rowBefore += this.colSpan(cell);
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
for (const pRow of Array.from(this._element.rows).slice(0, rowParent.rowIndex + 1)) {
|
||||
if (!pRow)
|
||||
continue;
|
||||
|
||||
let space = 0;
|
||||
for (const cell of pRow === rowParent ? Array.from(pRow.cells).slice(0, cell.cellIndex) : pRow.cells) {
|
||||
if (!cell)
|
||||
continue;
|
||||
|
||||
const rowSpan = cell.rowSpan || 1;
|
||||
if (rowParent.rowIndex - pRow.rowIndex < rowSpan)
|
||||
i += this.colSpan(cell);
|
||||
else if (pRow !== rowParent)
|
||||
space += this.colSpan(cell);
|
||||
|
||||
if (space > rowBefore)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// console.debug({ rowParent, cell, rowBefore, i });
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
_colIndexMemoized;
|
||||
|
||||
colIndex(cell) {
|
||||
if (!this._colIndexMemoized)
|
||||
this._colIndexMemoized = memoize(this.colIndexDirect.bind(this));
|
||||
|
||||
return this._colIndexMemoized(cell);
|
||||
return this._tableIndices.colIndex(cell);
|
||||
}
|
||||
|
||||
getColDirect(row, columnIndex) {
|
||||
for (const cell of row.cells)
|
||||
if (cell && this.colIndex(cell) <= columnIndex && this.colIndex(cell) + this.colSpan(cell) > columnIndex)
|
||||
return cell;
|
||||
}
|
||||
|
||||
_getColMemoized;
|
||||
|
||||
getCol(row, columnIndex) {
|
||||
if (!this._getColMemoized)
|
||||
this._getColMemoized = memoize(this.getColDirect.bind(this), (row, columnIndex) => Array.of(row.rowIndex, columnIndex).toString());
|
||||
|
||||
return this._getColMemoized(row, columnIndex);
|
||||
getCol(row, col) {
|
||||
return this._tableIndices.getCell(row.rowIndex, col);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5
package-lock.json
generated
5
package-lock.json
generated
@ -10952,6 +10952,11 @@
|
||||
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.defer": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defer/-/lodash.defer-4.1.0.tgz",
|
||||
"integrity": "sha1-6cFYqWHeGkbqJP2jRoW0zN01jz8="
|
||||
},
|
||||
"lodash.filter": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz",
|
||||
|
||||
@ -89,7 +89,6 @@
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"karma-webpack": "^3.0.5",
|
||||
"lint-staged": "^8.2.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"mini-css-extract-plugin": "^0.8.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"null-loader": "^2.0.0",
|
||||
@ -120,6 +119,8 @@
|
||||
"@juggle/resize-observer": "^2.5.0",
|
||||
"core-js": "^3.6.5",
|
||||
"js-cookie": "^2.2.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.defer": "^4.1.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"moment": "^2.25.3",
|
||||
"npm": "^6.14.5",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user