From 639d6033f3ac2d9cac661c476aeaf534d75eb3e7 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 25 Jan 2020 23:13:30 +0100 Subject: [PATCH] grid scollbar --- web/src/datagrid/DataGrid.js | 120 +++++++++++++++++ web/src/datagrid/ScrollBars.js | 214 +++++++++++++++++++++++++++++++ web/src/tabs/TableDataTab.js | 105 +-------------- web/src/utility/useDimensions.js | 48 +++++++ 4 files changed, 384 insertions(+), 103 deletions(-) create mode 100644 web/src/datagrid/DataGrid.js create mode 100644 web/src/datagrid/ScrollBars.js create mode 100644 web/src/utility/useDimensions.js diff --git a/web/src/datagrid/DataGrid.js b/web/src/datagrid/DataGrid.js new file mode 100644 index 00000000..694eb7ca --- /dev/null +++ b/web/src/datagrid/DataGrid.js @@ -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 ( + + + + + {columns.map(col => ( + + {col.name} + + ))} + + + + {rows + .slice(firstVisibleRowScrollIndex, firstVisibleRowScrollIndex + visibleRowCountUpperBound) + .map((row, index) => ( + + {columns.map(col => ( + + {row[col.name]} + + ))} + + ))} + +
+ + +
+ ); +} diff --git a/web/src/datagrid/ScrollBars.js b/web/src/datagrid/ScrollBars.js new file mode 100644 index 00000000..a5386e7e --- /dev/null +++ b/web/src/datagrid/ScrollBars.js @@ -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 ( + +   + + ); +} + +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 ( + +   + + ); +} + +// export interface IScrollBarProps { +// viewportRatio: number; +// minimum: number; +// maximum: number; +// containerStyle: any; +// onScroll?: any; +// } + +// export abstract class ScrollBarBase extends React.Component { +// 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
this.domScrollContainer = x} style={this.props.containerStyle}> +//
this.domScrollContent = x} style={{ width: this.contentSize }}> +//   +//
+//
; +// } + +// 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
this.domScrollContainer = x} style={this.props.containerStyle}> +//
this.domScrollContent = x} style={{ height: this.contentSize }}> +//   +//
+//
; +// } + +// getContainerSize(): number { +// return $(this.domScrollContainer).height(); +// } + +// updateContentSize() { +// $(this.domScrollContent).height(this.contentSize); +// } + +// getScrollPosition() { +// return $(this.domScrollContainer).scrollTop(); +// } + +// setScrollPosition(value: number) { +// $(this.domScrollContainer).scrollTop(value); +// } +// } diff --git a/web/src/tabs/TableDataTab.js b/web/src/tabs/TableDataTab.js index cbc27b3e..86c897b1 100644 --- a/web/src/tabs/TableDataTab.js +++ b/web/src/tabs/TableDataTab.js @@ -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 ( - - - - {columns.map(col => ( - - {col.name} - - ))} - - - - {rows.map((row, index) => ( - - {columns.map(col => ( - - {row[col.name]} - - ))} - - ))} - -
- ); + return ; } diff --git a/web/src/utility/useDimensions.js b/web/src/utility/useDimensions.js new file mode 100644 index 00000000..faa22a80 --- /dev/null +++ b/web/src/utility/useDimensions.js @@ -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;