grid scollbar

This commit is contained in:
Jan Prochazka 2020-01-25 23:13:30 +01:00
parent 564875c32e
commit 639d6033f3
4 changed files with 384 additions and 103 deletions

View File

@ -0,0 +1,120 @@
import React, { useState } from 'react';
import useFetch from '../utility/useFetch';
import styled from 'styled-components';
import theme from '../theme';
import { HorizontalScrollBar, VerticalScrollBar } from './ScrollBars';
import useDimensions from '../utility/useDimensions';
const GridContainer = styled.div``;
const Table = styled.table`
position: absolute;
left: 0;
top: 0;
bottom: 20px;
right: 20px;
overflow: scroll;
border-collapse: collapse;
`;
const TableHead = styled.thead`
// display: block;
// width: 300px;
`;
const TableBody = styled.tbody`
// display: block;
// overflow: auto;
// height: 100px;
`;
const TableHeaderRow = styled.tr`
// height: 35px;
`;
const TableBodyRow = styled.tr`
// height: 35px;
background-color: #ffffff;
&:nth-child(6n + 4) {
background-color: #ebebeb;
}
&:nth-child(6n + 7) {
background-color: #ebf5ff;
}
`;
const TableHeaderCell = styled.td`
font-weight: bold;
border: 1px solid #c0c0c0;
// border-collapse: collapse;
text-align: left;
padding: 2px;
background-color: #f6f7f9;
overflow: hidden;
`;
const TableBodyCell = styled.td`
font-weight: normal;
border: 1px solid #c0c0c0;
// border-collapse: collapse;
padding: 2px;
white-space: nowrap;
overflow: hidden;
`;
export default function DataGrid({ params }) {
const data = useFetch({
url: 'tables/table-data',
params,
});
const { rows, columns } = data || {};
const [firstVisibleRowScrollIndex, setFirstVisibleRowScrollIndex] = useState(0);
const [headerRowRef, { height: rowHeight }] = useDimensions();
const [tableBodyRef, { height: gridScrollAreaHeight }] = useDimensions();
// 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;
const rowCountNewIncluded = rows.length;
const handleRowScroll = value => {
setFirstVisibleRowScrollIndex(value);
};
console.log('visibleRowCountUpperBound', visibleRowCountUpperBound);
console.log('gridScrollAreaHeight', gridScrollAreaHeight);
return (
<GridContainer>
<Table>
<TableHead>
<TableHeaderRow ref={headerRowRef}>
{columns.map(col => (
<TableHeaderCell key={col.name} style={{ width: '60px' }}>
{col.name}
</TableHeaderCell>
))}
</TableHeaderRow>
</TableHead>
<TableBody ref={tableBodyRef}>
{rows
.slice(firstVisibleRowScrollIndex, firstVisibleRowScrollIndex + visibleRowCountUpperBound)
.map((row, index) => (
<TableBodyRow key={firstVisibleRowScrollIndex + index}>
{columns.map(col => (
<TableBodyCell key={col.name} style={{ width: '60px' }}>
{row[col.name]}
</TableBodyCell>
))}
</TableBodyRow>
))}
</TableBody>
</Table>
<HorizontalScrollBar minimum={0} maximum={columns.length - 1} />
<VerticalScrollBar
minimum={0}
maximum={rowCountNewIncluded - visibleRowCountUpperBound + 2}
onScroll={handleRowScroll}
viewportRatio={visibleRowCountUpperBound / rowCountNewIncluded}
/>
</GridContainer>
);
}

View File

@ -0,0 +1,214 @@
import _ from 'lodash';
import React from 'react';
import styled from 'styled-components';
import useDimensions from '../utility/useDimensions';
const StyledHorizontalScrollBar = styled.div`
overflow-x: scroll;
height: 16px;
position: absolute;
bottom: 0;
//left: 100px;
// right: 20px;
right: 0;
left: 0;
`;
const StyledHorizontalScrollContent = styled.div``;
const StyledVerticalScrollBar = styled.div`
overflow-y: scroll;
width: 20px;
position: absolute;
right: 0px;
width: 20px;
bottom: 16px;
// bottom: 0;
top: 0;
`;
const StyledVerticalScrollContent = styled.div``;
export function HorizontalScrollBar({
onScroll = undefined,
valueToSet = undefined,
valueToSetDate = undefined,
minimum,
maximum,
viewportRatio = 0.5,
}) {
const [ref, { width }] = useDimensions();
const contentSize = Math.round(width / viewportRatio);
React.useEffect(() => {
const position01 = (valueToSet - minimum) / (maximum - minimum + 1);
const position = position01 * (contentSize - width);
if (ref.current) ref.current.scrollLeft = Math.floor(position);
}, [valueToSetDate]);
return (
<StyledHorizontalScrollBar ref={ref}>
<StyledHorizontalScrollContent style={{ width: `${contentSize}px` }}>&nbsp;</StyledHorizontalScrollContent>
</StyledHorizontalScrollBar>
);
}
export function VerticalScrollBar({
onScroll,
valueToSet = undefined,
valueToSetDate = undefined,
minimum,
maximum,
viewportRatio = 0.5,
}) {
const [ref, { height }] = useDimensions();
const contentSize = Math.round(height / viewportRatio);
React.useEffect(() => {
const position01 = (valueToSet - minimum) / (maximum - minimum + 1);
const position = position01 * (contentSize - height);
if (ref.current) ref.current.scrollTop = Math.floor(position);
}, [valueToSetDate]);
const handleScroll = () => {
const position = ref.current.scrollTop;
const ratio = position / (contentSize - height);
if (ratio < 0) return 0;
let res = ratio * (maximum - minimum + 1) + minimum;
onScroll(Math.floor(res + 0.3));
};
return (
<StyledVerticalScrollBar ref={ref} onScroll={handleScroll}>
<StyledVerticalScrollContent style={{ height: `${contentSize}px` }}>&nbsp;</StyledVerticalScrollContent>
</StyledVerticalScrollBar>
);
}
// export interface IScrollBarProps {
// viewportRatio: number;
// minimum: number;
// maximum: number;
// containerStyle: any;
// onScroll?: any;
// }
// export abstract class ScrollBarBase extends React.Component<IScrollBarProps, {}> {
// domScrollContainer: Element;
// domScrollContent: Element;
// contentSize: number;
// containerResizedBind: any;
// constructor(props) {
// super(props);
// this.containerResizedBind = this.containerResized.bind(this);
// }
// componentDidMount() {
// $(this.domScrollContainer).scroll(this.onScroll.bind(this));
// createResizeDetector(this.domScrollContainer, this.containerResized.bind(this));
// window.addEventListener('resize', this.containerResizedBind);
// this.updateContentSize();
// }
// componentWillUnmount() {
// deleteResizeDetector(this.domScrollContainer);
// window.removeEventListener('resize', this.containerResizedBind);
// }
// onScroll() {
// if (this.props.onScroll) {
// this.props.onScroll(this.value);
// }
// }
// get value(): number {
// let position = this.getScrollPosition();
// let ratio = position / (this.contentSize - this.getContainerSize());
// if (ratio < 0) return 0;
// let res = ratio * (this.props.maximum - this.props.minimum + 1) + this.props.minimum;
// return Math.floor(res + 0.3);
// }
// set value(value: number) {
// let position01 = (value - this.props.minimum) / (this.props.maximum - this.props.minimum + 1);
// let position = position01 * (this.contentSize - this.getContainerSize());
// this.setScrollPosition(Math.floor(position));
// }
// containerResized() {
// this.setContentSizeField();
// this.updateContentSize();
// }
// setContentSizeField() {
// let lastContentSize = this.contentSize;
// this.contentSize = Math.round(this.getContainerSize() / this.props.viewportRatio);
// if (_.isNaN(this.contentSize)) this.contentSize = 0;
// if (this.contentSize > 1000000 && detectBrowser() == BrowserType.Firefox) this.contentSize = 1000000;
// if (lastContentSize != this.contentSize) {
// this.updateContentSize();
// }
// }
// abstract getContainerSize(): number;
// abstract updateContentSize();
// abstract getScrollPosition(): number;
// abstract setScrollPosition(value: number);
// }
// export class HorizontalScrollBar extends ScrollBarBase {
// render() {
// this.setContentSizeField();
// return <div className='ReactGridHorizontalScrollBar' ref={x => this.domScrollContainer = x} style={this.props.containerStyle}>
// <div className='ReactGridHorizontalScrollContent' ref={x => this.domScrollContent = x} style={{ width: this.contentSize }}>
// &nbsp;
// </div>
// </div>;
// }
// getContainerSize(): number {
// return $(this.domScrollContainer).width();
// }
// updateContentSize() {
// $(this.domScrollContent).width(this.contentSize);
// }
// getScrollPosition() {
// return $(this.domScrollContainer).scrollLeft();
// }
// setScrollPosition(value: number) {
// $(this.domScrollContainer).scrollLeft(value);
// }
// }
// export class VerticalScrollBar extends ScrollBarBase {
// render() {
// this.setContentSizeField();
// return <div className='ReactGridVerticalScrollBar' ref={x => this.domScrollContainer = x} style={this.props.containerStyle}>
// <div className='ReactGridVerticalScrollContent' ref={x => this.domScrollContent = x} style={{ height: this.contentSize }}>
// &nbsp;
// </div>
// </div>;
// }
// getContainerSize(): number {
// return $(this.domScrollContainer).height();
// }
// updateContentSize() {
// $(this.domScrollContent).height(this.contentSize);
// }
// getScrollPosition() {
// return $(this.domScrollContainer).scrollTop();
// }
// setScrollPosition(value: number) {
// $(this.domScrollContainer).scrollTop(value);
// }
// }

View File

@ -2,109 +2,8 @@ import React from 'react';
import useFetch from '../utility/useFetch';
import styled from 'styled-components';
import theme from '../theme';
// const Table = styled.table`
// position: absolute;
// left:0;
// top:0:
// bottom:0;
// right:0;
// overflow: scroll;
// `;
// const TableHead = styled.thead`
// // display: block;
// // width: 300px;
// `;
// const TableBody = styled.tbody`
// // display: block;
// // overflow: auto;
// // height: 100px;
// `;
// const TableHeaderRow = styled.tr`
// height: 35px;
// `;
// const TableBodyRow = styled.tr`
// height: 35px;
// `;
// const TableHeaderCell = styled.td`
// font-weight: bold;
// `;
// const TableBodyCell = styled.td`
// white-space: nowrap;
// `;
const Table = styled.div`
overflow-x: scroll;
width: 500px;
position: absolute;
left:0;
top:0:
bottom:0;
right:0;
`;
const TableHead = styled.div`
// display: block;
// width: 300px;
// width:700px;
`;
const TableBody = styled.div`
// display: block;
overflow-y: scroll;
height: 200px;
`;
const TableHeaderRow = styled.div`
display: flex;
height: 35px;
`;
const TableBodyRow = styled.div`
display: flex;
height: 35px;
`;
const TableHeaderCell = styled.div`
font-weight: bold;
width: 160px;
overflow: hidden;
`;
const TableBodyCell = styled.div`
white-space: nowrap;
width: 160px;
overflow: hidden;
`;
import DataGrid from '../datagrid/DataGrid';
export default function TableDataTab({ conid, database, schemaName, pureName }) {
const data = useFetch({
url: 'tables/table-data',
params: {
conid,
database,
schemaName,
pureName,
},
});
const { rows, columns } = data || {};
if (!columns || !rows) return null;
return (
<Table>
<TableHead>
<TableHeaderRow>
{columns.map(col => (
<TableHeaderCell key={col.name} style={{ width: '60px' }}>
{col.name}
</TableHeaderCell>
))}
</TableHeaderRow>
</TableHead>
<TableBody>
{rows.map((row, index) => (
<TableBodyRow key={index}>
{columns.map(col => (
<TableBodyCell key={col.name} style={{ width: '60px' }}>
{row[col.name]}
</TableBodyCell>
))}
</TableBodyRow>
))}
</TableBody>
</Table>
);
return <DataGrid params={{ conid, database, schemaName, pureName }} />;
}

View File

@ -0,0 +1,48 @@
import { useState, useCallback, useLayoutEffect } from 'react';
function getDimensionObject(node) {
const rect = node.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
top: 'x' in rect ? rect.x : rect.top,
left: 'y' in rect ? rect.y : rect.left,
x: 'x' in rect ? rect.x : rect.left,
y: 'y' in rect ? rect.y : rect.top,
right: rect.right,
bottom: rect.bottom,
};
}
function useDimensions({ liveMeasure = true } = {}) {
const [dimensions, setDimensions] = useState({});
const [node, setNode] = useState(null);
const ref = useCallback(node => {
setNode(node);
}, []);
// @ts-ignore
ref.current = 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;