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.' ), }; diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index b60df5be4a..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 @@ -386,6 +385,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 = []; @@ -1505,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`; } } @@ -2526,9 +2527,7 @@ class Browser extends DashboardView { {pageTitle} - 0} - /> + 0} /> {browser} {notification} {extras} 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', }} > - this.setState({ showCreate: true }) - ); + 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)); + this.props.schema.dispatch(SchemaActionTypes.FETCH).then(() => this.loadViews(this.context)); } componentWillUnmount() { + this._isMounted = false; clearTimeout(this.noteTimeout); } 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) { + window.scrollTo({ top: 0 }); this.loadData(nextProps.params.name); } } @@ -81,30 +85,40 @@ 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); + } }); } 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) @@ -112,14 +126,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; @@ -160,17 +170,22 @@ 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, loading: false }); + } }) .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: {}, loading: false }); + } }); } + onRefresh() { + this.loadData(this.props.params.name); + } + tableData() { return this.state.data; } @@ -209,11 +224,8 @@ class Views extends TableView { const loading = this.state ? this.state.loading : false; return (
- -
+ +
(
@@ -321,6 +332,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
; } @@ -336,7 +375,9 @@ class Views extends TableView { current={current} params={this.props.location?.search} linkPrefix={'views/'} - classClicked={() => {}} + classClicked={() => { + window.scrollTo({ top: 0 }); + }} categories={categories} /> ); @@ -345,15 +386,14 @@ class Views extends TableView { renderToolbar() { const subsection = this.props.params.name || ''; let editMenu = null; + let refreshButton = null; if (this.props.params.name) { editMenu = ( {}}> { - 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 +406,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 }); } @@ -376,9 +414,20 @@ class Views extends TableView { /> ); + refreshButton = ( + <> + + + Refresh + +
+ + ); } + return ( + {refreshButton} {editMenu} ); @@ -402,10 +451,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 +479,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 +499,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 +526,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)}`