From 8f6b211b1b2747f7930fc1c4d875a398e5df83d5 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 12 Mar 2020 08:02:13 +0100 Subject: [PATCH] filter controls --- packages/datalib/src/GridConfig.ts | 7 +- packages/datalib/src/GridDisplay.ts | 35 +- packages/datalib/src/TableGridDisplay.ts | 1 + .../web/src/datagrid/DataFilterControl.js | 401 ++++++++++++++++++ packages/web/src/datagrid/DataGridCore.js | 25 +- packages/web/src/modals/DropDownMenu.js | 8 +- packages/web/src/utility/keycodes.js | 99 +++++ 7 files changed, 563 insertions(+), 13 deletions(-) create mode 100644 packages/web/src/datagrid/DataFilterControl.js create mode 100644 packages/web/src/utility/keycodes.js diff --git a/packages/datalib/src/GridConfig.ts b/packages/datalib/src/GridConfig.ts index f94ec033..ec0f29a4 100644 --- a/packages/datalib/src/GridConfig.ts +++ b/packages/datalib/src/GridConfig.ts @@ -1,12 +1,16 @@ import { DisplayColumn } from './GridDisplay'; import { TableInfo } from '@dbgate/types'; -export interface GridConfig { +export interface GridConfigColumns { hiddenColumns: string[]; expandedColumns: string[]; addedColumns: string[]; } +export interface GridConfig extends GridConfigColumns { + filters: { [uniqueName: string]: string }; +} + export interface GridCache { tables: { [uniqueName: string]: TableInfo }; refreshTime: number; @@ -17,6 +21,7 @@ export function createGridConfig(): GridConfig { hiddenColumns: [], expandedColumns: [], addedColumns: [], + filters: {}, }; } diff --git a/packages/datalib/src/GridDisplay.ts b/packages/datalib/src/GridDisplay.ts index f03e9441..600532b1 100644 --- a/packages/datalib/src/GridDisplay.ts +++ b/packages/datalib/src/GridDisplay.ts @@ -1,5 +1,5 @@ import _ from 'lodash'; -import { GridConfig, GridCache } from './GridConfig'; +import { GridConfig, GridCache, GridConfigColumns } from './GridConfig'; import { ForeignKeyInfo, TableInfo, ColumnInfo } from '@dbgate/types'; import { filterName } from './filterName'; import { Select } from '@dbgate/sqltree'; @@ -54,7 +54,7 @@ export abstract class GridDisplay { }); } - includeInColumnSet(field: keyof GridConfig, uniqueName: string, isIncluded: boolean) { + includeInColumnSet(field: keyof GridConfigColumns, uniqueName: string, isIncluded: boolean) { if (isIncluded) { this.setConfig({ ...this.config, @@ -248,13 +248,15 @@ export abstract class GridDisplay { } getDisplayColumns(table: TableInfo, parentPath: string[]) { - return table?.columns - ?.map(col => this.getDisplayColumn(table, col, parentPath)) - ?.map(col => ({ - ...col, - isChecked: this.isColumnChecked(col), - hintColumnName: col.foreignKey ? `hint_${col.uniqueName}` : null, - })); + return ( + table?.columns + ?.map(col => this.getDisplayColumn(table, col, parentPath)) + ?.map(col => ({ + ...col, + isChecked: this.isColumnChecked(col), + hintColumnName: col.foreignKey ? `hint_${col.uniqueName}` : null, + })) || [] + ); } getColumns(columnFilter) { @@ -272,4 +274,19 @@ export abstract class GridDisplay { toggleExpandedColumn(uniqueName: string) { this.includeInColumnSet('expandedColumns', uniqueName, !this.isExpandedColumn(uniqueName)); } + + getFilter(uniqueName: string) { + return this.config.filters[uniqueName]; + } + + setFilter(uniqueName, value) { + this.setConfig({ + ...this.config, + filters: { + ...this.config.filters, + [uniqueName]: value, + }, + }); + this.reload(); + } } diff --git a/packages/datalib/src/TableGridDisplay.ts b/packages/datalib/src/TableGridDisplay.ts index c9a1b327..bc28f389 100644 --- a/packages/datalib/src/TableGridDisplay.ts +++ b/packages/datalib/src/TableGridDisplay.ts @@ -18,6 +18,7 @@ export class TableGridDisplay extends GridDisplay { } createSelect() { + if (!this.table.columns) return null; const orderColumnName = this.table.columns[0].columnName; const select: Select = { commandType: 'select', diff --git a/packages/web/src/datagrid/DataFilterControl.js b/packages/web/src/datagrid/DataFilterControl.js new file mode 100644 index 00000000..340078be --- /dev/null +++ b/packages/web/src/datagrid/DataFilterControl.js @@ -0,0 +1,401 @@ +import React from 'react'; +import { DropDownMenuItem, DropDownMenuDivider, showMenu } from '../modals/DropDownMenu'; +import styled from 'styled-components'; +import keycodes from '../utility/keycodes'; +// import { $ } from '../../Utility/jquery'; +// import autobind from 'autobind-decorator'; +// import * as React from 'react'; + +// import { createMultiLineFilter } from '../../DataLib/FilterTools'; +// import { ModalDialog } from '../Dialogs'; +// import { FilterDialog } from '../Dialogs/FilterDialog'; +// import { FilterMultipleValuesDialog } from '../Dialogs/FilterMultipleValuesDialog'; +// import { IconSpan } from '../Navigation/NavUtils'; +// import { KeyCodes } from '../ReactDataGrid/KeyCodes'; +// import { DropDownMenu, DropDownMenuDivider, DropDownMenuItem, DropDownSubmenuItem } from './DropDownMenu'; +// import { FilterParserType } from '../../SwaggerClients'; +// import { IFilterHolder } from '../CommonControls'; +// import { GrayFilterIcon } from '../Icons'; + +// export interface IDataFilterControlProps { +// filterType: FilterParserType; +// getFilter: Function; +// setFilter: Function; +// width: number; +// onControlKey?: Function; +// isReadOnly?: boolean; +// inputElementId?: string; +// } + +const FilterDiv = styled.div` + display: flex; +`; +const FilterInput = styled.input` + flex: 1; + width: 10px; +`; +const FilterButton = styled.button` + color: gray; +`; + +function DropDownContent({ filterType, setFilter, filterMultipleValues, openFilterWindow }) { + switch (filterType) { + case 'number': + return ( + <> + setFilter('')}>Clear Filter + filterMultipleValues()}>Filter multiple values + openFilterWindow('=')}>Equals... + openFilterWindow('<>')}>Does Not Equal... + setFilter('NULL')}>Is Null + setFilter('NOT NULL')}>Is Not Null + openFilterWindow('>')}>Greater Than... + openFilterWindow('>=')}>Greater Than Or Equal To... + openFilterWindow('<')}>Less Than... + openFilterWindow('<=')}>Less Than Or Equal To... + + ); + case 'logical': + return ( + <> + setFilter('')}>Clear Filter + filterMultipleValues()}>Filter multiple values + setFilter('NULL')}>Is Null + setFilter('NOT NULL')}>Is Not Null + setFilter('TRUE')}>Is True + setFilter('FALSE')}>Is False + setFilter('TRUE, NULL')}>Is True or NULL + setFilter('FALSE, NULL')}>Is False or NULL + + ); + case 'datetime': + return ( + <> + setFilter('')}>Clear Filter + filterMultipleValues()}>Filter multiple values + setFilter('NULL')}>Is Null + setFilter('NOT NULL')}>Is Not Null + + + + openFilterWindow('<=')}>Before... + openFilterWindow('>=')}>After... + openFilterWindow('>=;<=')}>Between... + + + + setFilter('TOMORROW')}>Tomorrow + setFilter('TODAY')}>Today + setFilter('YESTERDAY')}>Yesterday + + + + setFilter('NEXT WEEK')}>Next Week + setFilter('THIS WEEK')}>This Week + setFilter('LAST WEEK')}>Last Week + + + + setFilter('NEXT MONTH')}>Next Month + setFilter('THIS MONTH')}>This Month + setFilter('LAST MONTH')}>Last Month + + + + setFilter('NEXT YEAR')}>Next Year + setFilter('THIS YEAR')}>This Year + setFilter('LAST YEAR')}>Last Year + + + + {/* + setFilter('JAN')}>January + setFilter('FEB')}>February + setFilter('MAR')}>March + setFilter('APR')}>April + setFilter('JUN')}>June + setFilter('JUL')}>July + setFilter('AUG')}>August + setFilter('SEP')}>September + setFilter('OCT')}>October + setFilter('NOV')}>November + setFilter('DEC')}>December + + + + setFilter('MON')}>Monday + setFilter('TUE')}>Tuesday + setFilter('WED')}>Wednesday + setFilter('THU')}>Thursday + setFilter('FRI')}>Friday + setFilter('SAT')}>Saturday + setFilter('SUN')}>Sunday + */} + + ); + case 'string': + return ( + <> + setFilter('')}>Clear Filter + filterMultipleValues()}>Filter multiple values + + openFilterWindow('=')}>Equals... + openFilterWindow('<>')}>Does Not Equal... + setFilter('NULL')}>Is Null + setFilter('NOT NULL')}>Is Not Null + setFilter('EMPTY, NULL')}>Is Empty Or Null + setFilter('NOT EMPTY NOT NULL')}>Has Not Empty Value + + + + openFilterWindow('+')}>Contains... + openFilterWindow('~')}>Does Not Contain... + openFilterWindow('^')}>Begins With... + openFilterWindow('!^')}>Does Not Begin With... + openFilterWindow('$')}>Ends With... + openFilterWindow('!$')}>Does Not End With... + + ); + } +} + +export default function DataFilterControl({ isReadOnly = false, filterType, filter, setFilter }) { + const setFilterText = filter => { + setFilter(filter); + editorRef.current.value = filter || ''; + }; + const applyFilter = () => { + setFilter(editorRef.current.value); + }; + const filterMultipleValues = () => {}; + const openFilterWindow = operator => {}; + const buttonRef = React.createRef(); + const editorRef = React.createRef(); + + const handleKeyDown = ev => { + if (isReadOnly) return; + if (ev.keyCode == keycodes.enter) { + applyFilter(); + } + if (ev.keyCode == keycodes.escape) { + setFilterText(''); + } + // if (ev.keyCode == KeyCodes.DownArrow || ev.keyCode == KeyCodes.UpArrow) { + // if (this.props.onControlKey) this.props.onControlKey(ev.keyCode); + // } + }; + + const handleShowMenu = () => { + const rect = buttonRef.current.getBoundingClientRect(); + showMenu( + rect.left, + rect.bottom, + + ); + }; + + return ( + + + + + + + ); +} +// domEditor: Element; + +// @autobind +// applyFilter() { +// this.props.setFilter($(this.domEditor).val()); +// } + +// @autobind +// clearFilter() { +// $(this.domEditor).val(''); +// this.applyFilter(); +// } + +// setFilter(value: string) { +// $(this.domEditor).val(value); +// this.applyFilter(); +// return false; +// } + +// render() { +// let dropDownContent = null; + +// let filterIconSpan = ; +// //filterIconSpan = null; + +// if (this.props.filterType == 'Number') { +// dropDownContent = +// this.setFilter('')}>Clear Filter +// this.filterMultipleValues()}>Filter multiple values +// this.openFilterWindow('=')}>Equals... +// this.openFilterWindow('<>')}>Does Not Equal... +// this.setFilter('NULL')}>Is Null +// this.setFilter('NOT NULL')}>Is Not Null +// this.openFilterWindow('>')}>Greater Than... +// this.openFilterWindow('>=')}>Greater Than Or Equal To... +// this.openFilterWindow('<')}>Less Than... +// this.openFilterWindow('<=')}>Less Than Or Equal To... +// ; +// } + +// if (this.props.filterType == 'Logical') { +// dropDownContent = +// this.setFilter('')}>Clear Filter +// this.filterMultipleValues()}>Filter multiple values +// this.setFilter('NULL')}>Is Null +// this.setFilter('NOT NULL')}>Is Not Null +// this.setFilter('TRUE')}>Is True +// this.setFilter('FALSE')}>Is False +// this.setFilter('TRUE, NULL')}>Is True or NULL +// this.setFilter('FALSE, NULL')}>Is False or NULL +// ; +// } + +// if (this.props.filterType == 'DateTime') { +// dropDownContent = +// this.setFilter('')}>Clear Filter +// this.filterMultipleValues()}>Filter multiple values +// this.setFilter('NULL')}>Is Null +// this.setFilter('NOT NULL')}>Is Not Null + +// + +// this.openFilterWindow('<=')}>Before... +// this.openFilterWindow('>=')}>After... +// this.openFilterWindow('>=;<=')}>Between... + +// + +// this.setFilter('TOMORROW')}>Tomorrow +// this.setFilter('TODAY')}>Today +// this.setFilter('YESTERDAY')}>Yesterday + +// + +// this.setFilter('NEXT WEEK')}>Next Week +// this.setFilter('THIS WEEK')}>This Week +// this.setFilter('LAST WEEK')}>Last Week + +// + +// this.setFilter('NEXT MONTH')}>Next Month +// this.setFilter('THIS MONTH')}>This Month +// this.setFilter('LAST MONTH')}>Last Month + +// + +// this.setFilter('NEXT YEAR')}>Next Year +// this.setFilter('THIS YEAR')}>This Year +// this.setFilter('LAST YEAR')}>Last Year + +// + +// + +// this.setFilter('JAN')}>January +// this.setFilter('FEB')}>February +// this.setFilter('MAR')}>March +// this.setFilter('APR')}>April +// this.setFilter('JUN')}>June +// this.setFilter('JUL')}>July +// this.setFilter('AUG')}>August +// this.setFilter('SEP')}>September +// this.setFilter('OCT')}>October +// this.setFilter('NOV')}>November +// this.setFilter('DEC')}>December + +// + +// this.setFilter('MON')}>Monday +// this.setFilter('TUE')}>Tuesday +// this.setFilter('WED')}>Wednesday +// this.setFilter('THU')}>Thursday +// this.setFilter('FRI')}>Friday +// this.setFilter('SAT')}>Saturday +// this.setFilter('SUN')}>Sunday + +// +// ; +// } + +// if (this.props.filterType == 'String') { +// dropDownContent = +// this.setFilter('')}>Clear Filter +// this.filterMultipleValues()}>Filter multiple values + +// this.openFilterWindow('=')}>Equals... +// this.openFilterWindow('<>')}>Does Not Equal... +// this.setFilter('NULL')}>Is Null +// this.setFilter('NOT NULL')}>Is Not Null +// this.setFilter('EMPTY, NULL')}>Is Empty Or Null +// this.setFilter('NOT EMPTY NOT NULL')}>Has Not Empty Value + +// + +// this.openFilterWindow('+')}>Contains... +// this.openFilterWindow('~')}>Does Not Contain... +// this.openFilterWindow('^')}>Begins With... +// this.openFilterWindow('!^')}>Does Not Begin With... +// this.openFilterWindow('$')}>Ends With... +// this.openFilterWindow('!$')}>Does Not End With... +// ; +// } + +// if (this.props.isReadOnly) { +// dropDownContent = ; +// } + +// return
+// this.setDomEditor(x)} onKeyDown={this.editorKeyDown} placeholder='Search' > + +// {dropDownContent} +//
; +// } + +// async filterMultipleValues() { +// let result = await ModalDialog.run(); +// if (!result) return; +// let { mode, text } = result; +// let filter = createMultiLineFilter(mode, text); +// this.setFilter(filter); +// } + +// openFilterWindow(selectedOperator: string) { +// FilterDialog.runFilter(this, this.props.filterType, selectedOperator); +// return false; +// } + +// setDomEditor(editor) { +// this.domEditor = editor; +// $(editor).val(this.props.getFilter()); +// } + +// @autobind +// editorKeyDown(ev) { +// if (this.props.isReadOnly) return; +// if (ev.keyCode == KeyCodes.Enter) { +// this.applyFilter(); +// } +// if (ev.keyCode == KeyCodes.Escape) { +// this.clearFilter(); +// } +// if (ev.keyCode == KeyCodes.DownArrow || ev.keyCode == KeyCodes.UpArrow) { +// if (this.props.onControlKey) this.props.onControlKey(ev.keyCode); +// } +// } + +// focus() { +// $(this.domEditor).focus(); +// } +// } diff --git a/packages/web/src/datagrid/DataGridCore.js b/packages/web/src/datagrid/DataGridCore.js index 14969ae1..7e161489 100644 --- a/packages/web/src/datagrid/DataGridCore.js +++ b/packages/web/src/datagrid/DataGridCore.js @@ -7,6 +7,7 @@ import useDimensions from '../utility/useDimensions'; import { SeriesSizes } from './SeriesSizes'; import axios from '../utility/axios'; import ColumnLabel from './ColumnLabel'; +import DataFilterControl from './DataFilterControl'; const GridContainer = styled.div` position: absolute; @@ -40,10 +41,10 @@ const TableHeaderRow = styled.tr` const TableBodyRow = styled.tr` // height: 35px; background-color: #ffffff; - &:nth-child(6n + 4) { + &:nth-child(6n + 3) { background-color: #ebebeb; } - &:nth-child(6n + 7) { + &:nth-child(6n + 6) { background-color: #ebf5ff; } `; @@ -56,6 +57,12 @@ const TableHeaderCell = styled.td` background-color: #f6f7f9; overflow: hidden; `; +const TableFilterCell = styled.td` + text-align: left; + overflow: hidden; + margin: 0; + padding: 0; +`; const TableBodyCell = styled.td` font-weight: normal; border: 1px solid #c0c0c0; @@ -308,6 +315,20 @@ export default function DataGridCore(props) { ))} + + {realColumns.map(col => ( + + display.setFilter(col.uniqueName, value)} + /> + + ))} + {loadedRows diff --git a/packages/web/src/modals/DropDownMenu.js b/packages/web/src/modals/DropDownMenu.js index eb126981..b3651f2a 100644 --- a/packages/web/src/modals/DropDownMenu.js +++ b/packages/web/src/modals/DropDownMenu.js @@ -39,6 +39,12 @@ const StyledLink = styled.a` } `; +export const DropDownMenuDivider = styled.li` + margin: 9px 0px 9px 0px; + border-top: 1px solid #f2f2f2; + border-bottom: 1px solid #fff; +`; + export function DropDownMenuItem({ children, keyText = undefined, onClick }) { const handleMouseEnter = () => { // if (this.context.parentMenu) this.context.parentMenu.closeSubmenu(); @@ -90,7 +96,7 @@ export function DropDownMenuItem({ children, keyText = undefined, onClick }) { // // } // export function DropDownMenuDivider(props: {}) { -// return
  • ; +// return
  • ; // } // export class DropDownSubmenuItem extends React.Component { diff --git a/packages/web/src/utility/keycodes.js b/packages/web/src/utility/keycodes.js new file mode 100644 index 00000000..db92af3b --- /dev/null +++ b/packages/web/src/utility/keycodes.js @@ -0,0 +1,99 @@ +export default { + backspace: 8, + tab: 9, + enter: 13, + shift: 16, + ctrl: 17, + alt: 18, + pauseBreak: 19, + capsLock: 20, + escape: 27, + pageUp: 33, + pageDown: 34, + end: 35, + home: 36, + leftArrow: 37, + ppArrow: 38, + rightArrow: 39, + downArrow: 40, + insert: 45, + delete: 46, + n0: 48, + n1: 49, + n2: 50, + n3: 51, + n4: 52, + n5: 53, + n6: 54, + n7: 55, + n8: 56, + n9: 57, + a: 65, + b: 66, + c: 67, + d: 68, + e: 69, + f: 70, + g: 71, + h: 72, + i: 73, + j: 74, + k: 75, + l: 76, + m: 77, + n: 78, + o: 79, + p: 80, + q: 81, + r: 82, + s: 83, + t: 84, + u: 85, + v: 86, + w: 87, + x: 88, + y: 89, + z: 90, + leftWindowKey: 91, + rightWindowKey: 92, + selectKey: 93, + numPad0: 96, + numPad1: 97, + numPad2: 98, + numPad3: 99, + numPad4: 100, + numPad5: 101, + numPad6: 102, + numPad7: 103, + numPad8: 104, + numPad9: 105, + multiply: 106, + add: 107, + subtract: 109, + decimalPoint: 110, + divide: 111, + f1: 112, + f2: 113, + f3: 114, + f4: 115, + f5: 116, + f6: 117, + f7: 118, + f8: 119, + f9: 120, + f10: 121, + f12: 123, + numLock: 144, + scrollLock: 145, + semiColon: 186, + equalSign: 187, + comma: 188, + dash: 189, + period: 190, + forwardSlash: 191, + graveAccent: 192, + openBracket: 219, + backSlash: 220, + closeBracket: 221, + singleQuote: 222, +};