diff --git a/packages/web/src/datagrid/ColumnHeaderControl.svelte b/packages/web/src/datagrid/ColumnHeaderControl.svelte
new file mode 100644
index 000000000..69af6b3c3
--- /dev/null
+++ b/packages/web/src/datagrid/ColumnHeaderControl.svelte
@@ -0,0 +1,40 @@
+
+
+
+
+
diff --git a/packages/web/src/datagrid/ColumnLabel.svelte b/packages/web/src/datagrid/ColumnLabel.svelte
new file mode 100644
index 000000000..931f88cab
--- /dev/null
+++ b/packages/web/src/datagrid/ColumnLabel.svelte
@@ -0,0 +1,46 @@
+
+
+
+
+
+ {#if icon}
+
+ {/if}
+ {headerText || columnName}
+ {#if extInfo}
+ {extInfo}
+ {/if}
+
+
+
diff --git a/packages/web/src/datagrid/DataGrid.svelte b/packages/web/src/datagrid/DataGrid.svelte
index cab71cf0c..2d28f7558 100644
--- a/packages/web/src/datagrid/DataGrid.svelte
+++ b/packages/web/src/datagrid/DataGrid.svelte
@@ -1,15 +1,6 @@
diff --git a/packages/web/src/datagrid/DataGridCore.svelte b/packages/web/src/datagrid/DataGridCore.svelte
index 58a98ee65..99526f444 100644
--- a/packages/web/src/datagrid/DataGridCore.svelte
+++ b/packages/web/src/datagrid/DataGridCore.svelte
@@ -1,9 +1,64 @@
-
+
+
+
-
+
+ {#each visibleRealColumns as col (col.uniqueName)}
+
+ {/each}
diff --git a/packages/web/src/datagrid/LoadingDataGridCore.svelte b/packages/web/src/datagrid/LoadingDataGridCore.svelte
index d1258c4bb..b02094499 100644
--- a/packages/web/src/datagrid/LoadingDataGridCore.svelte
+++ b/packages/web/src/datagrid/LoadingDataGridCore.svelte
@@ -65,4 +65,4 @@
};
-
+
diff --git a/packages/web/src/datagrid/SeriesSizes.ts b/packages/web/src/datagrid/SeriesSizes.ts
new file mode 100644
index 000000000..f221a4c56
--- /dev/null
+++ b/packages/web/src/datagrid/SeriesSizes.ts
@@ -0,0 +1,341 @@
+import _ from 'lodash';
+
+export class SeriesSizeItem {
+ public scrollIndex: number = -1;
+ public modelIndex: number;
+ public frozenIndex: number = -1;
+ public size: number;
+ public position: number;
+ public get endPosition(): number {
+ return this.position + this.size;
+ }
+}
+
+export class SeriesSizes {
+ private sizeOverridesByModelIndex: { [id: number]: number } = {};
+ public count: number = 0;
+ public defaultSize: number = 50;
+ public maxSize: number = 1000;
+ private hiddenAndFrozenModelIndexes: number[] = [];
+ private frozenModelIndexes: number[] = [];
+ private hiddenModelIndexes: number[] = [];
+ private scrollItems: SeriesSizeItem[] = [];
+ private positions: number[] = [];
+ private scrollIndexes: number[] = [];
+ private frozenItems: SeriesSizeItem[] = [];
+
+ public get scrollCount(): number {
+ return this.count - (this.hiddenAndFrozenModelIndexes != null ? this.hiddenAndFrozenModelIndexes.length : 0);
+ }
+ public get frozenCount(): number {
+ return this.frozenModelIndexes != null ? this.frozenModelIndexes.length : 0;
+ }
+ public get frozenSize(): number {
+ return _.sumBy(this.frozenItems, x => x.size);
+ }
+ public get realCount(): number {
+ return this.frozenCount + this.scrollCount;
+ }
+
+ // public clear(): void {
+ // this.scrollItems = [];
+ // this.sizeOverridesByModelIndex = {};
+ // this.positions = [];
+ // this.scrollIndexes = [];
+ // this.frozenItems = [];
+ // this.hiddenAndFrozenModelIndexes = null;
+ // this.frozenModelIndexes = null;
+ // }
+ public putSizeOverride(modelIndex: number, size: number, sizeByUser = false): void {
+ if (this.maxSize && size > this.maxSize && !sizeByUser) {
+ size = this.maxSize;
+ }
+
+ let currentSize = this.sizeOverridesByModelIndex[modelIndex];
+ if (sizeByUser || !currentSize || size > currentSize) {
+ this.sizeOverridesByModelIndex[modelIndex] = size;
+ }
+ // if (!_.has(this.sizeOverridesByModelIndex, modelIndex))
+ // this.sizeOverridesByModelIndex[modelIndex] = size;
+ // if (size > this.sizeOverridesByModelIndex[modelIndex])
+ // this.sizeOverridesByModelIndex[modelIndex] = size;
+ }
+ public buildIndex(): void {
+ this.scrollItems = [];
+ this.scrollIndexes = _.filter(
+ _.map(_.range(this.count), x => this.modelToReal(x) - this.frozenCount),
+ // _.map(this.intKeys(_.keys(this.sizeOverridesByModelIndex)), (x) => this.modelToReal(x) - this.frozenCount),
+ x => x >= 0
+ );
+ this.scrollIndexes.sort();
+ let lastScrollIndex: number = -1;
+ let lastEndPosition: number = 0;
+ this.scrollIndexes.forEach(scrollIndex => {
+ let modelIndex: number = this.realToModel(scrollIndex + this.frozenCount);
+ let size: number = this.sizeOverridesByModelIndex[modelIndex];
+ let item = new SeriesSizeItem();
+ item.scrollIndex = scrollIndex;
+ item.modelIndex = modelIndex;
+ item.size = size;
+ item.position = lastEndPosition + (scrollIndex - lastScrollIndex - 1) * this.defaultSize;
+ this.scrollItems.push(item);
+ lastScrollIndex = scrollIndex;
+ lastEndPosition = item.endPosition;
+ });
+ this.positions = _.map(this.scrollItems, x => x.position);
+ this.frozenItems = [];
+ let lastpos: number = 0;
+ for (let i: number = 0; i < this.frozenCount; i++) {
+ let modelIndex: number = this.frozenModelIndexes[i];
+ let size: number = this.getSizeByModelIndex(modelIndex);
+ let item = new SeriesSizeItem();
+ item.frozenIndex = i;
+ item.modelIndex = modelIndex;
+ item.size = size;
+ item.position = lastpos;
+ this.frozenItems.push(item);
+ lastpos += size;
+ }
+ }
+
+ public getScrollIndexOnPosition(position: number): number {
+ let itemOrder: number = _.sortedIndex(this.positions, position);
+ if (this.positions[itemOrder] == position) return itemOrder;
+ if (itemOrder == 0) return Math.floor(position / this.defaultSize);
+ if (position <= this.scrollItems[itemOrder - 1].endPosition) return this.scrollItems[itemOrder - 1].scrollIndex;
+ return (
+ Math.floor((position - this.scrollItems[itemOrder - 1].position) / this.defaultSize) +
+ this.scrollItems[itemOrder - 1].scrollIndex
+ );
+ }
+ public getFrozenIndexOnPosition(position: number): number {
+ this.frozenItems.forEach(function (item) {
+ if (position >= item.position && position <= item.endPosition) return item.frozenIndex;
+ });
+ return -1;
+ }
+ // public getSizeSum(startScrollIndex: number, endScrollIndex: number): number {
+ // let order1: number = _.sortedIndexOf(this.scrollIndexes, startScrollIndex);
+ // let order2: number = _.sortedIndexOf(this.scrollIndexes, endScrollIndex);
+ // let count: number = endScrollIndex - startScrollIndex;
+ // if (order1 < 0)
+ // order1 = ~order1;
+ // if (order2 < 0)
+ // order2 = ~order2;
+ // let result: number = 0;
+ // for (let i: number = order1; i <= order2; i++) {
+ // if (i < 0)
+ // continue;
+ // if (i >= this.scrollItems.length)
+ // continue;
+ // let item = this.scrollItems[i];
+ // if (item.scrollIndex < startScrollIndex)
+ // continue;
+ // if (item.scrollIndex >= endScrollIndex)
+ // continue;
+ // result += item.size;
+ // count--;
+ // }
+ // result += count * this.defaultSize;
+ // return result;
+ // }
+ public getSizeByModelIndex(modelIndex: number): number {
+ if (_.has(this.sizeOverridesByModelIndex, modelIndex)) return this.sizeOverridesByModelIndex[modelIndex];
+ return this.defaultSize;
+ }
+ public getSizeByScrollIndex(scrollIndex: number): number {
+ return this.getSizeByRealIndex(scrollIndex + this.frozenCount);
+ }
+ public getSizeByRealIndex(realIndex: number): number {
+ let modelIndex: number = this.realToModel(realIndex);
+ return this.getSizeByModelIndex(modelIndex);
+ }
+ public removeSizeOverride(realIndex: number): void {
+ let modelIndex: number = this.realToModel(realIndex);
+ delete this.sizeOverridesByModelIndex[modelIndex];
+ }
+ public getScroll(sourceScrollIndex: number, targetScrollIndex: number): number {
+ if (sourceScrollIndex < targetScrollIndex) {
+ return -_.sum(
+ _.map(_.range(sourceScrollIndex, targetScrollIndex - sourceScrollIndex), x => this.getSizeByScrollIndex(x))
+ );
+ } else {
+ return _.sum(
+ _.map(_.range(targetScrollIndex, sourceScrollIndex - targetScrollIndex), x => this.getSizeByScrollIndex(x))
+ );
+ }
+ }
+ public modelIndexIsInScrollArea(modelIndex: number): boolean {
+ let realIndex = this.modelToReal(modelIndex);
+ return realIndex >= this.frozenCount;
+ }
+ public getTotalScrollSizeSum(): number {
+ let scrollSizeOverrides = _.map(
+ _.filter(this.intKeys(this.sizeOverridesByModelIndex), x => this.modelIndexIsInScrollArea(x)),
+ x => this.sizeOverridesByModelIndex[x]
+ );
+ return _.sum(scrollSizeOverrides) + (this.count - scrollSizeOverrides.length) * this.defaultSize;
+ }
+ public getVisibleScrollSizeSum(): number {
+ let scrollSizeOverrides = _.map(
+ _.filter(this.intKeys(this.sizeOverridesByModelIndex), x => !_.includes(this.hiddenAndFrozenModelIndexes, x)),
+ x => this.sizeOverridesByModelIndex[x]
+ );
+ return (
+ _.sum(scrollSizeOverrides) +
+ (this.count - this.hiddenModelIndexes.length - scrollSizeOverrides.length) * this.defaultSize
+ );
+ }
+ private intKeys(value): number[] {
+ return _.keys(value).map(x => _.parseInt(x));
+ }
+ public getPositionByRealIndex(realIndex: number): number {
+ if (realIndex < 0) return 0;
+ if (realIndex < this.frozenCount) return this.frozenItems[realIndex].position;
+ return this.getPositionByScrollIndex(realIndex - this.frozenCount);
+ }
+ public getPositionByScrollIndex(scrollIndex: number): number {
+ let order: number = _.sortedIndex(this.scrollIndexes, scrollIndex);
+ if (this.scrollIndexes[order] == scrollIndex) return this.scrollItems[order].position;
+ order--;
+ if (order < 0) return scrollIndex * this.defaultSize;
+ return (
+ this.scrollItems[order].endPosition + (scrollIndex - this.scrollItems[order].scrollIndex - 1) * this.defaultSize
+ );
+ }
+ public getVisibleScrollCount(firstVisibleIndex: number, viewportSize: number): number {
+ let res: number = 0;
+ let index: number = firstVisibleIndex;
+ let count: number = 0;
+ while (res < viewportSize && index <= this.scrollCount) {
+ res += this.getSizeByScrollIndex(index);
+ index++;
+ count++;
+ }
+ return count;
+ }
+ public getVisibleScrollCountReversed(lastVisibleIndex: number, viewportSize: number): number {
+ let res: number = 0;
+ let index: number = lastVisibleIndex;
+ let count: number = 0;
+ while (res < viewportSize && index >= 0) {
+ res += this.getSizeByScrollIndex(index);
+ index--;
+ count++;
+ }
+ return count;
+ }
+ public invalidateAfterScroll(
+ oldFirstVisible: number,
+ newFirstVisible: number,
+ invalidate: (_: number) => void,
+ viewportSize: number
+ ): void {
+ if (newFirstVisible > oldFirstVisible) {
+ let oldVisibleCount: number = this.getVisibleScrollCount(oldFirstVisible, viewportSize);
+ let newVisibleCount: number = this.getVisibleScrollCount(newFirstVisible, viewportSize);
+ for (let i: number = oldFirstVisible + oldVisibleCount - 1; i <= newFirstVisible + newVisibleCount; i++) {
+ invalidate(i + this.frozenCount);
+ }
+ } else {
+ for (let i: number = newFirstVisible; i <= oldFirstVisible; i++) {
+ invalidate(i + this.frozenCount);
+ }
+ }
+ }
+ public isWholeInView(firstVisibleIndex: number, index: number, viewportSize: number): boolean {
+ let res: number = 0;
+ let testedIndex: number = firstVisibleIndex;
+ while (res < viewportSize && testedIndex < this.count) {
+ res += this.getSizeByScrollIndex(testedIndex);
+ if (testedIndex == index) return res <= viewportSize;
+ testedIndex++;
+ }
+ return false;
+ }
+ public scrollInView(firstVisibleIndex: number, scrollIndex: number, viewportSize: number): number {
+ if (this.isWholeInView(firstVisibleIndex, scrollIndex, viewportSize)) {
+ return firstVisibleIndex;
+ }
+ if (scrollIndex < firstVisibleIndex) {
+ return scrollIndex;
+ }
+ let testedIndex = firstVisibleIndex + 1;
+ while (testedIndex < this.scrollCount) {
+ if (this.isWholeInView(testedIndex, scrollIndex, viewportSize)) {
+ return testedIndex;
+ }
+ testedIndex++;
+ }
+ return this.scrollCount - 1;
+
+ // let res: number = 0;
+ // let testedIndex: number = scrollIndex;
+ // while (res < viewportSize && testedIndex >= 0) {
+ // let size: number = this.getSizeByScrollIndex(testedIndex);
+ // if (res + size > viewportSize) return testedIndex + 1;
+ // testedIndex--;
+ // res += size;
+ // }
+ // if (res >= viewportSize && testedIndex < scrollIndex) return testedIndex + 1;
+ // return firstVisibleIndex;
+ }
+ public resize(realIndex: number, newSize: number): void {
+ if (realIndex < 0) return;
+ let modelIndex: number = this.realToModel(realIndex);
+ if (modelIndex < 0) return;
+ this.sizeOverridesByModelIndex[modelIndex] = newSize;
+ this.buildIndex();
+ }
+ public setExtraordinaryIndexes(hidden: number[], frozen: number[]): void {
+ //this._hiddenAndFrozenModelIndexes = _.clone(hidden);
+ hidden = hidden.filter(x => x >= 0);
+ frozen = frozen.filter(x => x >= 0);
+
+ hidden.sort((a, b) => a - b);
+ frozen.sort((a, b) => a - b);
+ this.frozenModelIndexes = _.filter(frozen, x => !_.includes(hidden, x));
+ this.hiddenModelIndexes = _.filter(hidden, x => !_.includes(frozen, x));
+ this.hiddenAndFrozenModelIndexes = _.concat(hidden, this.frozenModelIndexes);
+ this.frozenModelIndexes.sort((a, b) => a - b);
+ if (this.hiddenAndFrozenModelIndexes.length == 0) this.hiddenAndFrozenModelIndexes = null;
+ if (this.frozenModelIndexes.length == 0) this.frozenModelIndexes = null;
+ this.buildIndex();
+ }
+ public realToModel(realIndex: number): number {
+ if (this.hiddenAndFrozenModelIndexes == null && this.frozenModelIndexes == null) return realIndex;
+ if (realIndex < 0) return -1;
+ if (realIndex < this.frozenCount && this.frozenModelIndexes != null) return this.frozenModelIndexes[realIndex];
+ if (this.hiddenAndFrozenModelIndexes == null) return realIndex;
+ realIndex -= this.frozenCount;
+ for (let hidItem of this.hiddenAndFrozenModelIndexes) {
+ if (realIndex < hidItem) return realIndex;
+ realIndex++;
+ }
+ return realIndex;
+ }
+ public modelToReal(modelIndex: number): number {
+ if (this.hiddenAndFrozenModelIndexes == null && this.frozenModelIndexes == null) return modelIndex;
+ if (modelIndex < 0) return -1;
+ let frozenIndex: number = this.frozenModelIndexes != null ? _.indexOf(this.frozenModelIndexes, modelIndex) : -1;
+ if (frozenIndex >= 0) return frozenIndex;
+ if (this.hiddenAndFrozenModelIndexes == null) return modelIndex;
+ let hiddenIndex: number = _.sortedIndex(this.hiddenAndFrozenModelIndexes, modelIndex);
+ if (this.hiddenAndFrozenModelIndexes[hiddenIndex] == modelIndex) return -1;
+ if (hiddenIndex >= 0) return modelIndex - hiddenIndex + this.frozenCount;
+ return modelIndex;
+ }
+ public getFrozenPosition(frozenIndex: number): number {
+ return this.frozenItems[frozenIndex].position;
+ }
+ public hasSizeOverride(modelIndex: number): boolean {
+ return _.has(this.sizeOverridesByModelIndex, modelIndex);
+ }
+ public isVisible(testedRealIndex: number, firstVisibleScrollIndex: number, viewportSize: number): boolean {
+ if (testedRealIndex < 0) return false;
+ if (testedRealIndex >= 0 && testedRealIndex < this.frozenCount) return true;
+ let scrollIndex: number = testedRealIndex - this.frozenCount;
+ let onPageIndex: number = scrollIndex - firstVisibleScrollIndex;
+ return onPageIndex >= 0 && onPageIndex < this.getVisibleScrollCount(firstVisibleScrollIndex, viewportSize);
+ }
+}
diff --git a/packages/web/src/datagrid/TableDataGrid.svelte b/packages/web/src/datagrid/TableDataGrid.svelte
index 2f5d18194..0089a6496 100644
--- a/packages/web/src/datagrid/TableDataGrid.svelte
+++ b/packages/web/src/datagrid/TableDataGrid.svelte
@@ -1,8 +1,10 @@
-
-
- XXX
+
diff --git a/packages/web/src/datagrid/gridutil.ts b/packages/web/src/datagrid/gridutil.ts
new file mode 100644
index 000000000..bf64c2a94
--- /dev/null
+++ b/packages/web/src/datagrid/gridutil.ts
@@ -0,0 +1,144 @@
+import _ from 'lodash';
+import { SeriesSizes } from './SeriesSizes';
+import { CellAddress } from './selection';
+import { GridDisplay } from 'dbgate-datalib';
+import Grider from './Grider';
+
+export function countColumnSizes(grider: Grider, columns, containerWidth, display: GridDisplay) {
+ const columnSizes = new SeriesSizes();
+ if (!grider || !columns) return columnSizes;
+
+ let canvas = document.createElement('canvas');
+ let context = canvas.getContext('2d');
+
+ //return this.context.measureText(txt).width;
+
+ // console.log('countColumnSizes', loadedRows.length, containerWidth);
+
+ columnSizes.maxSize = (containerWidth * 2) / 3;
+ columnSizes.count = columns.length;
+
+ // columnSizes.setExtraordinaryIndexes(this.getHiddenColumnIndexes(), this.getFrozenColumnIndexes());
+ // console.log('display.hiddenColumnIndexes', display.hiddenColumnIndexes)
+
+ columnSizes.setExtraordinaryIndexes(display.hiddenColumnIndexes, []);
+
+ for (let colIndex = 0; colIndex < columns.length; colIndex++) {
+ //this.columnSizes.PutSizeOverride(col, this.columns[col].Name.length * 8);
+ const column = columns[colIndex];
+
+ if (display.config.columnWidths[column.uniqueName]) {
+ columnSizes.putSizeOverride(colIndex, display.config.columnWidths[column.uniqueName]);
+ continue;
+ }
+
+ // if (column.columnClientObject != null && column.columnClientObject.notNull) context.font = "bold 14px Helvetica";
+ // else context.font = "14px Helvetica";
+ context.font = 'bold 14px Helvetica';
+
+ const text = column.headerText;
+ const headerWidth = context.measureText(text).width + 64;
+
+ // if (column.columnClientObject != null && column.columnClientObject.icon != null) headerWidth += 16;
+ // if (this.getFilterOnColumn(column.uniquePath)) headerWidth += 16;
+ // if (this.getSortOrder(column.uniquePath)) headerWidth += 16;
+
+ columnSizes.putSizeOverride(colIndex, headerWidth);
+ }
+
+ // let headerWidth = this.rowHeaderWidthDefault;
+ // if (this.rowCount) headerWidth = context.measureText(this.rowCount.toString()).width + 8;
+ // this.rowHeaderWidth = this.rowHeaderWidthDefault;
+ // if (headerWidth > this.rowHeaderWidth) this.rowHeaderWidth = headerWidth;
+
+ context.font = '14px Helvetica';
+ for (let rowIndex = 0; rowIndex < Math.min(grider.rowCount, 20); rowIndex += 1) {
+ const row = grider.getRowData(rowIndex);
+ for (let colIndex = 0; colIndex < columns.length; colIndex++) {
+ const uqName = columns[colIndex].uniqueName;
+
+ if (display.config.columnWidths[uqName]) {
+ continue;
+ }
+
+ const text = row[uqName];
+ const width = context.measureText(text).width + 8;
+ // console.log('colName', colName, text, width);
+ columnSizes.putSizeOverride(colIndex, width);
+ // let colName = this.columns[colIndex].uniquePath;
+ // let text: string = row[colName].gridText;
+ // let width = context.measureText(text).width + 8;
+ // if (row[colName].dataPrefix) width += context.measureText(row[colName].dataPrefix).width + 3;
+ // this.columnSizes.putSizeOverride(colIndex, width);
+ }
+ }
+
+ // for (let modelIndex = 0; modelIndex < this.columns.length; modelIndex++) {
+ // let width = getHashValue(this.widthHashPrefix + this.columns[modelIndex].uniquePath);
+ // if (width) this.columnSizes.putSizeOverride(modelIndex, _.toNumber(width), true);
+ // }
+
+ columnSizes.buildIndex();
+ return columnSizes;
+}
+
+export function countVisibleRealColumns(columnSizes, firstVisibleColumnScrollIndex, gridScrollAreaWidth, columns) {
+ const visibleColumnCount = columnSizes.getVisibleScrollCount(firstVisibleColumnScrollIndex, gridScrollAreaWidth);
+ // console.log('visibleColumnCount', visibleColumnCount);
+ // console.log('gridScrollAreaWidth', gridScrollAreaWidth);
+
+ const visibleRealColumnIndexes = [];
+ const modelIndexes = {};
+ /** @type {(import('dbgate-datalib').DisplayColumn & {widthPx: string; colIndex: number})[]} */
+ const realColumns = [];
+
+ // frozen columns
+ for (let colIndex = 0; colIndex < columnSizes.frozenCount; colIndex++) {
+ visibleRealColumnIndexes.push(colIndex);
+ }
+ // scroll columns
+ for (
+ let colIndex = firstVisibleColumnScrollIndex;
+ colIndex < firstVisibleColumnScrollIndex + visibleColumnCount;
+ colIndex++
+ ) {
+ visibleRealColumnIndexes.push(colIndex + columnSizes.frozenCount);
+ }
+
+ // real columns
+ for (let colIndex of visibleRealColumnIndexes) {
+ let modelColumnIndex = columnSizes.realToModel(colIndex);
+ modelIndexes[colIndex] = modelColumnIndex;
+
+ let col = columns[modelColumnIndex];
+ if (!col) continue;
+ const widthNumber = columnSizes.getSizeByRealIndex(colIndex);
+ realColumns.push({
+ ...col,
+ colIndex,
+ widthNumber,
+ widthPx: `${widthNumber}px`,
+ });
+ }
+ return realColumns;
+}
+
+export function filterCellForRow(cell, row: number): CellAddress | null {
+ return cell && (cell[0] == row || _.isString(cell[0])) ? cell : null;
+}
+
+export function filterCellsForRow(cells, row: number): CellAddress[] | null {
+ const res = (cells || []).filter(x => x[0] == row || _.isString(x[0]));
+ return res.length > 0 ? res : null;
+}
+
+export function cellIsSelected(row, col, selectedCells) {
+ if (!selectedCells) return false;
+ for (const [selectedRow, selectedCol] of selectedCells) {
+ if (row == selectedRow && col == selectedCol) return true;
+ if (selectedRow == 'header' && col == selectedCol) return true;
+ if (row == selectedRow && selectedCol == 'header') return true;
+ if (selectedRow == 'header' && selectedCol == 'header') return true;
+ }
+ return false;
+}
diff --git a/packages/web/src/datagrid/selection.ts b/packages/web/src/datagrid/selection.ts
new file mode 100644
index 000000000..28ff9384a
--- /dev/null
+++ b/packages/web/src/datagrid/selection.ts
@@ -0,0 +1,69 @@
+import _ from 'lodash';
+export type CellAddress = [number | 'header' | 'filter' | undefined, number | 'header' | undefined];
+export type RegularCellAddress = [number, number];
+
+export const topLeftCell: CellAddress = [0, 0];
+export const undefinedCell: CellAddress = [undefined, undefined];
+export const nullCell: CellAddress = null;
+export const emptyCellArray: CellAddress[] = [];
+
+export function isRegularCell(cell: CellAddress): cell is RegularCellAddress {
+ if (!cell) return false;
+ const [row, col] = cell;
+ return _.isNumber(row) && _.isNumber(col);
+}
+
+export function getCellRange(a: CellAddress, b: CellAddress): CellAddress[] {
+ const [rowA, colA] = a;
+ const [rowB, colB] = b;
+
+ if (_.isNumber(rowA) && _.isNumber(colA) && _.isNumber(rowB) && _.isNumber(colB)) {
+ const rowMin = Math.min(rowA, rowB);
+ const rowMax = Math.max(rowA, rowB);
+ const colMin = Math.min(colA, colB);
+ const colMax = Math.max(colA, colB);
+ const res = [];
+ for (let row = rowMin; row <= rowMax; row++) {
+ for (let col = colMin; col <= colMax; col++) {
+ res.push([row, col]);
+ }
+ }
+ return res;
+ }
+ if (rowA == 'header' && rowB == 'header' && _.isNumber(colA) && _.isNumber(colB)) {
+ const colMin = Math.min(colA, colB);
+ const colMax = Math.max(colA, colB);
+ const res = [];
+ for (let col = colMin; col <= colMax; col++) {
+ res.push(['header', col]);
+ }
+ return res;
+ }
+ if (colA == 'header' && colB == 'header' && _.isNumber(rowA) && _.isNumber(rowB)) {
+ const rowMin = Math.min(rowA, rowB);
+ const rowMax = Math.max(rowA, rowB);
+ const res = [];
+ for (let row = rowMin; row <= rowMax; row++) {
+ res.push([row, 'header']);
+ }
+ return res;
+ }
+ if (colA == 'header' && colB == 'header' && rowA == 'header' && rowB == 'header') {
+ return [['header', 'header']];
+ }
+ return [];
+}
+
+export function convertCellAddress(row, col): CellAddress {
+ const rowNumber = parseInt(row);
+ const colNumber = parseInt(col);
+ return [_.isNaN(rowNumber) ? row : rowNumber, _.isNaN(colNumber) ? col : colNumber];
+}
+
+export function cellFromEvent(event): CellAddress {
+ const cell = event.target.closest('td');
+ if (!cell) return undefinedCell;
+ const col = cell.getAttribute('data-col');
+ const row = cell.getAttribute('data-row');
+ return convertCellAddress(row, col);
+}
diff --git a/packages/web/src/utility/useGridConfig.ts b/packages/web/src/utility/useGridConfig.ts
index ee1e25e7d..af453a42c 100644
--- a/packages/web/src/utility/useGridConfig.ts
+++ b/packages/web/src/utility/useGridConfig.ts
@@ -2,20 +2,24 @@ import { createGridConfig } from 'dbgate-datalib';
import { writable } from 'svelte/store';
import { onDestroy } from 'svelte';
-const loadGridConfigFunc = tabid => () => {
- const existing = localStorage.getItem(`tabdata_grid_${tabid}`);
- if (existing) {
- return {
- ...createGridConfig(),
- ...JSON.parse(existing),
- };
+function doLoadGridConfigFunc(tabid) {
+ try {
+ const existing = localStorage.getItem(`tabdata_grid_${tabid}`);
+ if (existing) {
+ return {
+ ...createGridConfig(),
+ ...JSON.parse(existing),
+ };
+ }
+ } catch (err) {
+ console.warn('Error loading grid config:', err.message);
}
return createGridConfig();
-};
+}
export default function useGridConfig(tabid) {
- const config = writable(loadGridConfigFunc(tabid));
+ const config = writable(doLoadGridConfigFunc(tabid));
const unsubscribe = config.subscribe(value => localStorage.setItem(`tabdata_grid_${tabid}`, JSON.stringify(value)));
- onDestroy(unsubscribe)
+ onDestroy(unsubscribe);
return config;
}