From f4ff4614b4829b34db113d180858f165661360b3 Mon Sep 17 00:00:00 2001 From: Ashish Baravaliya <vier.institute35@gmail.com> Date: Fri, 23 Feb 2024 13:15:39 -0500 Subject: [PATCH 1/6] feat: init commit - calculating stats + select area added --- .../BrowserCell/BrowserCell.react.js | 19 +++- src/components/BrowserCell/BrowserCell.scss | 62 ++++++++++- src/components/BrowserRow/BrowserRow.react.js | 2 + src/components/Toolbar/Toolbar.react.js | 103 +++++++++++++++++- src/components/Toolbar/Toolbar.scss | 38 +++++++ .../Data/Browser/BrowserTable.react.js | 6 + .../Data/Browser/BrowserToolbar.react.js | 3 + .../Data/Browser/DataBrowser.react.js | 86 ++++++++++++++- 8 files changed, 312 insertions(+), 7 deletions(-) diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index 52323ecaa5..3d81079497 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -536,6 +536,8 @@ export default class BrowserCell extends Component { onEditSelectedRow, isRequired, markRequiredFieldRow, + handleCellClick, + selectedCells, } = this.props; const classes = [...this.state.classes]; @@ -573,6 +575,21 @@ export default class BrowserCell extends Component { ); } + if (selectedCells.list.has(`${row}-${col}`)) { + if (selectedCells.rowStart === row) { + classes.push(styles.topBorder); + } + if (selectedCells.rowEnd === row) { + classes.push(styles.bottomBorder); + } + if (selectedCells.colStart === col) { + classes.push(styles.leftBorder); + } + if (selectedCells.colEnd === col) { + classes.push(styles.rightBorder); + } + } + return ( <span ref={this.cellRef} @@ -582,8 +599,8 @@ export default class BrowserCell extends Component { if (e.metaKey === true && type === 'Pointer') { onPointerCmdClick(value); } else { - onSelect({ row, col }); setCopyableValue(hidden ? undefined : this.copyableValue); + handleCellClick(e, row, col); } }} onDoubleClick={() => { diff --git a/src/components/BrowserCell/BrowserCell.scss b/src/components/BrowserCell/BrowserCell.scss index f2244498d8..e2eb438b73 100644 --- a/src/components/BrowserCell/BrowserCell.scss +++ b/src/components/BrowserCell/BrowserCell.scss @@ -36,7 +36,67 @@ } } -.hasMore{ +.leftBorder { + position: relative; + + &:after { + position: absolute; + pointer-events: none; + content: ''; + border-left: 2px solid #555572; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +} + +.rightBorder { + position: relative; + + &:after { + position: absolute; + pointer-events: none; + content: ''; + border-right: 2px solid #555572; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +} + +.topBorder { + position: relative; + + &:after { + position: absolute; + pointer-events: none; + content: ''; + border-top: 2px solid #555572; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +} + +.bottomBorder { + position: relative; + + &:after { + position: absolute; + pointer-events: none; + content: ''; + border-bottom: 2px solid #555572; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +} + +.hasMore { height: auto; max-height: 25px; overflow-y: scroll; diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js index dce01aeb45..6d3085a1b8 100644 --- a/src/components/BrowserRow/BrowserRow.react.js +++ b/src/components/BrowserRow/BrowserRow.react.js @@ -136,6 +136,8 @@ export default class BrowserRow extends Component { showNote={this.props.showNote} onRefresh={this.props.onRefresh} scripts={this.props.scripts} + handleCellClick={this.props.handleCellClick} + selectedCells={this.props.selectedCells} /> ); })} diff --git a/src/components/Toolbar/Toolbar.react.js b/src/components/Toolbar/Toolbar.react.js index d62d43441a..bf5082f618 100644 --- a/src/components/Toolbar/Toolbar.react.js +++ b/src/components/Toolbar/Toolbar.react.js @@ -6,11 +6,110 @@ * the root directory of this source tree. */ import PropTypes from 'lib/PropTypes'; -import React from 'react'; +import React, { useEffect } from 'react'; import Icon from 'components/Icon/Icon.react'; import styles from 'components/Toolbar/Toolbar.scss'; +import Popover from 'components/Popover/Popover.react'; +import Position from 'lib/Position'; import { useNavigate, useNavigationType, NavigationType } from 'react-router-dom'; +const POPOVER_CONTENT_ID = 'toolbarStatsPopover'; + +const Stats = ({ data }) => { + const [selected, setSelected] = React.useState(null); + const [open, setOpen] = React.useState(false); + const buttonRef = React.useRef(); + + const statsOptions = [ + { + type: 'sum', + label: 'Sum', + getValue: data => data.reduce((sum, value) => sum + value, 0), + }, + { + type: 'mean', + label: 'Mean', + getValue: data => data.reduce((sum, value) => sum + value, 0) / data.length, + }, + { + type: 'count', + label: 'Count', + getValue: data => data.length, + }, + { + type: 'p99', + label: 'P99', + getValue: data => { + const sorted = data.sort((a, b) => a - b); + return sorted[Math.floor(sorted.length * 0.99)]; + }, + }, + ]; + + const toggle = () => { + setOpen(!open); + }; + + const renderPopover = () => { + const node = buttonRef.current; + const position = Position.inDocument(node); + return ( + <Popover + fixed={true} + position={position} + onExternalClick={toggle} + contentId={POPOVER_CONTENT_ID} + > + <div id={POPOVER_CONTENT_ID}> + <div + onClick={toggle} + style={{ + cursor: 'pointer', + width: node.clientWidth, + height: node.clientHeight, + }} + ></div> + <div className={styles.stats_popover_container}> + {statsOptions.map(item => { + const itemStyle = [styles.stats_popover_item]; + if (item.type === selected?.type) { + itemStyle.push(styles.active); + } + return ( + <div + key={item.type} + className={itemStyle.join(' ')} + onClick={() => { + setSelected(item); + toggle(); + }} + > + <span>{item.label}</span> + </div> + ); + })} + </div> + </div> + </Popover> + ); + }; + + useEffect(() => { + setSelected(statsOptions[0]); + }, []); + + return ( + <> + {selected ? ( + <button ref={buttonRef} className={styles.stats} onClick={toggle}> + {`${selected.label}: ${selected.getValue(data)}`} + </button> + ) : null} + {open ? renderPopover() : null} + </> + ); +}; + const Toolbar = props => { const action = useNavigationType(); const navigate = useNavigate(); @@ -34,6 +133,7 @@ const Toolbar = props => { </div> </div> </div> + {props.selectedData.length ? <Stats data={props.selectedData} /> : null} <div className={styles.actions}>{props.children}</div> </div> ); @@ -44,6 +144,7 @@ Toolbar.propTypes = { subsection: PropTypes.string, details: PropTypes.string, relation: PropTypes.object, + selectedData: PropTypes.array, }; export default Toolbar; diff --git a/src/components/Toolbar/Toolbar.scss b/src/components/Toolbar/Toolbar.scss index d56f51068a..a802f43e3f 100644 --- a/src/components/Toolbar/Toolbar.scss +++ b/src/components/Toolbar/Toolbar.scss @@ -85,3 +85,41 @@ body:global(.expanded) { right: 14px; top: 4px; } + +.stats { + position: absolute; + right: 20px; + bottom: 10px; + background: $blue; + border-radius: 3px; + padding: 2px 4px; + font-size: 16px; + color: white; + box-shadow: none; + border: none; +} + +.stats_popover_container { + display: flex; + flex-direction: column; + background: $blue; + gap: 4px; + padding: 2px 0px; + font-size: 16px; + color: white; +} + +.stats_popover_item { + cursor: pointer; + padding: 0px 8px; + + &:hover { + color: $blue; + background-color: white; + } +} + +.active { + color: $blue; + background-color: white; +} diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js index 554b852195..27b1d97707 100644 --- a/src/dashboard/Data/Browser/BrowserTable.react.js +++ b/src/dashboard/Data/Browser/BrowserTable.react.js @@ -168,6 +168,8 @@ export default class BrowserTable extends React.Component { showNote={this.props.showNote} onRefresh={this.props.onRefresh} scripts={this.context.scripts} + selectedCells={this.props.selectedCells} + handleCellClick={this.props.handleCellClick} /> <Button value="Clone" @@ -236,6 +238,8 @@ export default class BrowserTable extends React.Component { showNote={this.props.showNote} onRefresh={this.props.onRefresh} scripts={this.context.scripts} + selectedCells={this.props.selectedCells} + handleCellClick={this.props.handleCellClick} /> <Button value="Add" @@ -312,6 +316,8 @@ export default class BrowserTable extends React.Component { showNote={this.props.showNote} onRefresh={this.props.onRefresh} scripts={this.context.scripts} + selectedCells={this.props.selectedCells} + handleCellClick={this.props.handleCellClick} /> ); } diff --git a/src/dashboard/Data/Browser/BrowserToolbar.react.js b/src/dashboard/Data/Browser/BrowserToolbar.react.js index 5155b94486..b697dd220a 100644 --- a/src/dashboard/Data/Browser/BrowserToolbar.react.js +++ b/src/dashboard/Data/Browser/BrowserToolbar.react.js @@ -72,6 +72,8 @@ const BrowserToolbar = ({ login, logout, toggleMasterKeyUsage, + + selectedData, }) => { const selectionLength = Object.keys(selection).length; const isPendingEditCloneRows = editCloneRows && editCloneRows.length > 0; @@ -238,6 +240,7 @@ const BrowserToolbar = ({ section={relation ? `Relation <${relation.targetClassName}>` : 'Class'} subsection={subsection} details={details.join(' \u2022 ')} + selectedData={selectedData} > {onAddRow && ( <a className={classes.join(' ')} onClick={onClick}> diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index e60b6fb23d..8671b15df1 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -34,6 +34,10 @@ export default class DataBrowser extends React.Component { editing: false, copyableValue: undefined, simplifiedSchema: this.getSimplifiedSchema(props.schema, props.className), + + selectedCells: { list: new Set(), rowStart: -1, rowEnd: -1, colStart: -1, colEnd: -1 }, + firstSelectedCell: null, + selectedData: [], }; this.handleKey = this.handleKey.bind(this); @@ -44,6 +48,7 @@ export default class DataBrowser extends React.Component { this.handleColumnsOrder = this.handleColumnsOrder.bind(this); this.setCopyableValue = this.setCopyableValue.bind(this); this.setContextMenu = this.setContextMenu.bind(this); + this.handleCellClick = this.handleCellClick.bind(this); this.saveOrderTimeout = null; } @@ -76,6 +81,11 @@ export default class DataBrowser extends React.Component { ); this.setState({ order }); } + this.setState({ + selectedCells: { list: new Set(), rowStart: -1, rowEnd: -1, colStart: -1, colEnd: -1 }, + firstSelectedCell: null, + selectedData: [], + }); } componentDidMount() { @@ -233,10 +243,10 @@ export default class DataBrowser extends React.Component { e.ctrlKey || e.metaKey ? lastVisibleColumnIndex : this.getNextVisibleColumnIndex( - 1, - firstVisibleColumnIndex, - lastVisibleColumnIndex - ), + 1, + firstVisibleColumnIndex, + lastVisibleColumnIndex + ), }, }); e.preventDefault(); @@ -317,6 +327,71 @@ export default class DataBrowser extends React.Component { }); } + handleCellClick(event, row, col) { + const { firstSelectedCell } = this.state; + const clickedCellKey = `${row}-${col}`; + + if (event.shiftKey && firstSelectedCell) { + const [firstRow, firstCol] = firstSelectedCell.split('-').map(Number); + const [lastRow, lastCol] = clickedCellKey.split('-').map(Number); + + const rowStart = Math.min(firstRow, lastRow); + const rowEnd = Math.max(firstRow, lastRow); + const colStart = Math.min(firstCol, lastCol); + const colEnd = Math.max(firstCol, lastCol); + + let validColumns = true; + for (let i = colStart; i <= colEnd; i++) { + const name = this.state.order[i].name; + if (this.props.columns[name].type !== 'Number') { + validColumns = false; + break; + } + } + + const newSelection = new Set(); + const selectedData = []; + for (let x = rowStart; x <= rowEnd; x++) { + let rowData = null; + if (validColumns) { + rowData = this.props.data[x]; + } + for (let y = colStart; y <= colEnd; y++) { + if (rowData) { + const value = rowData.attributes[this.state.order[y].name]; + if (typeof value === 'number' && !isNaN(value)) { + selectedData.push(rowData.attributes[this.state.order[y].name]); + } + } + newSelection.add(`${x}-${y}`); + } + } + + if (newSelection.size > 1) { + this.setCurrent(null); + this.setState({ + selectedCells: { + list: newSelection, + rowStart, + rowEnd, + colStart, + colEnd, + }, + selectedData, + }); + } else { + this.setCurrent({ row, col }); + } + } else { + this.setState({ + selectedCells: { list: new Set(), rowStart: -1, rowEnd: -1, colStart: -1, colEnd: -1 }, + selectedData: [], + current: { row, col }, + firstSelectedCell: clickedCellKey, + }); + } + } + render() { const { className, @@ -346,6 +421,8 @@ export default class DataBrowser extends React.Component { setContextMenu={this.setContextMenu} onFilterChange={this.props.onFilterChange} onFilterSave={this.props.onFilterSave} + selectedCells={this.state.selectedCells} + handleCellClick={this.handleCellClick} {...other} /> <BrowserToolbar @@ -370,6 +447,7 @@ export default class DataBrowser extends React.Component { editCloneRows={editCloneRows} onCancelPendingEditRows={onCancelPendingEditRows} order={this.state.order} + selectedData={this.state.selectedData} {...other} /> From a441886bc2509f21471175ccdf6237a3a8dfaf0d Mon Sep 17 00:00:00 2001 From: Ashish Baravaliya <vier.institute35@gmail.com> Date: Sun, 25 Feb 2024 18:54:42 -0500 Subject: [PATCH 2/6] feat: added bg color to highlighted cell --- src/components/BrowserCell/BrowserCell.react.js | 1 + src/components/BrowserCell/BrowserCell.scss | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index 3d81079497..09506e1cde 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -588,6 +588,7 @@ export default class BrowserCell extends Component { if (selectedCells.colEnd === col) { classes.push(styles.rightBorder); } + classes.push(styles.selected); } return ( diff --git a/src/components/BrowserCell/BrowserCell.scss b/src/components/BrowserCell/BrowserCell.scss index e2eb438b73..af7914205e 100644 --- a/src/components/BrowserCell/BrowserCell.scss +++ b/src/components/BrowserCell/BrowserCell.scss @@ -96,6 +96,10 @@ } } +.selected { + background-color: #e3effd; +} + .hasMore { height: auto; max-height: 25px; From e4dda63dd8a522a17c86e9bfd975e658214db33a Mon Sep 17 00:00:00 2001 From: Ashish Baravaliya <vier.institute35@gmail.com> Date: Mon, 26 Feb 2024 10:02:48 -0500 Subject: [PATCH 3/6] fix: linting issues --- src/components/BrowserCell/BrowserCell.react.js | 3 +-- src/dashboard/Data/Browser/DataBrowser.react.js | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index 09506e1cde..ec0880eadb 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -526,7 +526,6 @@ export default class BrowserCell extends Component { hidden, width, current, - onSelect, onEditChange, setCopyableValue, onPointerCmdClick, @@ -575,7 +574,7 @@ export default class BrowserCell extends Component { ); } - if (selectedCells.list.has(`${row}-${col}`)) { + if (selectedCells?.list.has(`${row}-${col}`)) { if (selectedCells.rowStart === row) { classes.push(styles.topBorder); } diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index 8671b15df1..99b090c891 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -214,10 +214,10 @@ export default class DataBrowser extends React.Component { e.ctrlKey || e.metaKey ? firstVisibleColumnIndex : this.getNextVisibleColumnIndex( - -1, - firstVisibleColumnIndex, - lastVisibleColumnIndex - ), + -1, + firstVisibleColumnIndex, + lastVisibleColumnIndex + ), }, }); e.preventDefault(); From 5c47fc5e5c51c71b9c01c3393b1d814ecb4a8cc1 Mon Sep 17 00:00:00 2001 From: Ashish Baravaliya <vier.institute35@gmail.com> Date: Mon, 26 Feb 2024 10:18:59 -0500 Subject: [PATCH 4/6] fix: linting issues --- src/dashboard/Data/Browser/DataBrowser.react.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index 99b090c891..ec1219481c 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -214,10 +214,10 @@ export default class DataBrowser extends React.Component { e.ctrlKey || e.metaKey ? firstVisibleColumnIndex : this.getNextVisibleColumnIndex( - -1, - firstVisibleColumnIndex, - lastVisibleColumnIndex - ), + -1, + firstVisibleColumnIndex, + lastVisibleColumnIndex + ), }, }); e.preventDefault(); @@ -243,10 +243,10 @@ export default class DataBrowser extends React.Component { e.ctrlKey || e.metaKey ? lastVisibleColumnIndex : this.getNextVisibleColumnIndex( - 1, - firstVisibleColumnIndex, - lastVisibleColumnIndex - ), + 1, + firstVisibleColumnIndex, + lastVisibleColumnIndex + ), }, }); e.preventDefault(); From 44ae36d7b022889f38e8218becc06003ef6a2ef8 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Mon, 26 Feb 2024 18:21:47 +0100 Subject: [PATCH 5/6] smaller font size Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- src/components/Toolbar/Toolbar.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Toolbar/Toolbar.scss b/src/components/Toolbar/Toolbar.scss index a802f43e3f..43a723e32b 100644 --- a/src/components/Toolbar/Toolbar.scss +++ b/src/components/Toolbar/Toolbar.scss @@ -93,7 +93,7 @@ body:global(.expanded) { background: $blue; border-radius: 3px; padding: 2px 4px; - font-size: 16px; + font-size: 14px; color: white; box-shadow: none; border: none; @@ -105,7 +105,7 @@ body:global(.expanded) { background: $blue; gap: 4px; padding: 2px 0px; - font-size: 16px; + font-size: 14px; color: white; } From bcdfebf7bc0bbac8b1fae349765f18bc66a9d6ec Mon Sep 17 00:00:00 2001 From: Ashish Baravaliya <vier.institute35@gmail.com> Date: Mon, 26 Feb 2024 14:09:49 -0500 Subject: [PATCH 6/6] fix: updated dropdown styles --- src/components/Toolbar/Toolbar.scss | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/Toolbar/Toolbar.scss b/src/components/Toolbar/Toolbar.scss index 43a723e32b..b5d41666e4 100644 --- a/src/components/Toolbar/Toolbar.scss +++ b/src/components/Toolbar/Toolbar.scss @@ -92,7 +92,7 @@ body:global(.expanded) { bottom: 10px; background: $blue; border-radius: 3px; - padding: 2px 4px; + padding: 2px 6px; font-size: 14px; color: white; box-shadow: none; @@ -103,7 +103,9 @@ body:global(.expanded) { display: flex; flex-direction: column; background: $blue; - gap: 4px; + border-radius: 3px; + margin-top: 1px; + gap: 2px; padding: 2px 0px; font-size: 14px; color: white; @@ -111,7 +113,7 @@ body:global(.expanded) { .stats_popover_item { cursor: pointer; - padding: 0px 8px; + padding: 0px 6px; &:hover { color: $blue;