mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 12:13:57 +00:00
virtual datagrid
This commit is contained in:
parent
421b30c2f5
commit
6f12b4fd14
@ -1,5 +1,5 @@
|
|||||||
[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
|
[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
|
||||||
[![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://paypal.me/JanProchazkaCz)
|
[![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://paypal.me/JanProchazkaCz/30eur)
|
||||||
|
|
||||||
# DbGate - database administration tool
|
# DbGate - database administration tool
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-modal": "^3.11.1",
|
"react-modal": "^3.11.1",
|
||||||
"react-scripts": "3.3.0",
|
"react-scripts": "3.3.0",
|
||||||
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"socket.io-client": "^2.3.0",
|
"socket.io-client": "^2.3.0",
|
||||||
"styled-components": "^4.4.1",
|
"styled-components": "^4.4.1",
|
||||||
"uuid": "^3.4.0"
|
"uuid": "^3.4.0"
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import useFetch from '../utility/useFetch';
|
import useFetch from '../utility/useFetch';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import theme from '../theme';
|
import theme from '../theme';
|
||||||
import { HorizontalScrollBar, VerticalScrollBar } from './ScrollBars';
|
import { HorizontalScrollBar, VerticalScrollBar } from './ScrollBars';
|
||||||
import useDimensions from '../utility/useDimensions';
|
import useDimensions from '../utility/useDimensions';
|
||||||
|
import { SeriesSizes } from './SeriesSizes';
|
||||||
|
|
||||||
const GridContainer = styled.div``;
|
const GridContainer = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
const Table = styled.table`
|
const Table = styled.table`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -62,15 +69,24 @@ export default function DataGrid({ params }) {
|
|||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
const { rows, columns } = data || {};
|
const { rows, columns } = data || {};
|
||||||
const [firstVisibleRowScrollIndex, setFirstVisibleRowScrollIndex] = useState(0);
|
const [firstVisibleRowScrollIndex, setFirstVisibleRowScrollIndex] = React.useState(0);
|
||||||
|
const [firstVisibleColumnScrollIndex, setFirstVisibleColumnScrollIndex] = React.useState(0);
|
||||||
|
|
||||||
const [headerRowRef, { height: rowHeight }] = useDimensions();
|
const [headerRowRef, { height: rowHeight }] = useDimensions();
|
||||||
const [tableBodyRef, { height: gridScrollAreaHeight }] = useDimensions();
|
const [tableBodyRef] = useDimensions();
|
||||||
|
const [containerRef, { height: containerHeight, width: containerWidth }] = useDimensions();
|
||||||
|
|
||||||
// const visibleRowCountUpperBound = Math.ceil(gridScrollAreaHeight / Math.floor(rowHeight));
|
const columnSizes = React.useMemo(() => countColumnSizes(), [data, containerWidth]);
|
||||||
// const visibleRowCountLowerBound = Math.floor(gridScrollAreaHeight / Math.ceil(rowHeight));
|
|
||||||
const visibleRowCountUpperBound = 20;
|
console.log('containerWidth', containerWidth);
|
||||||
const visibleRowCountLowerBound = 20;
|
|
||||||
|
const gridScrollAreaHeight = containerHeight - 2 * rowHeight;
|
||||||
|
const gridScrollAreaWidth = containerWidth - columnSizes.frozenSize;
|
||||||
|
|
||||||
|
const visibleRowCountUpperBound = Math.ceil(gridScrollAreaHeight / Math.floor(rowHeight));
|
||||||
|
const visibleRowCountLowerBound = Math.floor(gridScrollAreaHeight / Math.ceil(rowHeight));
|
||||||
|
// const visibleRowCountUpperBound = 20;
|
||||||
|
// const visibleRowCountLowerBound = 20;
|
||||||
|
|
||||||
if (!columns || !rows) return null;
|
if (!columns || !rows) return null;
|
||||||
const rowCountNewIncluded = rows.length;
|
const rowCountNewIncluded = rows.length;
|
||||||
@ -79,16 +95,119 @@ export default function DataGrid({ params }) {
|
|||||||
setFirstVisibleRowScrollIndex(value);
|
setFirstVisibleRowScrollIndex(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('visibleRowCountUpperBound', visibleRowCountUpperBound);
|
function countColumnSizes() {
|
||||||
console.log('gridScrollAreaHeight', gridScrollAreaHeight);
|
let canvas = document.createElement('canvas');
|
||||||
|
let context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
//return this.context.measureText(txt).width;
|
||||||
|
const columnSizes = new SeriesSizes();
|
||||||
|
if (!rows || !columns) return columnSizes;
|
||||||
|
|
||||||
|
console.log('countColumnSizes', rows.length, containerWidth);
|
||||||
|
|
||||||
|
columnSizes.maxSize = (containerWidth * 2) / 3;
|
||||||
|
columnSizes.count = columns.length;
|
||||||
|
|
||||||
|
// columnSizes.setExtraordinaryIndexes(this.getHiddenColumnIndexes(), this.getFrozenColumnIndexes());
|
||||||
|
columnSizes.setExtraordinaryIndexes([], []);
|
||||||
|
|
||||||
|
// for (let colIndex = 0; colIndex < columns.length; colIndex++) {
|
||||||
|
// //this.columnSizes.PutSizeOverride(col, this.columns[col].Name.length * 8);
|
||||||
|
// let column = columns[colIndex];
|
||||||
|
|
||||||
|
// if (column.columnClientObject != null && column.columnClientObject.notNull) context.font = "bold 14px Helvetica";
|
||||||
|
// else context.font = "14px Helvetica";
|
||||||
|
|
||||||
|
// let text = column.headerText;
|
||||||
|
// let headerWidth = context.measureText(text).width + 32;
|
||||||
|
|
||||||
|
// if (column.columnClientObject != null && column.columnClientObject.icon != null) headerWidth += 16;
|
||||||
|
// if (this.getFilterOnColumn(column.uniquePath)) headerWidth += 16;
|
||||||
|
// if (this.getSortOrder(column.uniquePath)) headerWidth += 16;
|
||||||
|
|
||||||
|
// this.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 row of data.rows) {
|
||||||
|
for (let colIndex = 0; colIndex < columns.length; colIndex++) {
|
||||||
|
let colName = columns[colIndex].name;
|
||||||
|
let text = row[colName];
|
||||||
|
let 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('visibleRowCountUpperBound', visibleRowCountUpperBound);
|
||||||
|
// console.log('gridScrollAreaHeight', gridScrollAreaHeight);
|
||||||
|
// console.log('containerHeight', containerHeight);
|
||||||
|
|
||||||
|
const visibleColumnCount = columnSizes.getVisibleScrollCount(firstVisibleColumnScrollIndex, gridScrollAreaWidth);
|
||||||
|
console.log('visibleColumnCount', visibleColumnCount);
|
||||||
|
|
||||||
|
const visibleRealColumnIndexes = [];
|
||||||
|
const modelIndexes = {};
|
||||||
|
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,
|
||||||
|
widthPx: `${widthNumber}px`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('visibleRealColumnIndexes', visibleRealColumnIndexes);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridContainer>
|
<GridContainer ref={containerRef}>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableHeaderRow ref={headerRowRef}>
|
<TableHeaderRow ref={headerRowRef}>
|
||||||
{columns.map(col => (
|
{realColumns.map(col => (
|
||||||
<TableHeaderCell key={col.name} style={{ width: '60px' }}>
|
<TableHeaderCell
|
||||||
|
key={col.name}
|
||||||
|
style={{ width: col.widthPx, minWidth: col.widthPx, maxWidth: col.widthPx }}
|
||||||
|
>
|
||||||
{col.name}
|
{col.name}
|
||||||
</TableHeaderCell>
|
</TableHeaderCell>
|
||||||
))}
|
))}
|
||||||
@ -99,8 +218,11 @@ export default function DataGrid({ params }) {
|
|||||||
.slice(firstVisibleRowScrollIndex, firstVisibleRowScrollIndex + visibleRowCountUpperBound)
|
.slice(firstVisibleRowScrollIndex, firstVisibleRowScrollIndex + visibleRowCountUpperBound)
|
||||||
.map((row, index) => (
|
.map((row, index) => (
|
||||||
<TableBodyRow key={firstVisibleRowScrollIndex + index}>
|
<TableBodyRow key={firstVisibleRowScrollIndex + index}>
|
||||||
{columns.map(col => (
|
{realColumns.map(col => (
|
||||||
<TableBodyCell key={col.name} style={{ width: '60px' }}>
|
<TableBodyCell
|
||||||
|
key={col.name}
|
||||||
|
style={{ width: col.widthPx, minWidth: col.widthPx, maxWidth: col.widthPx }}
|
||||||
|
>
|
||||||
{row[col.name]}
|
{row[col.name]}
|
||||||
</TableBodyCell>
|
</TableBodyCell>
|
||||||
))}
|
))}
|
||||||
@ -108,7 +230,11 @@ export default function DataGrid({ params }) {
|
|||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
<HorizontalScrollBar minimum={0} maximum={columns.length - 1} />
|
<HorizontalScrollBar
|
||||||
|
minimum={0}
|
||||||
|
maximum={columns.length - 1}
|
||||||
|
viewportRatio={gridScrollAreaWidth / columnSizes.getVisibleScrollSizeSum()}
|
||||||
|
/>
|
||||||
<VerticalScrollBar
|
<VerticalScrollBar
|
||||||
minimum={0}
|
minimum={0}
|
||||||
maximum={rowCountNewIncluded - visibleRowCountUpperBound + 2}
|
maximum={rowCountNewIncluded - visibleRowCountUpperBound + 2}
|
||||||
|
@ -37,13 +37,13 @@ export function HorizontalScrollBar({
|
|||||||
maximum,
|
maximum,
|
||||||
viewportRatio = 0.5,
|
viewportRatio = 0.5,
|
||||||
}) {
|
}) {
|
||||||
const [ref, { width }] = useDimensions();
|
const [ref, { width }, node] = useDimensions();
|
||||||
const contentSize = Math.round(width / viewportRatio);
|
const contentSize = Math.round(width / viewportRatio);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const position01 = (valueToSet - minimum) / (maximum - minimum + 1);
|
const position01 = (valueToSet - minimum) / (maximum - minimum + 1);
|
||||||
const position = position01 * (contentSize - width);
|
const position = position01 * (contentSize - width);
|
||||||
if (ref.current) ref.current.scrollLeft = Math.floor(position);
|
if (node) node.scrollLeft = Math.floor(position);
|
||||||
}, [valueToSetDate]);
|
}, [valueToSetDate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -61,17 +61,17 @@ export function VerticalScrollBar({
|
|||||||
maximum,
|
maximum,
|
||||||
viewportRatio = 0.5,
|
viewportRatio = 0.5,
|
||||||
}) {
|
}) {
|
||||||
const [ref, { height }] = useDimensions();
|
const [ref, { height }, node] = useDimensions();
|
||||||
const contentSize = Math.round(height / viewportRatio);
|
const contentSize = Math.round(height / viewportRatio);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const position01 = (valueToSet - minimum) / (maximum - minimum + 1);
|
const position01 = (valueToSet - minimum) / (maximum - minimum + 1);
|
||||||
const position = position01 * (contentSize - height);
|
const position = position01 * (contentSize - height);
|
||||||
if (ref.current) ref.current.scrollTop = Math.floor(position);
|
if (node) node.scrollTop = Math.floor(position);
|
||||||
}, [valueToSetDate]);
|
}, [valueToSetDate]);
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
const position = ref.current.scrollTop;
|
const position = node.scrollTop;
|
||||||
const ratio = position / (contentSize - height);
|
const ratio = position / (contentSize - height);
|
||||||
if (ratio < 0) return 0;
|
if (ratio < 0) return 0;
|
||||||
let res = ratio * (maximum - minimum + 1) + minimum;
|
let res = ratio * (maximum - minimum + 1) + minimum;
|
||||||
|
340
web/src/datagrid/SeriesSizes.js
Normal file
340
web/src/datagrid/SeriesSizes.js
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
export class SeriesSizeItem {
|
||||||
|
constructor() {
|
||||||
|
this.scrollIndex = -1;
|
||||||
|
this.frozenIndex = -1;
|
||||||
|
this.modelIndex = 0;
|
||||||
|
this.size = 0;
|
||||||
|
this.position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// modelIndex;
|
||||||
|
// size;
|
||||||
|
// position;
|
||||||
|
|
||||||
|
get endPosition() {
|
||||||
|
return this.position + this.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SeriesSizes {
|
||||||
|
constructor() {
|
||||||
|
this.scrollItems = [];
|
||||||
|
this.sizeOverridesByModelIndex = {};
|
||||||
|
this.positions = [];
|
||||||
|
this.scrollIndexes = [];
|
||||||
|
this.frozenItems = [];
|
||||||
|
this.hiddenAndFrozenModelIndexes = null;
|
||||||
|
this.frozenModelIndexes = null;
|
||||||
|
|
||||||
|
this.count = 0;
|
||||||
|
this.maxSize = 1000;
|
||||||
|
this.defaultSize = 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
// private sizeOverridesByModelIndex: { [id] } = {};
|
||||||
|
// count;
|
||||||
|
// defaultSize;
|
||||||
|
// maxSize;
|
||||||
|
// private hiddenAndFrozenModelIndexes[] = [];
|
||||||
|
// private frozenModelIndexes[] = [];
|
||||||
|
// private hiddenModelIndexes[] = [];
|
||||||
|
// private scrollItems: SeriesSizeItem[] = [];
|
||||||
|
// private positions[] = [];
|
||||||
|
// private scrollIndexes[] = [];
|
||||||
|
// private frozenItems: SeriesSizeItem[] = [];
|
||||||
|
|
||||||
|
get scrollCount() {
|
||||||
|
return this.count - (this.hiddenAndFrozenModelIndexes != null ? this.hiddenAndFrozenModelIndexes.length : 0);
|
||||||
|
}
|
||||||
|
get frozenCount() {
|
||||||
|
return this.frozenModelIndexes != null ? this.frozenModelIndexes.length : 0;
|
||||||
|
}
|
||||||
|
get frozenSize() {
|
||||||
|
return _.sumBy(this.frozenItems, x => x.size);
|
||||||
|
}
|
||||||
|
get realCount() {
|
||||||
|
return this.frozenCount + this.scrollCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
putSizeOverride(modelIndex, size, sizeByUser = false) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
buildIndex() {
|
||||||
|
this.scrollItems = [];
|
||||||
|
this.scrollIndexes = _.filter(
|
||||||
|
_.map(this.intKeys(this.sizeOverridesByModelIndex), x => this.modelToReal(x) - this.frozenCount),
|
||||||
|
x => x >= 0
|
||||||
|
);
|
||||||
|
this.scrollIndexes.sort();
|
||||||
|
let lastScrollIndex = -1;
|
||||||
|
let lastEndPosition = 0;
|
||||||
|
this.scrollIndexes.forEach(scrollIndex => {
|
||||||
|
let modelIndex = this.realToModel(scrollIndex + this.frozenCount);
|
||||||
|
let size = 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 = 0;
|
||||||
|
for (let i = 0; i < this.frozenCount; i++) {
|
||||||
|
let modelIndex = this.frozenModelIndexes[i];
|
||||||
|
let size = 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getScrollIndexOnPosition(position) {
|
||||||
|
let itemOrder = _.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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
getFrozenIndexOnPosition(position) {
|
||||||
|
this.frozenItems.forEach(function(item) {
|
||||||
|
if (position >= item.position && position <= item.endPosition) return item.frozenIndex;
|
||||||
|
});
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// getSizeSum(startScrollIndex, endScrollIndex) {
|
||||||
|
// let order1 = _.sortedIndexOf(this.scrollIndexes, startScrollIndex);
|
||||||
|
// let order2 = _.sortedIndexOf(this.scrollIndexes, endScrollIndex);
|
||||||
|
// let count = endScrollIndex - startScrollIndex;
|
||||||
|
// if (order1 < 0)
|
||||||
|
// order1 = ~order1;
|
||||||
|
// if (order2 < 0)
|
||||||
|
// order2 = ~order2;
|
||||||
|
// let result = 0;
|
||||||
|
// for (let i = 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;
|
||||||
|
// }
|
||||||
|
getSizeByModelIndex(modelIndex) {
|
||||||
|
if (_.has(this.sizeOverridesByModelIndex, modelIndex)) return this.sizeOverridesByModelIndex[modelIndex];
|
||||||
|
return this.defaultSize;
|
||||||
|
}
|
||||||
|
getSizeByScrollIndex(scrollIndex) {
|
||||||
|
return this.getSizeByRealIndex(scrollIndex + this.frozenCount);
|
||||||
|
}
|
||||||
|
getSizeByRealIndex(realIndex) {
|
||||||
|
let modelIndex = this.realToModel(realIndex);
|
||||||
|
return this.getSizeByModelIndex(modelIndex);
|
||||||
|
}
|
||||||
|
removeSizeOverride(realIndex) {
|
||||||
|
let modelIndex = this.realToModel(realIndex);
|
||||||
|
delete this.sizeOverridesByModelIndex[modelIndex];
|
||||||
|
}
|
||||||
|
getScroll(sourceScrollIndex, targetScrollIndex) {
|
||||||
|
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))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modelIndexIsInScrollArea(modelIndex) {
|
||||||
|
let realIndex = this.modelToReal(modelIndex);
|
||||||
|
return realIndex >= this.frozenCount;
|
||||||
|
}
|
||||||
|
getTotalScrollSizeSum() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
getVisibleScrollSizeSum() {
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
intKeys(value) {
|
||||||
|
return _.keys(value).map(x => _.parseInt(x));
|
||||||
|
}
|
||||||
|
getPositionByRealIndex(realIndex) {
|
||||||
|
if (realIndex < 0) return 0;
|
||||||
|
if (realIndex < this.frozenCount) return this.frozenItems[realIndex].position;
|
||||||
|
return this.getPositionByScrollIndex(realIndex - this.frozenCount);
|
||||||
|
}
|
||||||
|
getPositionByScrollIndex(scrollIndex) {
|
||||||
|
let order = _.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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
getVisibleScrollCount(firstVisibleIndex, viewportSize) {
|
||||||
|
let res = 0;
|
||||||
|
let index = firstVisibleIndex;
|
||||||
|
let count = 0;
|
||||||
|
while (res < viewportSize && index <= this.scrollCount) {
|
||||||
|
console.log('this.getSizeByScrollIndex(index)', this.getSizeByScrollIndex(index));
|
||||||
|
res += this.getSizeByScrollIndex(index);
|
||||||
|
index++;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
console.log('getVisibleScrollCount', firstVisibleIndex, viewportSize, count);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
getVisibleScrollCountReversed(lastVisibleIndex, viewportSize) {
|
||||||
|
let res = 0;
|
||||||
|
let index = lastVisibleIndex;
|
||||||
|
let count = 0;
|
||||||
|
while (res < viewportSize && index >= 0) {
|
||||||
|
res += this.getSizeByScrollIndex(index);
|
||||||
|
index--;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
invalidateAfterScroll(oldFirstVisible, newFirstVisible, invalidate, viewportSize) {
|
||||||
|
if (newFirstVisible > oldFirstVisible) {
|
||||||
|
let oldVisibleCount = this.getVisibleScrollCount(oldFirstVisible, viewportSize);
|
||||||
|
let newVisibleCount = this.getVisibleScrollCount(newFirstVisible, viewportSize);
|
||||||
|
for (let i = oldFirstVisible + oldVisibleCount - 1; i <= newFirstVisible + newVisibleCount; i++) {
|
||||||
|
invalidate(i + this.frozenCount);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = newFirstVisible; i <= oldFirstVisible; i++) {
|
||||||
|
invalidate(i + this.frozenCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isWholeInView(firstVisibleIndex, index, viewportSize) {
|
||||||
|
let res = 0;
|
||||||
|
let testedIndex = firstVisibleIndex;
|
||||||
|
while (res < viewportSize && testedIndex < this.count) {
|
||||||
|
res += this.getSizeByScrollIndex(testedIndex);
|
||||||
|
if (testedIndex == index) return res <= viewportSize;
|
||||||
|
testedIndex++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
scrollInView(firstVisibleIndex, scrollIndex, viewportSize) {
|
||||||
|
if (this.isWholeInView(firstVisibleIndex, scrollIndex, viewportSize)) {
|
||||||
|
return firstVisibleIndex;
|
||||||
|
}
|
||||||
|
if (scrollIndex < firstVisibleIndex) {
|
||||||
|
return scrollIndex;
|
||||||
|
}
|
||||||
|
let res = 0;
|
||||||
|
let testedIndex = scrollIndex;
|
||||||
|
while (res < viewportSize && testedIndex >= 0) {
|
||||||
|
let size = this.getSizeByScrollIndex(testedIndex);
|
||||||
|
if (res + size > viewportSize) return testedIndex + 1;
|
||||||
|
testedIndex--;
|
||||||
|
res += size;
|
||||||
|
}
|
||||||
|
if (res >= viewportSize && testedIndex < scrollIndex) return testedIndex + 1;
|
||||||
|
return firstVisibleIndex;
|
||||||
|
}
|
||||||
|
resize(realIndex, newSize) {
|
||||||
|
if (realIndex < 0) return;
|
||||||
|
let modelIndex = this.realToModel(realIndex);
|
||||||
|
if (modelIndex < 0) return;
|
||||||
|
this.sizeOverridesByModelIndex[modelIndex] = newSize;
|
||||||
|
this.buildIndex();
|
||||||
|
}
|
||||||
|
setExtraordinaryIndexes(hidden, frozen) {
|
||||||
|
//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();
|
||||||
|
}
|
||||||
|
realToModel(realIndex) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
modelToReal(modelIndex) {
|
||||||
|
if (this.hiddenAndFrozenModelIndexes == null && this.frozenModelIndexes == null) return modelIndex;
|
||||||
|
if (modelIndex < 0) return -1;
|
||||||
|
let frozenIndex = this.frozenModelIndexes != null ? _.indexOf(this.frozenModelIndexes, modelIndex) : -1;
|
||||||
|
if (frozenIndex >= 0) return frozenIndex;
|
||||||
|
if (this.hiddenAndFrozenModelIndexes == null) return modelIndex;
|
||||||
|
let hiddenIndex = _.sortedIndex(this.hiddenAndFrozenModelIndexes, modelIndex);
|
||||||
|
if (this.hiddenAndFrozenModelIndexes[hiddenIndex] == modelIndex) return -1;
|
||||||
|
if (hiddenIndex >= 0) return modelIndex - hiddenIndex + this.frozenCount;
|
||||||
|
return modelIndex;
|
||||||
|
}
|
||||||
|
getFrozenPosition(frozenIndex) {
|
||||||
|
return this.frozenItems[frozenIndex].position;
|
||||||
|
}
|
||||||
|
hasSizeOverride(modelIndex) {
|
||||||
|
return _.has(this.sizeOverridesByModelIndex, modelIndex);
|
||||||
|
}
|
||||||
|
isVisible(testedRealIndex, firstVisibleScrollIndex, viewportSize) {
|
||||||
|
if (testedRealIndex < 0) return false;
|
||||||
|
if (testedRealIndex >= 0 && testedRealIndex < this.frozenCount) return true;
|
||||||
|
let scrollIndex = testedRealIndex - this.frozenCount;
|
||||||
|
let onPageIndex = scrollIndex - firstVisibleScrollIndex;
|
||||||
|
return onPageIndex >= 0 && onPageIndex < this.getVisibleScrollCount(firstVisibleScrollIndex, viewportSize);
|
||||||
|
}
|
||||||
|
}
|
@ -1,48 +1,107 @@
|
|||||||
import { useState, useCallback, useLayoutEffect } from 'react';
|
// import { useState, useCallback, useLayoutEffect } from 'react';
|
||||||
|
|
||||||
function getDimensionObject(node) {
|
// function getDimensionObject(node) {
|
||||||
const rect = node.getBoundingClientRect();
|
// const rect = node.getBoundingClientRect();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
width: rect.width,
|
// width: rect.width,
|
||||||
height: rect.height,
|
// height: rect.height,
|
||||||
top: 'x' in rect ? rect.x : rect.top,
|
// top: 'x' in rect ? rect.x : rect.top,
|
||||||
left: 'y' in rect ? rect.y : rect.left,
|
// left: 'y' in rect ? rect.y : rect.left,
|
||||||
x: 'x' in rect ? rect.x : rect.left,
|
// x: 'x' in rect ? rect.x : rect.left,
|
||||||
y: 'y' in rect ? rect.y : rect.top,
|
// y: 'y' in rect ? rect.y : rect.top,
|
||||||
right: rect.right,
|
// right: rect.right,
|
||||||
bottom: rect.bottom,
|
// bottom: rect.bottom,
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
function useDimensions({ liveMeasure = true } = {}) {
|
// function useDimensions({ liveMeasure = true } = {}) {
|
||||||
const [dimensions, setDimensions] = useState({});
|
// const [dimensions, setDimensions] = useState({});
|
||||||
|
// const [node, setNode] = useState(null);
|
||||||
|
|
||||||
|
// const ref = useCallback(node => {
|
||||||
|
// setNode(node);
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
// useLayoutEffect(() => {
|
||||||
|
// if (node) {
|
||||||
|
// const measure = () => window.requestAnimationFrame(() => setDimensions(getDimensionObject(node)));
|
||||||
|
// measure();
|
||||||
|
|
||||||
|
// if (liveMeasure) {
|
||||||
|
// window.addEventListener('resize', measure);
|
||||||
|
// window.addEventListener('scroll', measure);
|
||||||
|
|
||||||
|
// return () => {
|
||||||
|
// window.removeEventListener('resize', measure);
|
||||||
|
// window.removeEventListener('scroll', measure);
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }, [node]);
|
||||||
|
|
||||||
|
// return [ref, dimensions, node];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export default useDimensions;
|
||||||
|
|
||||||
|
import { useLayoutEffect, useState, useCallback } from 'react';
|
||||||
|
import ResizeObserver from 'resize-observer-polyfill';
|
||||||
|
|
||||||
|
// Export hook
|
||||||
|
export default function useDimensions(dependencies = []) {
|
||||||
const [node, setNode] = useState(null);
|
const [node, setNode] = useState(null);
|
||||||
|
const ref = useCallback(newNode => {
|
||||||
const ref = useCallback(node => {
|
setNode(newNode);
|
||||||
setNode(node);
|
}, []);
|
||||||
|
|
||||||
|
// Keep track of measurements
|
||||||
|
const [dimensions, setDimensions] = useState({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Define measure function
|
||||||
|
const measure = useCallback(innerNode => {
|
||||||
|
const rect = innerNode.getBoundingClientRect();
|
||||||
|
setDimensions({
|
||||||
|
x: rect.left,
|
||||||
|
y: rect.top,
|
||||||
|
left: rect.left,
|
||||||
|
top: rect.top,
|
||||||
|
right: rect.right,
|
||||||
|
bottom: rect.bottom,
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height,
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
// @ts-ignore
|
|
||||||
ref.current = node;
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (node) {
|
if (!node) {
|
||||||
const measure = () => window.requestAnimationFrame(() => setDimensions(getDimensionObject(node)));
|
return;
|
||||||
measure();
|
|
||||||
|
|
||||||
if (liveMeasure) {
|
|
||||||
window.addEventListener('resize', measure);
|
|
||||||
window.addEventListener('scroll', measure);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('resize', measure);
|
|
||||||
window.removeEventListener('scroll', measure);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [node]);
|
|
||||||
|
// Set initial measurements
|
||||||
|
measure(node);
|
||||||
|
|
||||||
|
// Observe resizing of element
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
measure(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
resizeObserver.observe(node);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, [node, measure, ...dependencies]);
|
||||||
|
|
||||||
return [ref, dimensions, node];
|
return [ref, dimensions, node];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useDimensions;
|
|
||||||
|
@ -8981,6 +8981,11 @@ requires-port@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||||
|
|
||||||
|
resize-observer-polyfill@^1.5.1:
|
||||||
|
version "1.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||||
|
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
|
||||||
|
|
||||||
resolve-cwd@^2.0.0:
|
resolve-cwd@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
||||||
|
Loading…
Reference in New Issue
Block a user