column scroll

This commit is contained in:
Jan Prochazka
2020-03-21 22:32:58 +01:00
parent d16d946df1
commit 043d21c43a
6 changed files with 419 additions and 19 deletions

View File

@@ -48,10 +48,11 @@ export default function TabContent() {
return _.keys(mountedTabs).map(tabid => {
const { TabComponent, props } = mountedTabs[tabid];
const tabVisible = tabid == (selectedTab && selectedTab.tabid);
return (
// @ts-ignore
<TabContainer key={tabid} tabVisible={tabid == (selectedTab && selectedTab.tabid)}>
<TabComponent {...props} />
<TabContainer key={tabid} tabVisible={tabVisible}>
<TabComponent {...props} tabVisible={tabVisible} />
</TabContainer>
);
});

View File

@@ -110,7 +110,7 @@ function CellFormattedValue({ value }) {
/** @param props {import('./types').DataGridProps} */
export default function DataGridCore(props) {
const { conid, database, display, isMainGrid } = props;
const { conid, database, display, tabVisible } = props;
const columns = display.getGridColumns();
// console.log(`GRID, conid=${conid}, database=${database}, sql=${sql}`);
@@ -124,6 +124,12 @@ export default function DataGridCore(props) {
const loadedTimeRef = React.useRef(0);
const [vScrollValueToSet, setvScrollValueToSet] = React.useState();
const [vScrollValueToSetDate, setvScrollValueToSetDate] = React.useState(new Date());
const [hScrollValueToSet, sethScrollValueToSet] = React.useState();
const [hScrollValueToSetDate, sethScrollValueToSetDate] = React.useState(new Date());
const [currentCell, setCurrentCell] = React.useState(topLeftCell);
const [selectedCells, setSelectedCells] = React.useState(emptyCellArray);
const [dragStartCell, setDragStartCell] = React.useState(nullCell);
@@ -186,19 +192,23 @@ export default function DataGridCore(props) {
const [headerRowRef, { height: rowHeight }] = useDimensions();
const [tableBodyRef] = useDimensions();
const [containerRef, { height: containerHeight, width: containerWidth }] = useDimensions();
const tableRef = React.useRef();
const [tableRef, { height: tableHeight, width: tableWidth }, tableElement] = useDimensions();
const columnSizes = React.useMemo(() => countColumnSizes(), [loadedRows, containerWidth, display]);
const headerColWidth = 40;
// console.log('containerWidth', containerWidth);
const gridScrollAreaHeight = containerHeight - 2 * rowHeight;
const gridScrollAreaWidth = containerWidth - columnSizes.frozenSize;
const gridScrollAreaWidth = containerWidth - columnSizes.frozenSize - headerColWidth;
const visibleRowCountUpperBound = Math.ceil(gridScrollAreaHeight / Math.floor(rowHeight));
const visibleRowCountLowerBound = Math.floor(gridScrollAreaHeight / Math.ceil(rowHeight));
// const visibleRowCountUpperBound = 20;
// const visibleRowCountLowerBound = 20;
// console.log('containerHeight', containerHeight);
// console.log('visibleRowCountUpperBound', visibleRowCountUpperBound);
// console.log('rowHeight', rowHeight);
const reload = () => {
setLoadProps({
@@ -221,11 +231,11 @@ export default function DataGridCore(props) {
});
React.useEffect(() => {
if (isMainGrid) {
if (tabVisible) {
// @ts-ignore
if (tableRef.current) tableRef.current.focus();
if (tableElement) tableElement.focus();
}
}, []);
}, [tabVisible, tableElement]);
if (!loadedRows || !columns) return null;
const rowCountNewIncluded = loadedRows.length;
@@ -280,7 +290,7 @@ export default function DataGridCore(props) {
// if (headerWidth > this.rowHeaderWidth) this.rowHeaderWidth = headerWidth;
context.font = '14px Helvetica';
for (let row of loadedRows) {
for (let row of loadedRows.slice(0, 20)) {
for (let colIndex = 0; colIndex < columns.length; colIndex++) {
let uqName = columns[colIndex].uniqueName;
let text = row[uqName];
@@ -397,9 +407,8 @@ export default function DataGridCore(props) {
if (col < 0) col = 0;
if (col >= columnSizes.realCount) col = columnSizes.realCount - 1;
setCurrentCell([row, col]);
if (!event.shiftKey) {
setSelectedCells([]);
}
setSelectedCells([...(event.ctrlKey ? selectedCells : []), [row, col]]);
scrollIntoView([row, col]);
// this.selectedCells.push(this.currentCell);
// this.scrollIntoView(this.currentCell);
@@ -407,6 +416,50 @@ export default function DataGridCore(props) {
return true;
}
function scrollIntoView(cell) {
const [row, col] = cell;
if (row != null) {
let newRow = null;
const rowCount = rowCountNewIncluded;
if (row < firstVisibleRowScrollIndex) newRow = row;
else if (row + 1 >= firstVisibleRowScrollIndex + visibleRowCountLowerBound)
newRow = row - visibleRowCountLowerBound + 2;
if (newRow < 0) newRow = 0;
if (newRow >= rowCount) newRow = rowCount - 1;
if (newRow != null) {
setFirstVisibleRowScrollIndex(newRow);
// firstVisibleRowScrollIndex = newRow;
setvScrollValueToSet(newRow);
setvScrollValueToSetDate(new Date());
// vscroll.value = newRow;
}
//int newRow = _rowSizes.ScrollInView(FirstVisibleRowScrollIndex, cell.Row.Value - _rowSizes.FrozenCount, GridScrollAreaHeight);
//ScrollContent(newRow, FirstVisibleColumnScrollIndex);
}
if (col != null) {
if (col >= columnSizes.frozenCount) {
let newColumn = columnSizes.scrollInView(
firstVisibleColumnScrollIndex,
col - columnSizes.frozenCount,
gridScrollAreaWidth
);
setFirstVisibleColumnScrollIndex(newColumn);
// @ts-ignore
sethScrollValueToSet(newColumn);
sethScrollValueToSetDate(new Date());
// firstVisibleColumnScrollIndex = newColumn;
// hscroll.value = newColumn;
}
}
}
function cellIsSelected(row, col) {
const [currentRow, currentCol] = currentCell;
if (row == currentRow && col == currentCol) return true;
@@ -424,7 +477,8 @@ export default function DataGridCore(props) {
// console.log('containerHeight', containerHeight);
const visibleColumnCount = columnSizes.getVisibleScrollCount(firstVisibleColumnScrollIndex, gridScrollAreaWidth);
// console.log('visibleColumnCount', visibleColumnCount);
console.log('visibleColumnCount', visibleColumnCount);
console.log('gridScrollAreaWidth', gridScrollAreaWidth);
const visibleRealColumnIndexes = [];
const modelIndexes = {};
@@ -459,7 +513,7 @@ export default function DataGridCore(props) {
});
}
const hederColwidthPx = '40px';
const hederColwidthPx = `${headerColWidth}px`;
const filterCount = display.filterCount;
const handleClearFilters = () => {
@@ -467,6 +521,12 @@ export default function DataGridCore(props) {
};
// console.log('visibleRealColumnIndexes', visibleRealColumnIndexes);
console.log(
'gridScrollAreaWidth / columnSizes.getVisibleScrollSizeSum()',
gridScrollAreaWidth,
columnSizes.getVisibleScrollSizeSum()
);
return (
<GridContainer ref={containerRef}>
<Table
@@ -524,14 +584,18 @@ export default function DataGridCore(props) {
{loadedRows
.slice(firstVisibleRowScrollIndex, firstVisibleRowScrollIndex + visibleRowCountUpperBound)
.map((row, index) => (
<TableBodyRow key={firstVisibleRowScrollIndex + index}>
<TableBodyRow key={firstVisibleRowScrollIndex + index} style={{ height: `${rowHeight}px` }}>
<TableHeaderCell data-row={firstVisibleRowScrollIndex + index} data-col="header">
{firstVisibleRowScrollIndex + index + 1}
</TableHeaderCell>
{realColumns.map(col => (
<TableBodyCell
key={col.uniqueName}
style={{ width: col.widthPx, minWidth: col.widthPx, maxWidth: col.widthPx }}
style={{
width: col.widthPx,
minWidth: col.widthPx,
maxWidth: col.widthPx,
}}
data-row={firstVisibleRowScrollIndex + index}
data-col={col.colIndex}
// @ts-ignore
@@ -546,12 +610,16 @@ export default function DataGridCore(props) {
</TableBody>
</Table>
<HorizontalScrollBar
valueToSet={hScrollValueToSet}
valueToSetDate={hScrollValueToSetDate}
minimum={0}
maximum={columns.length - 1}
viewportRatio={gridScrollAreaWidth / columnSizes.getVisibleScrollSizeSum()}
onScroll={handleColumnScroll}
/>
<VerticalScrollBar
valueToSet={vScrollValueToSet}
valueToSetDate={vScrollValueToSetDate}
minimum={0}
maximum={rowCountNewIncluded - visibleRowCountUpperBound + 2}
onScroll={handleRowScroll}

View File

@@ -0,0 +1,331 @@
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(this.intKeys(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 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);
}
}

View File

@@ -4,5 +4,5 @@ export interface DataGridProps {
conid: number;
database: string;
display: GridDisplay;
isMainGrid?: boolean;
tabVisible?: boolean;
}

View File

@@ -9,7 +9,7 @@ import useConnectionInfo from '../utility/useConnectionInfo';
import engines from '@dbgate/engines';
import getTableInfo from '../utility/getTableInfo';
export default function TableDataTab({ conid, database, schemaName, pureName }) {
export default function TableDataTab({ conid, database, schemaName, pureName, tabVisible }) {
const tableInfo = useTableInfo({ conid, database, schemaName, pureName });
const [config, setConfig] = React.useState(createGridConfig());
const [cache, setCache] = React.useState(createGridCache());
@@ -24,7 +24,7 @@ export default function TableDataTab({ conid, database, schemaName, pureName })
conid={conid}
database={database}
display={display}
isMainGrid
tabVisible={tabVisible}
/>
);
}