diff --git a/package-lock.json b/package-lock.json
index 0adda95eba..9b9eb97ba5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -39,9 +39,11 @@
"react-dnd": "10.0.2",
"react-dnd-html5-backend": "10.0.2",
"react-dom": "16.14.0",
+ "react-draggable": "4.4.6",
"react-helmet": "6.1.0",
"react-json-view": "1.21.3",
"react-popper-tooltip": "4.4.2",
+ "react-resizable": "3.0.5",
"react-router-dom": "6.4.1",
"regenerator-runtime": "0.13.11"
},
@@ -7732,6 +7734,15 @@
"mimic-response": "^1.0.0"
}
},
+ "node_modules/clsx": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
+ "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -20357,6 +20368,20 @@
"react": "^16.14.0"
}
},
+ "node_modules/react-draggable": {
+ "version": "4.4.6",
+ "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz",
+ "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^1.1.1",
+ "prop-types": "^15.8.1"
+ },
+ "peerDependencies": {
+ "react": ">= 16.3.0",
+ "react-dom": ">= 16.3.0"
+ }
+ },
"node_modules/react-fast-compare": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
@@ -20511,6 +20536,19 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
+ "node_modules/react-resizable": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz",
+ "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==",
+ "license": "MIT",
+ "dependencies": {
+ "prop-types": "15.x",
+ "react-draggable": "^4.0.3"
+ },
+ "peerDependencies": {
+ "react": ">= 16.3"
+ }
+ },
"node_modules/react-router": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.1.tgz",
diff --git a/package.json b/package.json
index a432d38f66..7ba2c0b012 100644
--- a/package.json
+++ b/package.json
@@ -65,9 +65,11 @@
"react-dnd": "10.0.2",
"react-dnd-html5-backend": "10.0.2",
"react-dom": "16.14.0",
+ "react-draggable": "4.4.6",
"react-helmet": "6.1.0",
"react-json-view": "1.21.3",
"react-popper-tooltip": "4.4.2",
+ "react-resizable": "3.0.5",
"react-router-dom": "6.4.1",
"regenerator-runtime": "0.13.11"
},
diff --git a/src/components/AggregationPanel/AggregationPanel.js b/src/components/AggregationPanel/AggregationPanel.js
new file mode 100644
index 0000000000..5cd5a0440b
--- /dev/null
+++ b/src/components/AggregationPanel/AggregationPanel.js
@@ -0,0 +1,84 @@
+import LoaderDots from 'components/LoaderDots/LoaderDots.react';
+import React, { useEffect, useMemo } from 'react';
+import styles from './AggregationPanel.scss';
+import {
+ AudioElement,
+ ButtonElement,
+ ImageElement,
+ KeyValueElement,
+ TableElement,
+ TextElement,
+ VideoElement,
+} from './AggregationPanelComponents';
+
+const AggregationPanel = ({
+ data,
+ isLoadingCloudFunction,
+ showAggregatedData,
+ setErrorAggregatedData,
+ errorAggregatedData,
+ showNote,
+ setSelectedObjectId,
+ selectedObjectId
+}) => {
+
+ useEffect(() => {
+ if (Object.keys(errorAggregatedData).length !== 0) {
+ setSelectedObjectId(null);
+ setErrorAggregatedData({});
+ }
+ }, [errorAggregatedData, setSelectedObjectId, setErrorAggregatedData]);
+
+ const isLoading = useMemo(() =>
+ selectedObjectId && isLoadingCloudFunction && showAggregatedData,
+ [selectedObjectId, isLoadingCloudFunction, showAggregatedData]
+ );
+
+ const shouldShowAggregatedData = useMemo(() =>
+ selectedObjectId && showAggregatedData && Object.keys(data).length !== 0 && Object.keys(errorAggregatedData).length === 0, [selectedObjectId, showAggregatedData, data, errorAggregatedData]
+ );
+
+ return (
+ <>
+ {isLoading ? (
+
+
+
+ ) : shouldShowAggregatedData ? (
+ data.panel.segments.map((segment, index) => (
+
+
{segment.title}
+
+ {segment.items.map((item, idx) => {
+ switch (item.type) {
+ case 'text':
+ return
;
+ case 'keyValue':
+ return
;
+ case 'table':
+ return
;
+ case 'image':
+ return
;
+ case 'video':
+ return
;
+ case 'audio':
+ return
;
+ case 'button':
+ return
;
+ default:
+ return null;
+ }
+ })}
+
+
+ ))
+ ) : (
+
+ No object selected.
+
+ )}
+ >
+ );
+};
+
+export default AggregationPanel;
diff --git a/src/components/AggregationPanel/AggregationPanel.scss b/src/components/AggregationPanel/AggregationPanel.scss
new file mode 100644
index 0000000000..2bfda14147
--- /dev/null
+++ b/src/components/AggregationPanel/AggregationPanel.scss
@@ -0,0 +1,99 @@
+@import 'stylesheets/globals.scss';
+
+.heading {
+ font-size: 14px;
+ margin-top: 0;
+ padding: 8px;
+ padding-left: 10px;
+ background-color: $blue;
+ color: $white;
+}
+
+.segmentItems {
+ font-size: 14px;
+ padding-left: 10px;
+ padding-right: 10px;
+ padding-top: 6px;
+ display: flex;
+ flex-direction: column;
+ border-left: 1px solid #e3e3ea;
+ gap: 10px;
+}
+
+.keyValue {
+ font-size: 14px;
+ display: flex;
+ gap: 10px;
+}
+
+.video {
+ width: 100%;
+ height: 100%;
+}
+
+.image {
+ width: 100%;
+ height: 100%;
+}
+
+.audio {
+ width: 100%;
+}
+
+.segmentItems table,
+.segmentItems th,
+.segmentItems td {
+ font-size: 14px;
+ border: 1px solid #ddd;
+}
+
+.segmentItems th,
+.segmentItems td {
+ padding: 4px;
+ text-align: left;
+}
+
+.buttonContainer {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.button {
+ width: auto;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ cursor: pointer;
+ margin-bottom: 15px;
+ background-color: $blue;
+ padding: 3px 13px;
+ border: none;
+ color: $white;
+ line-height: 28px;
+ outline: 0;
+ text-decoration: none;
+ text-align: center;
+ border-radius: 5px;
+ font-size: 14px;
+ &:hover,
+ &:focus {
+ background-color: $darkBlue;
+ }
+}
+
+.loading{
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ text-align: center;
+}
+
+.center {
+ position: absolute;
+ text-align: center;
+ top: 50%;
+ left: 50%;
+ @include transform(translate(-50%, -50%));
+ }
\ No newline at end of file
diff --git a/src/components/AggregationPanel/AggregationPanelComponents.js b/src/components/AggregationPanel/AggregationPanelComponents.js
new file mode 100644
index 0000000000..2f876c17d0
--- /dev/null
+++ b/src/components/AggregationPanel/AggregationPanelComponents.js
@@ -0,0 +1,97 @@
+import React from 'react';
+import styles from './AggregationPanel.scss';
+
+// Text Element Component
+export const TextElement = ({ text }) => (
+
+);
+
+// Key-Value Element Component
+export const KeyValueElement = ({ item }) => (
+
+);
+
+// Table Element Component
+export const TableElement = ({ columns, rows }) => (
+
+
+
+
+ {columns.map((column, idx) => (
+ {column.name} |
+ ))}
+
+
+
+ {rows.map((row, idx) => (
+
+ {columns.map((column, colIdx) => (
+ {row[column.name]} |
+ ))}
+
+ ))}
+
+
+
+);
+
+// Image Element Component
+export const ImageElement = ({ url }) => (
+
+);
+
+// Video Element Component
+export const VideoElement = ({ url }) => (
+
+
+
+);
+
+// Audio Element Component
+export const AudioElement = ({ url }) => (
+
+
+
+);
+
+// Button Element Component
+export const ButtonElement = ({ item, showNote }) => {
+ const handleClick = () => {
+ fetch(item.action.url, {
+ method: item.action.method,
+ headers: item.action.headers,
+ body: JSON.stringify(item.action.body),
+ })
+ .then(response => response.json())
+ .then(data => {
+ const formattedData = JSON.stringify(data, null, 2);
+ showNote(`${formattedData}`,false)
+ })
+ .catch(error => {
+ showNote(`${error}`,true)
+ });
+ };
+
+ return (
+
+
+
+ );
+};
diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js
index 52cccf903b..74614feb69 100644
--- a/src/components/BrowserCell/BrowserCell.react.js
+++ b/src/components/BrowserCell/BrowserCell.react.js
@@ -280,6 +280,7 @@ export default class BrowserCell extends Component {
//#region Cell Context Menu related methods
onContextMenu(event) {
+ this.props.setErrorAggregatedData({});
if (event.type !== 'contextmenu') {
return;
}
@@ -289,6 +290,13 @@ export default class BrowserCell extends Component {
onSelect({ row, col });
setCopyableValue(hidden ? undefined : this.copyableValue);
+ if (this.props.selectedObjectId !== this.props.objectId) {
+ this.props.setShowAggregatedData(true);
+ this.props.setSelectedObjectId(this.props.objectId);
+ if (this.props.isPanelVisible) {
+ this.props.callCloudFunction(this.props.objectId, this.props.className);
+ }
+ }
const available = Filters.availableFilters(
this.props.simplifiedSchema,
@@ -535,7 +543,7 @@ export default class BrowserCell extends Component {
field,
constraint,
compareTo,
- class: className
+ class: className,
})
)
);
@@ -556,6 +564,10 @@ export default class BrowserCell extends Component {
current,
onEditChange,
setCopyableValue,
+ selectedObjectId,
+ setSelectedObjectId,
+ callCloudFunction,
+ isPanelVisible,
onPointerCmdClick,
row,
col,
@@ -565,6 +577,7 @@ export default class BrowserCell extends Component {
markRequiredFieldRow,
handleCellClick,
selectedCells,
+ setShowAggregatedData
} = this.props;
const classes = [...this.state.classes];
@@ -628,6 +641,17 @@ export default class BrowserCell extends Component {
onPointerCmdClick(value);
} else {
setCopyableValue(hidden ? undefined : this.copyableValue);
+ if (selectedObjectId !== this.props.objectId) {
+ setShowAggregatedData(true);
+ setSelectedObjectId(this.props.objectId);
+ if (
+ this.props.objectId &&
+ isPanelVisible &&
+ ((e.shiftKey && !this.props.firstSelectedCell) || !e.shiftKey)
+ ) {
+ callCloudFunction(this.props.objectId, this.props.className);
+ }
+ }
handleCellClick(e, row, col);
}
}}
diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js
index c2054942b2..0e6f40a694 100644
--- a/src/components/BrowserRow/BrowserRow.react.js
+++ b/src/components/BrowserRow/BrowserRow.react.js
@@ -36,6 +36,10 @@ export default class BrowserRow extends Component {
selection,
selectRow,
setCopyableValue,
+ selectedObjectId,
+ setSelectedObjectId,
+ callCloudFunction,
+ isPanelVisible,
setCurrent,
setEditing,
setRelation,
@@ -141,6 +145,10 @@ export default class BrowserRow extends Component {
isRequired={isRequired}
markRequiredFieldRow={markRequiredFieldRow}
setCopyableValue={setCopyableValue}
+ selectedObjectId={selectedObjectId}
+ setSelectedObjectId={setSelectedObjectId}
+ isPanelVisible={isPanelVisible}
+ callCloudFunction={callCloudFunction}
setContextMenu={setContextMenu}
onEditSelectedRow={onEditSelectedRow}
showNote={this.props.showNote}
@@ -148,6 +156,9 @@ export default class BrowserRow extends Component {
scripts={this.props.scripts}
handleCellClick={this.props.handleCellClick}
selectedCells={this.props.selectedCells}
+ setShowAggregatedData={this.props.setShowAggregatedData}
+ setErrorAggregatedData={this.props.setErrorAggregatedData}
+ firstSelectedCell={this.props.firstSelectedCell}
/>
);
})}
diff --git a/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js b/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js
index e714759376..1287e62ae5 100644
--- a/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js
+++ b/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js
@@ -24,6 +24,8 @@ export default class DataBrowserHeaderBar extends React.Component {
preventSchemaEdits,
selected,
isDataLoaded,
+ setSelectedObjectId,
+ setCurrent
} = this.props;
const elements = [
@@ -48,7 +50,11 @@ export default class DataBrowserHeaderBar extends React.Component {
!preventSort &&
(type === 'String' || type === 'Number' || type === 'Date' || type === 'Boolean')
) {
- onClick = () => updateOrdering((order === 'descending' ? '' : '-') + name);
+ onClick = () =>{
+ updateOrdering((order === 'descending' ? '' : '-') + name);
+ setSelectedObjectId(null);
+ setCurrent(null)
+ }
}
let className = styles.wrap;
@@ -76,7 +82,9 @@ export default class DataBrowserHeaderBar extends React.Component {
if (onAddColumn) {
const finalStyle = {};
if (headers.length % 2) {
- finalStyle.background = 'rgba(224,224,234,0.10)';
+ finalStyle.background = '#726F85';
+ } else{
+ finalStyle.background = '#66637A';
}
elements.push(
diff --git a/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.scss b/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.scss
index df45061d13..ba42c5694e 100644
--- a/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.scss
+++ b/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.scss
@@ -11,7 +11,7 @@
position: absolute;
top: 0;
left: 0;
- height: 30px;
+ height: 0;
background: #66637a;
white-space: nowrap;
display: inline-block;
@@ -52,7 +52,7 @@
vertical-align: top;
text-align: center;
width: 30px;
- background: rgba(224,224,234,0.10);
+ background: rgb(114, 111, 133)
}
.handle {
diff --git a/src/components/Toolbar/Toolbar.react.js b/src/components/Toolbar/Toolbar.react.js
index 5be4513fac..aad773e702 100644
--- a/src/components/Toolbar/Toolbar.react.js
+++ b/src/components/Toolbar/Toolbar.react.js
@@ -15,7 +15,7 @@ import { useNavigate, useNavigationType, NavigationType } from 'react-router-dom
const POPOVER_CONTENT_ID = 'toolbarStatsPopover';
-const Stats = ({ data }) => {
+const Stats = ({ data, classwiseCloudFunctions, className }) => {
const [selected, setSelected] = React.useState(null);
const [open, setOpen] = React.useState(false);
const buttonRef = React.useRef();
@@ -98,10 +98,17 @@ const Stats = ({ data }) => {
setSelected(statsOptions[0]);
}, []);
+ const rightMarginStyle = classwiseCloudFunctions && classwiseCloudFunctions[className] ? '120px' : 'initial';
+
return (
<>
{selected ? (
-
- {props?.selectedData?.length ? : null}
+ {props?.selectedData?.length ? : null}
{props.children}
+ {props.classwiseCloudFunctions && props.classwiseCloudFunctions[props.className] && (
+
+ {props.isPanelVisible ? (
+ <>
+
+ Hide Panel
+ >
+ ) : (
+ <>
+
+ Show Panel
+ >
+ )}
+
+ )}
);
};
diff --git a/src/components/Toolbar/Toolbar.scss b/src/components/Toolbar/Toolbar.scss
index b5d41666e4..88f7261171 100644
--- a/src/components/Toolbar/Toolbar.scss
+++ b/src/components/Toolbar/Toolbar.scss
@@ -125,3 +125,31 @@ body:global(.expanded) {
color: $blue;
background-color: white;
}
+
+.btn {
+ position: absolute;
+ right: 5px;
+ bottom: 5px;
+ border: none;
+ padding: 6px 3px;
+ width: 110px;
+ background: none;
+ color: white;
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ cursor: pointer;
+
+ svg {
+
+ &:hover {
+ fill: #ffffff;
+ }
+ }
+
+ &:hover {
+ svg {
+ fill: #ffffff;
+ }
+ }
+}
diff --git a/src/dashboard/Dashboard.js b/src/dashboard/Dashboard.js
index e30a1824f8..0aebec1f25 100644
--- a/src/dashboard/Dashboard.js
+++ b/src/dashboard/Dashboard.js
@@ -52,7 +52,7 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import Playground from './Data/Playground/Playground.react';
import DashboardSettings from './Settings/DashboardSettings/DashboardSettings.react';
-import Security from './Settings/Security/Security.react';
+import Security from './Settings/Security/Security.react';
const ShowSchemaOverview = false; //In progress features. Change false to true to work on this feature.
@@ -121,6 +121,7 @@ export default class Dashboard extends React.Component {
get('/parse-dashboard-config.json')
.then(({ apps, newFeaturesInLatestVersion = [] }) => {
this.setState({ newFeaturesInLatestVersion });
+
const appInfoPromises = apps.map(app => {
if (app.serverURL.startsWith('https://api.parse.com/1')) {
//api.parse.com doesn't have feature availability endpoint, fortunately we know which features
@@ -179,6 +180,7 @@ export default class Dashboard extends React.Component {
configLoadingState: AsyncStatus.FAILED,
});
});
+
}
render() {
diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js
index 188c6f46a6..9f97022618 100644
--- a/src/dashboard/Data/Browser/Browser.react.js
+++ b/src/dashboard/Data/Browser/Browser.react.js
@@ -40,6 +40,8 @@ import * as ClassPreferences from 'lib/ClassPreferences';
import { Helmet } from 'react-helmet';
import generatePath from 'lib/generatePath';
import { withRouter } from 'lib/withRouter';
+import { get } from 'lib/AJAX';
+import { setBasePath } from 'lib/AJAX';
// The initial and max amount of rows fetched by lazy loading
const MAX_ROWS_FETCHED = 200;
@@ -103,7 +105,12 @@ class Browser extends DashboardView {
draggedRowSelection: false,
classes: {},
- allClassesSchema: {}
+ allClassesSchema: {},
+ configData: {},
+ classwiseCloudFunctions: {},
+ AggregationPanelData: {},
+ isLoading: false,
+ errorAggregatedData: {},
};
this.addLocation = this.addLocation.bind(this);
@@ -121,6 +128,8 @@ class Browser extends DashboardView {
this.showExport = this.showExport.bind(this);
this.login = this.login.bind(this);
this.logout = this.logout.bind(this);
+ this.setLoading = this.setLoading.bind(this);
+ this.setErrorAggregatedData = this.setErrorAggregatedData.bind(this);
this.toggleMasterKeyUsage = this.toggleMasterKeyUsage.bind(this);
this.showAttachRowsDialog = this.showAttachRowsDialog.bind(this);
this.cancelAttachRows = this.cancelAttachRows.bind(this);
@@ -173,6 +182,9 @@ class Browser extends DashboardView {
this.onMouseDownRowCheckBox = this.onMouseDownRowCheckBox.bind(this);
this.onMouseUpRowCheckBox = this.onMouseUpRowCheckBox.bind(this);
this.onMouseOverRowCheckBox = this.onMouseOverRowCheckBox.bind(this);
+ this.classAndCloudFuntionMap = this.classAndCloudFuntionMap.bind(this);
+ this.fetchAggregationPanelData = this.fetchAggregationPanelData.bind(this);
+ this.setAggregationPanelData = this.setAggregationPanelData.bind(this);
this.dataBrowserRef = React.createRef();
@@ -200,6 +212,11 @@ class Browser extends DashboardView {
componentDidMount() {
this.addLocation(this.props.params.appId);
window.addEventListener('mouseup', this.onMouseUpRowCheckBox);
+ setBasePath('/');
+ get('/parse-dashboard-config.json').then(data => {
+ this.setState({ configData: data });
+ this.classAndCloudFuntionMap(this.state.configData);
+ });
}
componentWillUnmount() {
@@ -229,11 +246,60 @@ class Browser extends DashboardView {
if (!nextProps.params.className && nextProps.schema.data.get('classes')) {
const t = nextProps.schema.data.get('classes');
this.classes = Object.keys(t.toObject());
- this.allClassesSchema = this.getAllClassesSchema(this.classes ,nextProps.schema.data.get('classes'));
+ this.allClassesSchema = this.getAllClassesSchema(
+ this.classes,
+ nextProps.schema.data.get('classes')
+ );
this.redirectToFirstClass(nextProps.schema.data.get('classes'), nextContext);
}
}
+ setLoading(bool) {
+ this.setState({
+ isLoading: bool,
+ });
+ }
+
+ setErrorAggregatedData(data) {
+ this.setState({
+ errorAggregatedData: data,
+ });
+ }
+
+ fetchAggregationPanelData(objectId, className) {
+ this.setState({
+ isLoading: true,
+ });
+ const params = {
+ objectId: objectId,
+ };
+ const cloudCodeFunction = this.state.classwiseCloudFunctions[className][0].cloudCodeFunction;
+
+ Parse.Cloud.run(cloudCodeFunction, params).then(
+ result => {
+ if (result && result.panel && result.panel && result.panel.segments) {
+ this.setState({ AggregationPanelData: result, isLoading: false });
+ } else {
+ this.setState({
+ isLoading: false,
+ errorAggregatedData: 'Improper JSON format',
+ });
+ this.showNote(this.state.errorAggregatedData,true)
+ }
+ },
+ error => {
+ this.setState({
+ isLoading: false,
+ errorAggregatedData: error.message,
+ });
+ this.showNote(this.state.errorAggregatedData,true)
+ }
+ );
+ }
+
+ setAggregationPanelData(data) {
+ this.setState({ AggregationPanelData: data });
+ }
addLocation(appId) {
if (window.localStorage) {
let pathname = null;
@@ -259,6 +325,26 @@ class Browser extends DashboardView {
}
}
+ classAndCloudFuntionMap(data) {
+ const classMap = {};
+ data.apps.forEach(app => {
+ app.infoPanel.forEach(panel => {
+ panel.classes.forEach(className => {
+ if (!classMap[className]) {
+ classMap[className] = [];
+ }
+ classMap[className].push({
+ title: panel.title,
+ cloudCodeFunction: panel.cloudCodeFunction,
+ classes: panel.classes,
+ });
+ });
+ });
+ });
+
+ this.setState({ classwiseCloudFunctions: classMap });
+ }
+
removeLocation() {
if (window.localStorage) {
const lastLocation = {
@@ -763,9 +849,9 @@ class Browser extends DashboardView {
}
}
- getAllClassesSchema(allClasses , allClassesData) {
+ getAllClassesSchema(allClasses, allClassesData) {
const schemaSimplifiedData = {};
- allClasses.forEach((className) => {
+ allClasses.forEach(className => {
const classSchema = allClassesData.get(className);
if (classSchema) {
schemaSimplifiedData[className] = {};
@@ -1008,6 +1094,9 @@ class Browser extends DashboardView {
{
ordering: ordering,
selection: {},
+ errorAggregatedData: {},
+ isLoading: false,
+ AggregationPanelData: {},
},
() => this.fetchData(source, this.state.filters)
);
@@ -1277,7 +1366,7 @@ class Browser extends DashboardView {
if (error.code === Parse.Error.AGGREGATE_ERROR) {
if (error.errors.length == 1) {
errorDeletingNote =
- 'Error deleting ' + className + ' with id \'' + error.errors[0].object.id + '\'';
+ 'Error deleting ' + className + ' with id \'' + error.errors[0].object.id + '\'';
} else if (error.errors.length < toDeleteObjectIds.length) {
errorDeletingNote =
'Error deleting ' +
@@ -1834,10 +1923,11 @@ class Browser extends DashboardView {
}
onMouseUpRowCheckBox() {
- this.state.rowCheckboxDragging && this.setState({
- rowCheckboxDragging: false,
- draggedRowSelection: false,
- });
+ this.state.rowCheckboxDragging &&
+ this.setState({
+ rowCheckboxDragging: false,
+ draggedRowSelection: false,
+ });
}
onMouseOverRowCheckBox(id) {
@@ -1968,6 +2058,14 @@ class Browser extends DashboardView {
onMouseUpRowCheckBox={this.onMouseUpRowCheckBox}
onMouseOverRowCheckBox={this.onMouseOverRowCheckBox}
classes={this.classes}
+ classwiseCloudFunctions={this.state.classwiseCloudFunctions}
+ callCloudFunction={this.fetchAggregationPanelData}
+ isLoadingCloudFunction={this.state.isLoading}
+ setLoading={this.setLoading}
+ AggregationPanelData={this.state.AggregationPanelData}
+ setAggregationPanelData={this.setAggregationPanelData}
+ setErrorAggregatedData={this.setErrorAggregatedData}
+ errorAggregatedData={this.state.errorAggregatedData}
/>
);
}
diff --git a/src/dashboard/Data/Browser/Browser.scss b/src/dashboard/Data/Browser/Browser.scss
index ba6b92c463..97948cb9a0 100644
--- a/src/dashboard/Data/Browser/Browser.scss
+++ b/src/dashboard/Data/Browser/Browser.scss
@@ -82,9 +82,7 @@ body:global(.expanded) {
top: 30px;
bottom: 0;
left: 0;
- min-width: 100%;
- overflow-y: auto;
- overflow-x: hidden;
+ width: 100%;
}
.table .empty {
@@ -264,4 +262,13 @@ body:global(.expanded) {
position: relative;
}
}
-}
\ No newline at end of file
+}
+
+.dataContainer {
+ height: 100%;
+ overflow: auto;
+}
+
+.noScroll {
+ overflow-x: hidden;
+}
\ No newline at end of file
diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js
index 6bf22627fc..59dc53d5da 100644
--- a/src/dashboard/Data/Browser/BrowserTable.react.js
+++ b/src/dashboard/Data/Browser/BrowserTable.react.js
@@ -30,9 +30,14 @@ export default class BrowserTable extends React.Component {
this.state = {
offset: 0,
+ panelWidth: 300,
+ isResizing: false,
+ maxWidth: window.innerWidth - 300,
};
this.handleScroll = this.handleScroll.bind(this);
this.tableRef = React.createRef();
+ this.handleResize = this.handleResize.bind(this);
+ this.updateMaxWidth = this.updateMaxWidth.bind(this);
}
componentWillReceiveProps(props) {
@@ -56,10 +61,36 @@ export default class BrowserTable extends React.Component {
componentDidMount() {
this.tableRef.current.addEventListener('scroll', this.handleScroll);
+ window.addEventListener('resize', this.updateMaxWidth);
}
componentWillUnmount() {
this.tableRef.current.removeEventListener('scroll', this.handleScroll);
+ window.removeEventListener('resize', this.updateMaxWidth);
+ }
+
+ handleResize(event, { size }) {
+ this.setState({ panelWidth: size.width });
+ }
+
+ handleMouseDown() {
+ this.setState({ isResizing: true });
+ document.body.style.cursor = 'ew-resize';
+ }
+
+ handleMouseMove(e) {
+ if (!this.state.isResizing) {
+ return;
+ }
+ this.setState({ panelWidth: e.clientX });
+ }
+
+ handleMouseUp() {
+ if (!this.state.isResizing) {
+ return;
+ }
+ this.setState({ isResizing: false });
+ document.body.style.cursor = 'default';
}
handleScroll() {
@@ -92,6 +123,12 @@ export default class BrowserTable extends React.Component {
}
});
}
+ updateMaxWidth = () => {
+ this.setState({ maxWidth: window.innerWidth - 300 });
+ if (this.state.panelWidth > window.innerWidth - 300) {
+ this.setState({ panelWidth: window.innerWidth - 300 });
+ }
+ };
render() {
let ordering = {};
@@ -163,6 +200,10 @@ export default class BrowserTable extends React.Component {
setEditing={this.props.setEditing}
setRelation={this.props.setRelation}
setCopyableValue={this.props.setCopyableValue}
+ selectedObjectId={this.props.selectedObjectId}
+ setSelectedObjectId={this.props.setSelectedObjectId}
+ callCloudFunction={this.props.callCloudFunction}
+ isPanelVisible={this.props.isPanelVisible}
setContextMenu={this.props.setContextMenu}
onEditSelectedRow={this.props.onEditSelectedRow}
markRequiredFieldRow={this.props.markRequiredFieldRow}
@@ -174,6 +215,9 @@ export default class BrowserTable extends React.Component {
onMouseDownRowCheckBox={this.props.onMouseDownRowCheckBox}
onMouseUpRowCheckBox={this.props.onMouseUpRowCheckBox}
onMouseOverRowCheckBox={this.props.onMouseOverRowCheckBox}
+ setShowAggregatedData={this.props.setShowAggregatedData}
+ setErrorAggregatedData={this.props.setErrorAggregatedData}
+ firstSelectedCell={this.props.firstSelectedCell}
/>
);
}
@@ -505,29 +563,41 @@ export default class BrowserTable extends React.Component {
);
}
}
+ const rightValue =
+ this.props.panelWidth && this.props.isPanelVisible ? `${this.props.panelWidth}px` : '0px';
return (
-
- {table}
-
checked).length ===
- this.props.data.length
- }
- selectAll={checked =>
- this.props.data.forEach(({ id }) => this.props.selectRow(id, checked))
- }
- headers={headers}
- updateOrdering={this.props.updateOrdering}
- readonly={!!this.props.relation || !!this.props.isUnique}
- handleDragDrop={this.props.handleHeaderDragDrop}
- onResize={this.props.handleResize}
- onAddColumn={this.props.onAddColumn}
- preventSchemaEdits={this.context.preventSchemaEdits}
- isDataLoaded={!!this.props.data}
- />
+
+
+ checked).length ===
+ this.props.data.length
+ }
+ selectAll={checked =>
+ this.props.data.forEach(({ id }) => this.props.selectRow(id, checked))
+ }
+ headers={headers}
+ updateOrdering={this.props.updateOrdering}
+ readonly={!!this.props.relation || !!this.props.isUnique}
+ handleDragDrop={this.props.handleHeaderDragDrop}
+ onResize={this.props.handleResize}
+ onAddColumn={this.props.onAddColumn}
+ preventSchemaEdits={this.context.preventSchemaEdits}
+ isDataLoaded={!!this.props.data}
+ setSelectedObjectId={this.props.setSelectedObjectId}
+ setCurrent={this.props.setCurrent}
+ />
+ {table}
+
);
}
diff --git a/src/dashboard/Data/Browser/BrowserToolbar.react.js b/src/dashboard/Data/Browser/BrowserToolbar.react.js
index d2f08c837b..b8e44cc038 100644
--- a/src/dashboard/Data/Browser/BrowserToolbar.react.js
+++ b/src/dashboard/Data/Browser/BrowserToolbar.react.js
@@ -76,6 +76,10 @@ const BrowserToolbar = ({
selectedData,
allClasses,
allClassesSchema,
+
+ togglePanel,
+ isPanelVisible,
+ classwiseCloudFunctions
}) => {
const selectionLength = Object.keys(selection).length;
const isPendingEditCloneRows = editCloneRows && editCloneRows.length > 0;
@@ -262,12 +266,16 @@ const BrowserToolbar = ({
const showLogin = () => loginDialogRef.current.handleOpen();
return (
` : 'Class'}
subsection={subsection}
details={details.join(' \u2022 ')}
selectedData={selectedData}
+ togglePanel={togglePanel}
+ isPanelVisible={isPanelVisible}
+ classwiseCloudFunctions={classwiseCloudFunctions}
>
{onAddRow && (
diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js
index 1df0b97607..90a5dd8347 100644
--- a/src/dashboard/Data/Browser/DataBrowser.react.js
+++ b/src/dashboard/Data/Browser/DataBrowser.react.js
@@ -5,12 +5,16 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
+import ContextMenu from 'components/ContextMenu/ContextMenu.react';
import copy from 'copy-to-clipboard';
import BrowserTable from 'dashboard/Data/Browser/BrowserTable.react';
import BrowserToolbar from 'dashboard/Data/Browser/BrowserToolbar.react';
-import ContextMenu from 'components/ContextMenu/ContextMenu.react';
import * as ColumnPreferences from 'lib/ColumnPreferences';
import React from 'react';
+import { ResizableBox } from 'react-resizable';
+import styles from './Databrowser.scss';
+
+import AggregationPanel from '../../../components/AggregationPanel/AggregationPanel';
/**
* DataBrowser renders the browser toolbar and data table
@@ -33,24 +37,36 @@ export default class DataBrowser extends React.Component {
current: null,
editing: false,
copyableValue: undefined,
+ selectedObjectId: undefined,
simplifiedSchema: this.getSimplifiedSchema(props.schema, props.className),
- allClassesSchema: this.getAllClassesSchema(props.schema,props.classes),
-
+ allClassesSchema: this.getAllClassesSchema(props.schema, props.classes),
+ isPanelVisible: false,
selectedCells: { list: new Set(), rowStart: -1, rowEnd: -1, colStart: -1, colEnd: -1 },
firstSelectedCell: null,
selectedData: [],
+ prevClassName: props.className,
+ panelWidth: 300,
+ isResizing: false,
+ maxWidth: window.innerWidth - 300,
+ showAggregatedData: true,
};
+ this.handleResizeDiv = this.handleResizeDiv.bind(this);
+ this.handleResizeStart = this.handleResizeStart.bind(this);
+ this.handleResizeStop = this.handleResizeStop.bind(this);
+ this.updateMaxWidth = this.updateMaxWidth.bind(this);
this.handleKey = this.handleKey.bind(this);
this.handleHeaderDragDrop = this.handleHeaderDragDrop.bind(this);
this.handleResize = this.handleResize.bind(this);
+ this.togglePanelVisibility = this.togglePanelVisibility.bind(this);
this.setCurrent = this.setCurrent.bind(this);
this.setEditing = this.setEditing.bind(this);
this.handleColumnsOrder = this.handleColumnsOrder.bind(this);
+ this.setShowAggregatedData = this.setShowAggregatedData.bind(this);
this.setCopyableValue = this.setCopyableValue.bind(this);
+ this.setSelectedObjectId = this.setSelectedObjectId.bind(this);
this.setContextMenu = this.setContextMenu.bind(this);
this.handleCellClick = this.handleCellClick.bind(this);
-
this.saveOrderTimeout = null;
}
@@ -68,7 +84,10 @@ export default class DataBrowser extends React.Component {
current: null,
editing: false,
simplifiedSchema: this.getSimplifiedSchema(props.schema, props.className),
- allClassesSchema: this.getAllClassesSchema(props.schema,props.classes),
+ allClassesSchema: this.getAllClassesSchema(props.schema, props.classes),
+ selectedCells: { list: new Set(), rowStart: -1, rowEnd: -1, colStart: -1, colEnd: -1 },
+ firstSelectedCell: null,
+ selectedData: []
});
} else if (
Object.keys(props.columns).length !== Object.keys(this.props.columns).length ||
@@ -83,21 +102,75 @@ 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: [],
- });
+ if (props && props.className) {
+ if (!props.classwiseCloudFunctions[props.className]) {
+ this.setState({ isPanelVisible: false });
+ this.setState({ selectedObjectId: undefined });
+ }
+ } else {
+ this.setState({ isPanelVisible: false });
+ this.setState({ selectedObjectId: undefined });
+ }
+
+ this.checkClassNameChange(this.state.prevClassName, props.className);
}
componentDidMount() {
document.body.addEventListener('keydown', this.handleKey);
+ window.addEventListener('resize', this.updateMaxWidth);
}
componentWillUnmount() {
document.body.removeEventListener('keydown', this.handleKey);
+ window.removeEventListener('resize', this.updateMaxWidth);
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (
+ this.state.current === null &&
+ this.state.selectedObjectId !== undefined &&
+ prevState.selectedObjectId !== undefined
+ ) {
+ this.setState({
+ selectedObjectId: undefined,
+ showAggregatedData: false
+ });
+ this.props.setAggregationPanelData({});
+ if(this.props.errorAggregatedData != {}){
+ this.props.setErrorAggregatedData({});
+ }
+ }
+ }
+
+ handleResizeStart() {
+ this.setState({ isResizing: true });
+ }
+
+ handleResizeStop(event, { size }) {
+ this.setState({
+ isResizing: false,
+ panelWidth: size.width,
+ });
}
+ handleResizeDiv(event, { size }) {
+ this.setState({ panelWidth: size.width });
+ }
+
+ setShowAggregatedData(bool) {
+ this.setState({
+ showAggregatedData: bool,
+ });
+ }
+
+ updateMaxWidth = () => {
+ const SidePanelWidth = 300;
+ this.setState({ maxWidth: window.innerWidth - SidePanelWidth });
+ if (this.state.panelWidth > window.innerWidth - SidePanelWidth) {
+ this.setState({ panelWidth: window.innerWidth - SidePanelWidth });
+ }
+ };
+
updatePreferences(order, shouldReload) {
if (this.saveOrderTimeout) {
clearTimeout(this.saveOrderTimeout);
@@ -110,10 +183,29 @@ export default class DataBrowser extends React.Component {
}, 1000);
}
+ togglePanelVisibility() {
+ this.setState(prevState => ({ isPanelVisible: !prevState.isPanelVisible }));
+
+ if (!this.state.isPanelVisible) {
+ this.props.setAggregationPanelData({});
+ this.props.setLoading(false);
+ if(this.props.errorAggregatedData != {}){
+ this.props.setErrorAggregatedData({});
+ }
+ }
+
+ if (!this.state.isPanelVisible && this.state.selectedObjectId) {
+ if(this.props.errorAggregatedData != {}){
+ this.props.setErrorAggregatedData({});
+ }
+ this.props.callCloudFunction(this.state.selectedObjectId, this.props.className);
+ }
+ }
+
getAllClassesSchema(schema) {
const allClasses = Object.keys(schema.data.get('classes').toObject());
const schemaSimplifiedData = {};
- allClasses.forEach((className) => {
+ allClasses.forEach(className => {
const classSchema = schema.data.get('classes').get(className);
if (classSchema) {
schemaSimplifiedData[className] = {};
@@ -129,6 +221,20 @@ export default class DataBrowser extends React.Component {
return schemaSimplifiedData;
}
+ checkClassNameChange(prevClassName, className) {
+ if (prevClassName !== className) {
+ this.setState({
+ prevClassName: className,
+ isPanelVisible: false,
+ selectedObjectId: undefined,
+ });
+ this.props.setAggregationPanelData({});
+ if(this.props.errorAggregatedData != {}){
+ this.props.setErrorAggregatedData({});
+ }
+ }
+ }
+
getSimplifiedSchema(schema, classNameForEditors) {
const schemaSimplifiedData = {};
const classSchema = schema.data.get('classes').get(classNameForEditors);
@@ -142,7 +248,6 @@ export default class DataBrowser extends React.Component {
}
return schemaSimplifiedData;
}
-
handleResize(index, delta) {
this.setState(({ order }) => {
order[index].width = Math.max(60, order[index].width + delta);
@@ -228,6 +333,7 @@ export default class DataBrowser extends React.Component {
case 37:
// Left - standalone (move to the next visible column on the left)
// or with ctrl/meta (excel style - move to the first visible column)
+
this.setState({
current: {
row: this.state.current.row,
@@ -246,12 +352,20 @@ export default class DataBrowser extends React.Component {
case 38:
// Up - standalone (move to the previous row)
// or with ctrl/meta (excel style - move to the first row)
+ let prevObjectID = this.state.selectedObjectId;
this.setState({
current: {
row: e.ctrlKey || e.metaKey ? 0 : Math.max(this.state.current.row - 1, 0),
col: this.state.current.col,
},
});
+ this.setState({
+ selectedObjectId:this.props.data[this.state.current.row].id,
+ showAggregatedData:true
+ })
+ if(prevObjectID !== this.state.selectedObjectId && this.state.isPanelVisible){
+ this.props.callCloudFunction(this.state.selectedObjectId,this.props.className)
+ }
e.preventDefault();
break;
case 39:
@@ -275,6 +389,7 @@ export default class DataBrowser extends React.Component {
case 40:
// Down - standalone (move to the next row)
// or with ctrl/meta (excel style - move to the last row)
+ prevObjectID = this.state.selectedObjectId;
this.setState({
current: {
row:
@@ -284,6 +399,15 @@ export default class DataBrowser extends React.Component {
col: this.state.current.col,
},
});
+
+ this.setState({
+ selectedObjectId: this.props.data[this.state.current.row].id,
+ showAggregatedData: true,
+ });
+ if (prevObjectID !== this.state.selectedObjectId && this.state.isPanelVisible) {
+ this.props.callCloudFunction(this.state.selectedObjectId, this.props.className);
+ }
+
e.preventDefault();
break;
case 67: // C
@@ -338,6 +462,12 @@ export default class DataBrowser extends React.Component {
}
}
+ setSelectedObjectId(selectedObjectId) {
+ if (this.state.selectedObjectId !== selectedObjectId) {
+ this.setState({ selectedObjectId });
+ }
+ }
+
setContextMenu(contextMenuX, contextMenuY, contextMenuItems) {
this.setState({ contextMenuX, contextMenuY, contextMenuItems });
}
@@ -390,6 +520,7 @@ export default class DataBrowser extends React.Component {
if (newSelection.size > 1) {
this.setCurrent(null);
+ this.props.setLoading(false);
this.setState({
selectedCells: {
list: newSelection,
@@ -398,6 +529,7 @@ export default class DataBrowser extends React.Component {
colStart,
colEnd,
},
+ selectedObjectId: undefined,
selectedData,
});
} else {
@@ -426,26 +558,63 @@ export default class DataBrowser extends React.Component {
const { preventSchemaEdits, applicationId } = app;
return (
-
+
+
+ {this.state.isPanelVisible && (
+
+
+
+ )}
+
+
diff --git a/src/dashboard/Data/Browser/Databrowser.scss b/src/dashboard/Data/Browser/Databrowser.scss
new file mode 100644
index 0000000000..112306c2b7
--- /dev/null
+++ b/src/dashboard/Data/Browser/Databrowser.scss
@@ -0,0 +1,25 @@
+.resizablePanel{
+ position: fixed;
+ top: 96px;
+ right: 0;
+ bottom: 0px;
+ :global(.react-resizable-handle) {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 10px;
+ cursor: col-resize;
+ box-shadow: -3px 0 3px -1px rgba(203, 203, 203, 0.2);
+ user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ }
+}
+
+.aggregationPanelContainer{
+ height: 100%;
+ overflow: auto;
+ background-color: #fefafb;
+}