mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 12:13:57 +00:00
grid scollbar
This commit is contained in:
parent
564875c32e
commit
639d6033f3
120
web/src/datagrid/DataGrid.js
Normal file
120
web/src/datagrid/DataGrid.js
Normal 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>
|
||||
);
|
||||
}
|
214
web/src/datagrid/ScrollBars.js
Normal file
214
web/src/datagrid/ScrollBars.js
Normal 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` }}> </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` }}> </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 }}>
|
||||
//
|
||||
// </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 }}>
|
||||
//
|
||||
// </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);
|
||||
// }
|
||||
// }
|
@ -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 }} />;
|
||||
}
|
||||
|
48
web/src/utility/useDimensions.js
Normal file
48
web/src/utility/useDimensions.js
Normal 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;
|
Loading…
Reference in New Issue
Block a user