mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-21 11:45:59 +00:00
datagrid
This commit is contained in:
40
packages/web/src/datagrid/ColumnHeaderControl.svelte
Normal file
40
packages/web/src/datagrid/ColumnHeaderControl.svelte
Normal file
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import ColumnLabel from './ColumnLabel.svelte';
|
||||
|
||||
export let conid = undefined;
|
||||
export let database = undefined;
|
||||
export let column;
|
||||
</script>
|
||||
|
||||
<div class="header">
|
||||
<div class="label">
|
||||
<ColumnLabel {...column} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.header {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.label {
|
||||
flex: 1;
|
||||
min-width: 10px;
|
||||
padding: 2px;
|
||||
margin: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.icon {
|
||||
margin-left: 3px;
|
||||
}
|
||||
.resizer {
|
||||
background-color: var(--theme-border);
|
||||
width: 2px;
|
||||
cursor: col-resize;
|
||||
z-index: 1;
|
||||
}
|
||||
.grouping {
|
||||
color: green;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
46
packages/web/src/datagrid/ColumnLabel.svelte
Normal file
46
packages/web/src/datagrid/ColumnLabel.svelte
Normal file
@@ -0,0 +1,46 @@
|
||||
<script context="module" lang="ts">
|
||||
export function getColumnIcon(column, forceIcon = false) {
|
||||
if (column.autoIncrement) return 'img autoincrement';
|
||||
if (column.foreignKey) return 'img foreign-key';
|
||||
if (forceIcon) return 'img column';
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
|
||||
export let notNull = false;
|
||||
export let forceIcon = false;
|
||||
export let headerText = '';
|
||||
export let columnName = '';
|
||||
export let extInfo = null;
|
||||
|
||||
$: icon = getColumnIcon($$props, forceIcon);
|
||||
</script>
|
||||
|
||||
<span class="label" class:notNull>
|
||||
{#if icon}
|
||||
<FontIcon {icon} />
|
||||
{/if}
|
||||
{headerText || columnName}
|
||||
{#if extInfo}
|
||||
<span class="extinfo">{extInfo}</span>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<style>
|
||||
.label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.label.notNull {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.extinfo {
|
||||
font-weight: normal;
|
||||
margin-left: 5px;
|
||||
color: var(--theme-font-3);
|
||||
}
|
||||
</style>
|
||||
@@ -1,15 +1,6 @@
|
||||
<script lang="ts">
|
||||
export let config;
|
||||
export let gridCoreComponent;
|
||||
export let loadNextData = undefined;
|
||||
export let grider = undefined;
|
||||
|
||||
$: firstVisibleRowScrollIndex = 0;
|
||||
$: visibleRowCountUpperBound = 25;
|
||||
|
||||
if (loadNextData && firstVisibleRowScrollIndex + visibleRowCountUpperBound >= grider.rowCount) {
|
||||
loadNextData();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:component this={gridCoreComponent} {...$$props} />
|
||||
|
||||
@@ -1,9 +1,64 @@
|
||||
<div class="container">
|
||||
<script lang="ts">
|
||||
import { GridDisplay } from 'dbgate-datalib';
|
||||
import _ from 'lodash';
|
||||
import ColumnHeaderControl from './ColumnHeaderControl.svelte';
|
||||
import { countColumnSizes, countVisibleRealColumns } from './gridutil';
|
||||
|
||||
export let loadNextData = undefined;
|
||||
export let grider = undefined;
|
||||
export let display: GridDisplay = undefined;
|
||||
export let conid = undefined;
|
||||
export let database = undefined;
|
||||
|
||||
let containerHeight = 0;
|
||||
let containerWidth = 0;
|
||||
let rowHeight = 0;
|
||||
let firstVisibleRowScrollIndex = 0;
|
||||
let firstVisibleColumnScrollIndex = 0;
|
||||
// $: firstVisibleRowScrollIndex = 0;
|
||||
$: visibleRowCountUpperBound = 25;
|
||||
|
||||
// $: console.log('grider', grider);
|
||||
$: columns = display.allColumns;
|
||||
|
||||
$: columnSizes = countColumnSizes(grider, columns, containerWidth, display);
|
||||
|
||||
$: headerColWidth = 40;
|
||||
|
||||
$: gridScrollAreaHeight = containerHeight - 2 * rowHeight;
|
||||
$: gridScrollAreaWidth = containerWidth - columnSizes.frozenSize - headerColWidth - 32;
|
||||
|
||||
$: visibleRealColumns = countVisibleRealColumns(
|
||||
columnSizes,
|
||||
firstVisibleColumnScrollIndex,
|
||||
gridScrollAreaWidth,
|
||||
columns
|
||||
);
|
||||
|
||||
$: console.log('visibleRealColumns', visibleRealColumns);
|
||||
|
||||
$: realColumnUniqueNames = _.range(columnSizes.realCount).map(
|
||||
realIndex => (columns[columnSizes.realToModel(realIndex)] || {}).uniqueName
|
||||
);
|
||||
|
||||
$: {
|
||||
if (loadNextData && firstVisibleRowScrollIndex + visibleRowCountUpperBound >= grider.rowCount) {
|
||||
loadNextData();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container" bind:clientWidth={containerWidth} bind:clientHeight={containerHeight}>
|
||||
<input type="text" class="focus-field" />
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="header=cell" data-row="header" data-col="header" />
|
||||
<td class="header-cell" data-row="header" data-col="header" />
|
||||
{#each visibleRealColumns as col (col.uniqueName)}
|
||||
<td class="header-cell" data-row="header" data-col={col.colIndex}>
|
||||
<ColumnHeaderControl column={col} {conid} {database} />
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody />
|
||||
|
||||
@@ -65,4 +65,4 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<DataGridCore />
|
||||
<DataGridCore {...$$props} loadNextData={handleLoadNextData} {grider} />
|
||||
|
||||
341
packages/web/src/datagrid/SeriesSizes.ts
Normal file
341
packages/web/src/datagrid/SeriesSizes.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { TableFormViewDisplay } from 'dbgate-datalib';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import { createGridCache, TableFormViewDisplay, TableGridDisplay } from 'dbgate-datalib';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import { writable } from 'svelte/store';
|
||||
import { extensions } from '../stores';
|
||||
|
||||
import { useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||
import { useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||
|
||||
import DataGrid from './DataGrid.svelte';
|
||||
import SqlDataGridCore from './SqlDataGridCore.svelte';
|
||||
@@ -16,20 +18,30 @@ import { useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||
$: connection = useConnectionInfo({ conid });
|
||||
$: dbinfo = useDatabaseInfo({ conid, database });
|
||||
|
||||
// $: display = connection
|
||||
// ? new TableFormViewDisplay(
|
||||
// { schemaName, pureName },
|
||||
// findEngineDriver(connection, extensions),
|
||||
// config,
|
||||
// setConfig,
|
||||
// cache || myCache,
|
||||
// setCache || setMyCache,
|
||||
// dbinfo
|
||||
// )
|
||||
// : null;;
|
||||
const cache = writable(createGridCache());
|
||||
|
||||
// $: console.log('display', display);
|
||||
|
||||
$: display = connection
|
||||
? new TableGridDisplay(
|
||||
{ schemaName, pureName },
|
||||
findEngineDriver($connection, $extensions),
|
||||
$config,
|
||||
config.update,
|
||||
$cache,
|
||||
cache.update,
|
||||
$dbinfo
|
||||
)
|
||||
: // ? new TableFormViewDisplay(
|
||||
// { schemaName, pureName },
|
||||
// findEngineDriver(connection, $extensions),
|
||||
// $config,
|
||||
// config.update,
|
||||
// $cache,
|
||||
// cache.update,
|
||||
// $dbinfo
|
||||
// )
|
||||
null;
|
||||
</script>
|
||||
|
||||
<!-- <DataGrid {...$$props} gridCoreComponent={SqlDataGridCore} /> -->
|
||||
|
||||
XXX
|
||||
<DataGrid {...$$props} gridCoreComponent={SqlDataGridCore} {display} />
|
||||
|
||||
144
packages/web/src/datagrid/gridutil.ts
Normal file
144
packages/web/src/datagrid/gridutil.ts
Normal file
@@ -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;
|
||||
}
|
||||
69
packages/web/src/datagrid/selection.ts
Normal file
69
packages/web/src/datagrid/selection.ts
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user