From d97773a0b1e965876b5af2cd9849ff3257f20925 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 12 Jul 2025 02:00:31 +0200 Subject: [PATCH 01/10] fix: prevent stale pointer navigation --- src/dashboard/Data/Browser/Browser.react.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index b60df5be4a..faef9afc5d 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -386,6 +386,13 @@ class Browser extends DashboardView { } addLocation(appId) { if (window.localStorage) { + const currentSearch = this.props.location?.search; + if (currentSearch) { + const params = new URLSearchParams(currentSearch); + if (params.has('filters')) { + return; + } + } let pathname = null; const newLastLocations = []; From 747ebd6cba9141f5ac3409a68215837be5ea6f23 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 12 Jul 2025 02:14:22 +0200 Subject: [PATCH 02/10] fix: improve view selection and pointer navigation --- src/dashboard/Data/Browser/Browser.react.js | 24 ++---- src/dashboard/Data/Views/Views.react.js | 92 ++++++++++----------- 2 files changed, 50 insertions(+), 66 deletions(-) diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index faef9afc5d..c524607f01 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -44,8 +44,7 @@ import { withRouter } from 'lib/withRouter'; import { get } from 'lib/AJAX'; import BrowserFooter from './BrowserFooter.react'; -const SELECTED_ROWS_MESSAGE = - 'There are selected rows. Are you sure you want to leave this page?'; +const SELECTED_ROWS_MESSAGE = 'There are selected rows. Are you sure you want to leave this page?'; function SelectedRowsNavigationPrompt({ when }) { const message = SELECTED_ROWS_MESSAGE; @@ -119,7 +118,7 @@ function SelectedRowsNavigationPrompt({ when }) { } // The initial and max amount of rows fetched by lazy loading -const BROWSER_LAST_LOCATION = 'brower_last_location'; +const BROWSER_LAST_LOCATION = 'browser_last_location'; @subscribeTo('Schema', 'schema') @withRouter @@ -1512,22 +1511,17 @@ 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}'`; + errorDeletingNote = `Error deleting ${className} with id '${error.errors[0].object.id}'`; } else if (error.errors.length < toDeleteObjectIds.length) { - errorDeletingNote = - `Error deleting ${error.errors.length} out of ${toDeleteObjectIds.length} ${className} objects`; + errorDeletingNote = `Error deleting ${error.errors.length} out of ${toDeleteObjectIds.length} ${className} objects`; } else { - errorDeletingNote = - `Error deleting all ${error.errors.length} ${className} objects`; + errorDeletingNote = `Error deleting all ${error.errors.length} ${className} objects`; } } else { if (toDeleteObjectIds.length == 1) { - errorDeletingNote = - `Error deleting ${className} with id '${toDeleteObjectIds[0]}'`; + errorDeletingNote = `Error deleting ${className} with id '${toDeleteObjectIds[0]}'`; } else { - errorDeletingNote = - `Error deleting ${toDeleteObjectIds.length} ${className} objects`; + errorDeletingNote = `Error deleting ${toDeleteObjectIds.length} ${className} objects`; } } @@ -2533,9 +2527,7 @@ class Browser extends DashboardView { {pageTitle} - 0} - /> + 0} /> {browser} {notification} {extras} diff --git a/src/dashboard/Data/Views/Views.react.js b/src/dashboard/Data/Views/Views.react.js index 5029ed6ca8..1461add92e 100644 --- a/src/dashboard/Data/Views/Views.react.js +++ b/src/dashboard/Data/Views/Views.react.js @@ -22,7 +22,6 @@ import { ActionTypes as SchemaActionTypes } from 'lib/stores/SchemaStore'; import styles from './Views.scss'; import tableStyles from 'dashboard/TableView.scss'; - export default @subscribeTo('Schema', 'schema') @withRouter @@ -47,15 +46,11 @@ class Views extends TableView { }; this.headersRef = React.createRef(); this.noteTimeout = null; - this.action = new SidebarAction('Create a view', () => - this.setState({ showCreate: true }) - ); + this.action = new SidebarAction('Create a view', () => this.setState({ showCreate: true })); } componentWillMount() { - this.props.schema - .dispatch(SchemaActionTypes.FETCH) - .then(() => this.loadViews(this.context)); + this.props.schema.dispatch(SchemaActionTypes.FETCH).then(() => this.loadViews(this.context)); } componentWillUnmount() { @@ -64,9 +59,7 @@ class Views extends TableView { componentWillReceiveProps(nextProps, nextContext) { if (this.context !== nextContext) { - this.props.schema - .dispatch(SchemaActionTypes.FETCH) - .then(() => this.loadViews(nextContext)); + this.props.schema.dispatch(SchemaActionTypes.FETCH).then(() => this.loadViews(nextContext)); } if (this.props.params.name !== nextProps.params.name || this.context !== nextContext) { this.loadData(nextProps.params.name); @@ -86,10 +79,7 @@ class Views extends TableView { })); }) .catch(error => { - this.showNote( - `Request failed: ${error.message || 'Unknown error occurred'}`, - true - ); + this.showNote(`Request failed: ${error.message || 'Unknown error occurred'}`, true); }); } }); @@ -112,14 +102,10 @@ class Views extends TableView { .then(results => { const columns = {}; const computeWidth = str => { - const text = - typeof str === 'object' && str !== null - ? JSON.stringify(str) - : String(str); + const text = typeof str === 'object' && str !== null ? JSON.stringify(str) : String(str); if (typeof document !== 'undefined') { const canvas = - computeWidth._canvas || - (computeWidth._canvas = document.createElement('canvas')); + computeWidth._canvas || (computeWidth._canvas = document.createElement('canvas')); const context = canvas.getContext('2d'); context.font = '12px "Source Code Pro", "Courier New", monospace'; const width = context.measureText(text).width + 32; @@ -163,10 +149,7 @@ class Views extends TableView { this.setState({ data: results, order, columns, tableWidth }); }) .catch(error => { - this.showNote( - `Request failed: ${error.message || 'Unknown error occurred'}`, - true - ); + this.showNote(`Request failed: ${error.message || 'Unknown error occurred'}`, true); this.setState({ data: [], order: [], columns: {} }); }); } @@ -210,10 +193,7 @@ class Views extends TableView { return (
-
+
(
@@ -321,6 +300,34 @@ class Views extends TableView { } renderEmpty() { + if (!this.props.params.name) { + if (this.state.views.length > 0) { + return ( + + ); + } + return ( + + Use views to display aggregated data from your classes.{' '} + + Learn more + + . + + } + cta="Create a view" + action={() => this.setState({ showCreate: true })} + /> + ); + } return
No data available
; } @@ -351,9 +358,7 @@ class Views extends TableView { { - const index = this.state.views.findIndex( - v => v.name === this.props.params.name - ); + const index = this.state.views.findIndex(v => v.name === this.props.params.name); if (index >= 0) { this.setState({ editView: this.state.views[index], @@ -366,9 +371,7 @@ class Views extends TableView { { - const index = this.state.views.findIndex( - v => v.name === this.props.params.name - ); + const index = this.state.views.findIndex(v => v.name === this.props.params.name); if (index >= 0) { this.setState({ deleteIndex: index }); } @@ -402,10 +405,7 @@ class Views extends TableView { this.setState( state => ({ showCreate: false, views: [...state.views, view] }), () => { - ViewPreferences.saveViews( - this.context.applicationId, - this.state.views - ); + ViewPreferences.saveViews(this.context.applicationId, this.state.views); this.loadViews(this.context); } ); @@ -433,10 +433,7 @@ class Views extends TableView { return { editView: null, editIndex: null, views: newViews }; }, () => { - ViewPreferences.saveViews( - this.context.applicationId, - this.state.views - ); + ViewPreferences.saveViews(this.context.applicationId, this.state.views); this.loadViews(this.context); } ); @@ -456,10 +453,7 @@ class Views extends TableView { return { deleteIndex: null, views: newViews }; }, () => { - ViewPreferences.saveViews( - this.context.applicationId, - this.state.views - ); + ViewPreferences.saveViews(this.context.applicationId, this.state.views); if (this.props.params.name === name) { const path = generatePath(this.context, 'views'); this.props.navigate(path); @@ -486,9 +480,7 @@ class Views extends TableView { } handlePointerClick({ className, id, field = 'objectId' }) { - const filters = JSON.stringify([ - { field, constraint: 'eq', compareTo: id }, - ]); + const filters = JSON.stringify([{ field, constraint: 'eq', compareTo: id }]); const path = generatePath( this.context, `browser/${className}?filters=${encodeURIComponent(filters)}` From 7e86873845b21ddc4e3b1965113bb575f3df7696 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 12 Jul 2025 02:23:28 +0200 Subject: [PATCH 03/10] fix: resolve views crash and navigation --- src/dashboard/Data/Views/Views.react.js | 33 +++++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/dashboard/Data/Views/Views.react.js b/src/dashboard/Data/Views/Views.react.js index 1461add92e..62bfa64297 100644 --- a/src/dashboard/Data/Views/Views.react.js +++ b/src/dashboard/Data/Views/Views.react.js @@ -14,6 +14,7 @@ import DeleteViewDialog from './DeleteViewDialog.react'; import BrowserMenu from 'components/BrowserMenu/BrowserMenu.react'; import MenuItem from 'components/BrowserMenu/MenuItem.react'; import Separator from 'components/BrowserMenu/Separator.react'; +import EmptyState from 'components/EmptyState/EmptyState.react'; import * as ViewPreferences from 'lib/ViewPreferences'; import generatePath from 'lib/generatePath'; import { withRouter } from 'lib/withRouter'; @@ -30,6 +31,7 @@ class Views extends TableView { super(); this.section = 'Core'; this.subsection = 'Views'; + this._isMounted = false; this.state = { views: [], counts: {}, @@ -49,11 +51,16 @@ class Views extends TableView { this.action = new SidebarAction('Create a view', () => this.setState({ showCreate: true })); } + componentDidMount() { + this._isMounted = true; + } + componentWillMount() { this.props.schema.dispatch(SchemaActionTypes.FETCH).then(() => this.loadViews(this.context)); } componentWillUnmount() { + this._isMounted = false; clearTimeout(this.noteTimeout); } @@ -74,16 +81,22 @@ class Views extends TableView { new Parse.Query(view.className) .aggregate(view.query, { useMasterKey: true }) .then(res => { - this.setState(({ counts }) => ({ - counts: { ...counts, [view.name]: res.length }, - })); + if (this._isMounted) { + this.setState(({ counts }) => ({ + counts: { ...counts, [view.name]: res.length }, + })); + } }) .catch(error => { - this.showNote(`Request failed: ${error.message || 'Unknown error occurred'}`, true); + if (this._isMounted) { + this.showNote(`Request failed: ${error.message || 'Unknown error occurred'}`, true); + } }); } }); - this.loadData(this.props.params.name); + if (this._isMounted) { + this.loadData(this.props.params.name); + } }); } @@ -146,11 +159,15 @@ class Views extends TableView { const colNames = Object.keys(columns); const order = colNames.map(name => ({ name, width: columns[name].width })); const tableWidth = order.reduce((sum, col) => sum + col.width, 0); - this.setState({ data: results, order, columns, tableWidth }); + if (this._isMounted) { + this.setState({ data: results, order, columns, tableWidth }); + } }) .catch(error => { - this.showNote(`Request failed: ${error.message || 'Unknown error occurred'}`, true); - this.setState({ data: [], order: [], columns: {} }); + if (this._isMounted) { + this.showNote(`Request failed: ${error.message || 'Unknown error occurred'}`, true); + this.setState({ data: [], order: [], columns: {} }); + } }); } From 8bdebaf7a45c31ef9b047834b3c90f6e192224de Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 12 Jul 2025 02:38:04 +0200 Subject: [PATCH 04/10] fix: improve empty state icons for views --- src/dashboard/Data/Views/Views.react.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dashboard/Data/Views/Views.react.js b/src/dashboard/Data/Views/Views.react.js index 62bfa64297..1276c6f0e3 100644 --- a/src/dashboard/Data/Views/Views.react.js +++ b/src/dashboard/Data/Views/Views.react.js @@ -320,12 +320,12 @@ class Views extends TableView { if (!this.props.params.name) { if (this.state.views.length > 0) { return ( - + ); } return ( From d3e3db2991f91ba742c0c217f62dbcf1ded1a707 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 12 Jul 2025 02:41:56 +0200 Subject: [PATCH 05/10] fix: use camelCase style property --- src/dashboard/Data/Browser/BrowserTable.react.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js index 1d8001816a..378d162ff9 100644 --- a/src/dashboard/Data/Browser/BrowserTable.react.js +++ b/src/dashboard/Data/Browser/BrowserTable.react.js @@ -574,7 +574,7 @@ export default class BrowserTable extends React.Component { id="browser-table" style={{ right: rightValue, - 'overflow-x': this.props.isResizing ? 'hidden' : 'auto', + overflowX: this.props.isResizing ? 'hidden' : 'auto', }} > Date: Sat, 12 Jul 2025 02:45:30 +0200 Subject: [PATCH 06/10] fix: allow single child in BrowserMenu --- src/components/BrowserMenu/BrowserMenu.react.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/BrowserMenu/BrowserMenu.react.js b/src/components/BrowserMenu/BrowserMenu.react.js index 2eb60e50ec..ee5f9c0b41 100644 --- a/src/components/BrowserMenu/BrowserMenu.react.js +++ b/src/components/BrowserMenu/BrowserMenu.react.js @@ -83,7 +83,10 @@ export default class BrowserMenu extends React.Component { BrowserMenu.propTypes = { icon: PropTypes.string.isRequired.describe('The name of the icon to place in the menu.'), title: PropTypes.string.isRequired.describe('The title text of the menu.'), - children: PropTypes.arrayOf(PropTypes.node).describe( + children: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.node), + PropTypes.node, + ]).describe( 'The contents of the menu when open. It should be a set of MenuItem and Separator components.' ), }; From 8d1a690e32a36edaf7777417a21894884add71af Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 12 Jul 2025 02:54:20 +0200 Subject: [PATCH 07/10] feat: improve Views UX --- src/dashboard/Data/Views/Views.react.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/dashboard/Data/Views/Views.react.js b/src/dashboard/Data/Views/Views.react.js index 1276c6f0e3..859a11493a 100644 --- a/src/dashboard/Data/Views/Views.react.js +++ b/src/dashboard/Data/Views/Views.react.js @@ -2,6 +2,7 @@ import CategoryList from 'components/CategoryList/CategoryList.react'; import SidebarAction from 'components/Sidebar/SidebarAction'; import TableView from 'dashboard/TableView.react'; import Toolbar from 'components/Toolbar/Toolbar.react'; +import Icon from 'components/Icon/Icon.react'; import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react'; import Parse from 'parse'; import React from 'react'; @@ -22,6 +23,7 @@ import subscribeTo from 'lib/subscribeTo'; import { ActionTypes as SchemaActionTypes } from 'lib/stores/SchemaStore'; import styles from './Views.scss'; import tableStyles from 'dashboard/TableView.scss'; +import browserStyles from 'dashboard/Data/Browser/Browser.scss'; export default @subscribeTo('Schema', 'schema') @@ -69,6 +71,7 @@ class Views extends TableView { this.props.schema.dispatch(SchemaActionTypes.FETCH).then(() => this.loadViews(nextContext)); } if (this.props.params.name !== nextProps.params.name || this.context !== nextContext) { + window.scrollTo({ top: 0 }); this.loadData(nextProps.params.name); } } @@ -171,6 +174,10 @@ class Views extends TableView { }); } + onRefresh() { + this.loadData(this.props.params.name); + } + tableData() { return this.state.data; } @@ -360,7 +367,9 @@ class Views extends TableView { current={current} params={this.props.location?.search} linkPrefix={'views/'} - classClicked={() => {}} + classClicked={() => { + window.scrollTo({ top: 0 }); + }} categories={categories} /> ); @@ -399,6 +408,10 @@ class Views extends TableView { } return ( + + + Refresh + {editMenu} ); From bebb654ca2534df86e8ec3cdf5cc849809f4aac5 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 12 Jul 2025 03:00:45 +0200 Subject: [PATCH 08/10] fix: add loader when refreshing views --- src/dashboard/Data/Views/Views.react.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/dashboard/Data/Views/Views.react.js b/src/dashboard/Data/Views/Views.react.js index 859a11493a..e3327419c4 100644 --- a/src/dashboard/Data/Views/Views.react.js +++ b/src/dashboard/Data/Views/Views.react.js @@ -47,6 +47,7 @@ class Views extends TableView { deleteIndex: null, lastError: null, lastNote: null, + loading: false, }; this.headersRef = React.createRef(); this.noteTimeout = null; @@ -104,13 +105,20 @@ class Views extends TableView { } loadData(name) { + if (this._isMounted) { + this.setState({ loading: true }); + } if (!name) { - this.setState({ data: [], order: [], columns: {} }); + if (this._isMounted) { + this.setState({ data: [], order: [], columns: {}, loading: false }); + } return; } const view = (this.state.views || []).find(v => v.name === name); if (!view) { - this.setState({ data: [], order: [], columns: {} }); + if (this._isMounted) { + this.setState({ data: [], order: [], columns: {}, loading: false }); + } return; } new Parse.Query(view.className) @@ -163,13 +171,13 @@ class Views extends TableView { const order = colNames.map(name => ({ name, width: columns[name].width })); const tableWidth = order.reduce((sum, col) => sum + col.width, 0); if (this._isMounted) { - this.setState({ data: results, order, columns, tableWidth }); + this.setState({ data: results, order, columns, tableWidth, loading: false }); } }) .catch(error => { if (this._isMounted) { this.showNote(`Request failed: ${error.message || 'Unknown error occurred'}`, true); - this.setState({ data: [], order: [], columns: {} }); + this.setState({ data: [], order: [], columns: {}, loading: false }); } }); } @@ -216,7 +224,7 @@ class Views extends TableView { const loading = this.state ? this.state.loading : false; return (
- +
Date: Sat, 12 Jul 2025 03:08:04 +0200 Subject: [PATCH 09/10] fix: adjust Views toolbar --- src/dashboard/Data/Views/Views.react.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/dashboard/Data/Views/Views.react.js b/src/dashboard/Data/Views/Views.react.js index e3327419c4..8f2e031be7 100644 --- a/src/dashboard/Data/Views/Views.react.js +++ b/src/dashboard/Data/Views/Views.react.js @@ -414,12 +414,23 @@ class Views extends TableView { ); } + + let refreshButton = null; + if (editMenu) { + refreshButton = ( + <> + + + Refresh + +
+ + ); + } + return ( - - - Refresh - + {refreshButton} {editMenu} ); From 46fcfa3d26df7b8c692cda57a1e01ca3ae7e30a7 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sat, 12 Jul 2025 03:10:30 +0200 Subject: [PATCH 10/10] Update Views.react.js --- src/dashboard/Data/Views/Views.react.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/dashboard/Data/Views/Views.react.js b/src/dashboard/Data/Views/Views.react.js index 8f2e031be7..36b1c75eec 100644 --- a/src/dashboard/Data/Views/Views.react.js +++ b/src/dashboard/Data/Views/Views.react.js @@ -386,6 +386,7 @@ class Views extends TableView { renderToolbar() { const subsection = this.props.params.name || ''; let editMenu = null; + let refreshButton = null; if (this.props.params.name) { editMenu = ( {}}> @@ -413,10 +414,6 @@ class Views extends TableView { /> ); - } - - let refreshButton = null; - if (editMenu) { refreshButton = ( <>