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