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;