mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-30 22:03:58 +00:00
selection
This commit is contained in:
@@ -11,6 +11,7 @@ import axios from '../utility/axios';
|
|||||||
import ColumnLabel from './ColumnLabel';
|
import ColumnLabel from './ColumnLabel';
|
||||||
import DataFilterControl from './DataFilterControl';
|
import DataFilterControl from './DataFilterControl';
|
||||||
import { getFilterType } from '@dbgate/filterparser';
|
import { getFilterType } from '@dbgate/filterparser';
|
||||||
|
import { convertCellAddress, cellFromEvent, getCellRange } from './selection';
|
||||||
|
|
||||||
const GridContainer = styled.div`
|
const GridContainer = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -18,6 +19,7 @@ const GridContainer = styled.div`
|
|||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
user-select: none;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Table = styled.table`
|
const Table = styled.table`
|
||||||
@@ -73,6 +75,13 @@ const TableBodyCell = styled.td`
|
|||||||
padding: 2px;
|
padding: 2px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
${props =>
|
||||||
|
// @ts-ignore
|
||||||
|
props.isSelected &&
|
||||||
|
`
|
||||||
|
background: initial;
|
||||||
|
background-color: deepskyblue;
|
||||||
|
color: white;`}
|
||||||
`;
|
`;
|
||||||
const HintSpan = styled.span`
|
const HintSpan = styled.span`
|
||||||
color: gray;
|
color: gray;
|
||||||
@@ -105,6 +114,10 @@ export default function DataGridCore(props) {
|
|||||||
|
|
||||||
const loadedTimeRef = React.useRef(0);
|
const loadedTimeRef = React.useRef(0);
|
||||||
|
|
||||||
|
const [currentCell, setCurrentCell] = React.useState([undefined, undefined]);
|
||||||
|
const [selectedCells, setSelectedCells] = React.useState([]);
|
||||||
|
const [dragStartCell, setDragStartCell] = React.useState(null);
|
||||||
|
|
||||||
const loadNextData = async () => {
|
const loadNextData = async () => {
|
||||||
if (isLoading) return;
|
if (isLoading) return;
|
||||||
setLoadProps({
|
setLoadProps({
|
||||||
@@ -273,6 +286,42 @@ export default function DataGridCore(props) {
|
|||||||
return columnSizes;
|
return columnSizes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleGridMouseDown(event) {
|
||||||
|
const cell = cellFromEvent(event);
|
||||||
|
setCurrentCell(cell);
|
||||||
|
setSelectedCells(getCellRange(cell, cell));
|
||||||
|
setDragStartCell(cell);
|
||||||
|
console.log('START',cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGridMouseMove(event) {
|
||||||
|
if (dragStartCell) {
|
||||||
|
const cell = cellFromEvent(event);
|
||||||
|
setCurrentCell(cell);
|
||||||
|
setSelectedCells(getCellRange(dragStartCell, cell));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGridMouseUp(event) {
|
||||||
|
if (dragStartCell) {
|
||||||
|
const cell = cellFromEvent(event);
|
||||||
|
setCurrentCell(cell);
|
||||||
|
setSelectedCells(getCellRange(dragStartCell, cell));
|
||||||
|
setDragStartCell(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cellIsSelected(row, col) {
|
||||||
|
const [currentRow, currentCol] = currentCell;
|
||||||
|
if (row == currentRow && col == currentCol) return true;
|
||||||
|
for (const [selectedRow, selectedCol] of selectedCells) {
|
||||||
|
if (row == selectedRow && col == selectedCol) return true;
|
||||||
|
if (selectedRow == 'header' && col == selectedCol) return true;
|
||||||
|
if (row == selectedRow && selectedCol == 'header') return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// console.log('visibleRowCountUpperBound', visibleRowCountUpperBound);
|
// console.log('visibleRowCountUpperBound', visibleRowCountUpperBound);
|
||||||
// console.log('gridScrollAreaHeight', gridScrollAreaHeight);
|
// console.log('gridScrollAreaHeight', gridScrollAreaHeight);
|
||||||
// console.log('containerHeight', containerHeight);
|
// console.log('containerHeight', containerHeight);
|
||||||
@@ -282,7 +331,7 @@ export default function DataGridCore(props) {
|
|||||||
|
|
||||||
const visibleRealColumnIndexes = [];
|
const visibleRealColumnIndexes = [];
|
||||||
const modelIndexes = {};
|
const modelIndexes = {};
|
||||||
/** @type {(import('@dbgate/datalib').DisplayColumn & {widthPx: string})[]} */
|
/** @type {(import('@dbgate/datalib').DisplayColumn & {widthPx: string; colIndex: number})[]} */
|
||||||
const realColumns = [];
|
const realColumns = [];
|
||||||
|
|
||||||
// frozen columns
|
// frozen columns
|
||||||
@@ -308,6 +357,7 @@ export default function DataGridCore(props) {
|
|||||||
const widthNumber = columnSizes.getSizeByRealIndex(colIndex);
|
const widthNumber = columnSizes.getSizeByRealIndex(colIndex);
|
||||||
realColumns.push({
|
realColumns.push({
|
||||||
...col,
|
...col,
|
||||||
|
colIndex,
|
||||||
widthPx: `${widthNumber}px`,
|
widthPx: `${widthNumber}px`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -322,12 +372,14 @@ export default function DataGridCore(props) {
|
|||||||
// console.log('visibleRealColumnIndexes', visibleRealColumnIndexes);
|
// console.log('visibleRealColumnIndexes', visibleRealColumnIndexes);
|
||||||
return (
|
return (
|
||||||
<GridContainer ref={containerRef}>
|
<GridContainer ref={containerRef}>
|
||||||
<Table>
|
<Table onMouseDown={handleGridMouseDown} onMouseMove={handleGridMouseMove} onMouseUp={handleGridMouseUp}>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableHeaderRow ref={headerRowRef}>
|
<TableHeaderRow ref={headerRowRef}>
|
||||||
<TableHeaderCell />
|
<TableHeaderCell data-row="header" data-col="header" />
|
||||||
{realColumns.map(col => (
|
{realColumns.map(col => (
|
||||||
<TableHeaderCell
|
<TableHeaderCell
|
||||||
|
data-row="header"
|
||||||
|
data-col={col.colIndex}
|
||||||
key={col.uniqueName}
|
key={col.uniqueName}
|
||||||
style={{ width: col.widthPx, minWidth: col.widthPx, maxWidth: col.widthPx }}
|
style={{ width: col.widthPx, minWidth: col.widthPx, maxWidth: col.widthPx }}
|
||||||
>
|
>
|
||||||
@@ -336,7 +388,11 @@ export default function DataGridCore(props) {
|
|||||||
))}
|
))}
|
||||||
</TableHeaderRow>
|
</TableHeaderRow>
|
||||||
<TableHeaderRow>
|
<TableHeaderRow>
|
||||||
<TableHeaderCell style={{ width: hederColwidthPx, minWidth: hederColwidthPx, maxWidth: hederColwidthPx }}>
|
<TableHeaderCell
|
||||||
|
style={{ width: hederColwidthPx, minWidth: hederColwidthPx, maxWidth: hederColwidthPx }}
|
||||||
|
data-row="filter"
|
||||||
|
data-col="header"
|
||||||
|
>
|
||||||
{filterCount > 0 && (
|
{filterCount > 0 && (
|
||||||
<button onClick={handleClearFilters}>
|
<button onClick={handleClearFilters}>
|
||||||
<i className="fas fa-times" />
|
<i className="fas fa-times" />
|
||||||
@@ -347,6 +403,8 @@ export default function DataGridCore(props) {
|
|||||||
<TableFilterCell
|
<TableFilterCell
|
||||||
key={col.uniqueName}
|
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="filter"
|
||||||
|
data-col={col.colIndex}
|
||||||
>
|
>
|
||||||
<DataFilterControl
|
<DataFilterControl
|
||||||
filterType={getFilterType(col.commonType ? col.commonType.typeCode : null)}
|
filterType={getFilterType(col.commonType ? col.commonType.typeCode : null)}
|
||||||
@@ -362,11 +420,17 @@ export default function DataGridCore(props) {
|
|||||||
.slice(firstVisibleRowScrollIndex, firstVisibleRowScrollIndex + visibleRowCountUpperBound)
|
.slice(firstVisibleRowScrollIndex, firstVisibleRowScrollIndex + visibleRowCountUpperBound)
|
||||||
.map((row, index) => (
|
.map((row, index) => (
|
||||||
<TableBodyRow key={firstVisibleRowScrollIndex + index}>
|
<TableBodyRow key={firstVisibleRowScrollIndex + index}>
|
||||||
<TableHeaderCell>{firstVisibleRowScrollIndex + index + 1}</TableHeaderCell>
|
<TableHeaderCell data-row={firstVisibleRowScrollIndex + index} data-col="header">
|
||||||
|
{firstVisibleRowScrollIndex + index + 1}
|
||||||
|
</TableHeaderCell>
|
||||||
{realColumns.map(col => (
|
{realColumns.map(col => (
|
||||||
<TableBodyCell
|
<TableBodyCell
|
||||||
key={col.uniqueName}
|
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
|
||||||
|
isSelected={cellIsSelected(firstVisibleRowScrollIndex + index, col.colIndex)}
|
||||||
>
|
>
|
||||||
<CellFormattedValue value={row[col.uniqueName]} />
|
<CellFormattedValue value={row[col.uniqueName]} />
|
||||||
{col.hintColumnName && <HintSpan>{row[col.hintColumnName]}</HintSpan>}
|
{col.hintColumnName && <HintSpan>{row[col.hintColumnName]}</HintSpan>}
|
||||||
|
|||||||
53
packages/web/src/datagrid/selection.ts
Normal file
53
packages/web/src/datagrid/selection.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
export type CellAddress = [number | 'header' | 'filter' | undefined, number | 'header' | undefined];
|
||||||
|
|
||||||
|
export function getCellRange(a: CellAddress, b: CellAddress): CellAddress[] {
|
||||||
|
const [rowA, colA] = a;
|
||||||
|
const [rowB, colB] = b;
|
||||||
|
|
||||||
|
if (_.isNumber(rowA) && _.isNumber(colA) && _.isNumber(rowB) && _.isNumber(colB)) {
|
||||||
|
const rowMin = Math.min(rowA, rowB);
|
||||||
|
const rowMax = Math.max(rowA, rowB);
|
||||||
|
const colMin = Math.min(colA, colB);
|
||||||
|
const colMax = Math.max(colA, colB);
|
||||||
|
const res = [];
|
||||||
|
for (let row = rowMin; row <= rowMax; row++) {
|
||||||
|
for (let col = colMin; col <= colMax; col++) {
|
||||||
|
res.push([row, col]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if (rowA == 'header' && rowB == 'header' && _.isNumber(colA) && _.isNumber(colB)) {
|
||||||
|
const colMin = Math.min(colA, colB);
|
||||||
|
const colMax = Math.max(colA, colB);
|
||||||
|
const res = [];
|
||||||
|
for (let col = colMin; col <= colMax; col++) {
|
||||||
|
res.push(['header', col]);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if (colA == 'header' && colB == 'header' && _.isNumber(rowA) && _.isNumber(rowB)) {
|
||||||
|
const rowMin = Math.min(rowA, rowB);
|
||||||
|
const rowMax = Math.max(rowA, rowB);
|
||||||
|
const res = [];
|
||||||
|
for (let row = rowMin; row <= rowMax; row++) {
|
||||||
|
res.push([row, 'header']);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertCellAddress(row, col): CellAddress {
|
||||||
|
const rowNumber = parseInt(row);
|
||||||
|
const colNumber = parseInt(col);
|
||||||
|
return [_.isNaN(rowNumber) ? row : rowNumber, _.isNaN(colNumber) ? col : colNumber];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cellFromEvent(event): CellAddress {
|
||||||
|
const cell = event.target.closest('td');
|
||||||
|
const col = cell.getAttribute('data-col');
|
||||||
|
const row = cell.getAttribute('data-row');
|
||||||
|
return convertCellAddress(row, col);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user