From 18f93b9096d0e5728ffd9800a9c74270ea3da490 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 1 Mar 2025 01:46:52 +1100 Subject: [PATCH 01/13] feat: add paging --- src/dashboard/Data/Browser/Browser.react.js | 34 +++++-- src/dashboard/Data/Browser/Browser.scss | 2 +- .../Data/Browser/BrowserFooter.react.js | 96 +++++++++++++++++++ src/dashboard/Data/Browser/BrowserFooter.scss | 14 +++ .../Data/Browser/BrowserTable.react.js | 33 ------- 5 files changed, 137 insertions(+), 42 deletions(-) create mode 100644 src/dashboard/Data/Browser/BrowserFooter.react.js create mode 100644 src/dashboard/Data/Browser/BrowserFooter.scss diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 93b01a308d..84a77433e5 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -41,9 +41,9 @@ import { Helmet } from 'react-helmet'; import generatePath from 'lib/generatePath'; import { withRouter } from 'lib/withRouter'; import { get } from 'lib/AJAX'; +import BrowserFooter from './BrowserFooter.react'; // The initial and max amount of rows fetched by lazy loading -const MAX_ROWS_FETCHED = 200; const BROWSER_LAST_LOCATION = 'brower_last_location'; @subscribeTo('Schema', 'schema') @@ -75,6 +75,8 @@ class Browser extends DashboardView { clp: {}, filters: new List(), ordering: '-createdAt', + skip: 0, + limit: 20, selection: {}, exporting: false, exportingCount: 0, @@ -892,7 +894,7 @@ class Browser extends DashboardView { } async fetchParseData(source, filters) { - const { useMasterKey } = this.state; + const { useMasterKey, skip, limit } = this.state; const query = await queryFromFilters(source, filters); const sortDir = this.state.ordering[0] === '-' ? '-' : '+'; const field = this.state.ordering.substr(sortDir === '-' ? 1 : 0); @@ -902,8 +904,9 @@ class Browser extends DashboardView { } else { query.ascending(field); } + query.skip(skip); + query.limit(limit); - query.limit(MAX_ROWS_FETCHED); this.excludeFields(query, source); let promise = query.find({ useMasterKey }); let isUnique = false; @@ -955,7 +958,7 @@ class Browser extends DashboardView { this.setState({ data: data, filters, - lastMax: MAX_ROWS_FETCHED, + lastMax: this.state.limit, filteredCounts: filteredCounts, }); } @@ -969,7 +972,7 @@ class Browser extends DashboardView { selection: {}, data, filters, - lastMax: MAX_ROWS_FETCHED, + lastMax: this.state.limit, }); } @@ -1022,7 +1025,7 @@ class Browser extends DashboardView { query.lessThan('createdAt', this.state.data[this.state.data.length - 1].get('createdAt')); query.addDescending('createdAt'); } - query.limit(MAX_ROWS_FETCHED); + query.limit(this.state.limit); this.excludeFields(query, source); const { useMasterKey } = this.state; @@ -1033,7 +1036,7 @@ class Browser extends DashboardView { })); } }); - this.setState({ lastMax: this.state.lastMax + MAX_ROWS_FETCHED }); + this.setState({ lastMax: this.state.lastMax + this.state.limit }); } updateFilters(filters) { @@ -1997,7 +2000,8 @@ class Browser extends DashboardView { } } browser = ( - + + { + this.setState({ skip }); + this.updateOrdering(this.state.ordering); + }} + count={this.state.counts[className]} + limit={this.state.limit} + setLimit={(limit) => { + this.setState({ limit }) + this.updateOrdering(this.state.ordering); + }} + /> + ); } } diff --git a/src/dashboard/Data/Browser/Browser.scss b/src/dashboard/Data/Browser/Browser.scss index 97948cb9a0..3ed76ef649 100644 --- a/src/dashboard/Data/Browser/Browser.scss +++ b/src/dashboard/Data/Browser/Browser.scss @@ -12,7 +12,7 @@ top: 96px; left: 300px; right: 0; - bottom: 0; + bottom: 40px; overflow: auto; padding-top: 30px; } diff --git a/src/dashboard/Data/Browser/BrowserFooter.react.js b/src/dashboard/Data/Browser/BrowserFooter.react.js new file mode 100644 index 0000000000..644992cdb9 --- /dev/null +++ b/src/dashboard/Data/Browser/BrowserFooter.react.js @@ -0,0 +1,96 @@ +import React from "react"; +import styles from './BrowserFooter.scss'; +import Button from 'components/Button/Button.react'; + +class BrowserFooter extends React.Component { + state = { + pageInput: (Math.floor(this.props.skip / this.props.limit) + 1).toString(), + }; + + handleLimitChange = (event) => { + const newLimit = parseInt(event.target.value, 10); + this.props.setLimit(newLimit); + this.props.setSkip(0); + this.setState({ pageInput: "1" }); + }; + + handlePageChange = (newSkip) => { + if (newSkip >= 0 && newSkip < this.props.count) { + this.props.setSkip(newSkip); + this.setState({ pageInput: (Math.floor(newSkip / this.props.limit) + 1).toString() }); + } + }; + + handleInputChange = (e) => { + const value = e.target.value; + + // Allow user to type freely but validate only on blur/Enter + if (value === "" || /^\d*$/.test(value)) { + this.setState({ pageInput: value }); + } + }; + + validateAndApplyPage = () => { + const { limit, count } = this.props; + let newPage = parseInt(this.state.pageInput, 10); + + if (isNaN(newPage) || newPage < 1) { + newPage = 1; + } else if (newPage > Math.ceil(count / limit)) { + newPage = Math.ceil(count / limit); + } + + this.setState({ pageInput: newPage.toString() }); + this.handlePageChange((newPage - 1) * limit); + }; + + handleKeyDown = (e) => { + if (e.key === "Enter") { + this.validateAndApplyPage(); + } + }; + + render() { + const { skip, count, limit } = this.props; + const totalPages = Math.ceil(count / limit); + + return ( +
+ + {count?.toLocaleString() || 0} items + + + items per page + + / {totalPages.toLocaleString()} +
+ ); + } +} + +export default BrowserFooter; diff --git a/src/dashboard/Data/Browser/BrowserFooter.scss b/src/dashboard/Data/Browser/BrowserFooter.scss new file mode 100644 index 0000000000..19718fcd93 --- /dev/null +++ b/src/dashboard/Data/Browser/BrowserFooter.scss @@ -0,0 +1,14 @@ +@import 'stylesheets/globals.scss'; + +.footer { + position: absolute; + width: calc(100% - 300px); + bottom: 0; + gap: 10px; + padding: 0px 20px; + height: 40px; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; +} \ 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 59dc53d5da..27b8b6cbad 100644 --- a/src/dashboard/Data/Browser/BrowserTable.react.js +++ b/src/dashboard/Data/Browser/BrowserTable.react.js @@ -34,7 +34,6 @@ export default class BrowserTable extends React.Component { 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); @@ -60,12 +59,10 @@ 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); } @@ -93,36 +90,6 @@ export default class BrowserTable extends React.Component { document.body.style.cursor = 'default'; } - handleScroll() { - if (!this.props.data || this.props.data.length === 0) { - return; - } - requestAnimationFrame(() => { - const currentScrollTop = this.tableRef.current.scrollTop; - let rowsAbove = Math.floor(currentScrollTop / ROW_HEIGHT); - let offset = this.state.offset; - const currentRow = rowsAbove - this.state.offset; - - // If the scroll is near the beginning or end of the offset, - // we need to update the table data with the previous/next offset - if (currentRow < 10 || currentRow >= ROWS_OFFSET) { - // Rounds the number of rows above - rowsAbove = Math.floor(rowsAbove / 10) * 10; - - offset = - currentRow < 10 - ? Math.max(0, rowsAbove - ROWS_OFFSET) // Previous set of rows - : rowsAbove - 10; // Next set of rows - } - if (this.state.offset !== offset) { - this.setState({ offset }); - this.tableRef.current.scrollTop = currentScrollTop; - } - if (this.props.maxFetched - offset <= ROWS_OFFSET * 1.4) { - this.props.fetchNextPage(); - } - }); - } updateMaxWidth = () => { this.setState({ maxWidth: window.innerWidth - 300 }); if (this.state.panelWidth > window.innerWidth - 300) { From 21c6b780cd5388580922532b5e423f8ecf715518 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 1 Mar 2025 02:13:39 +1100 Subject: [PATCH 02/13] fix lint --- src/dashboard/Data/Browser/Browser.react.js | 174 +++++++++--------- .../Data/Browser/BrowserFooter.react.js | 12 +- .../Data/Browser/BrowserTable.react.js | 1 - 3 files changed, 93 insertions(+), 94 deletions(-) diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 84a77433e5..46cf11cada 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -1299,7 +1299,7 @@ class Browser extends DashboardView { this.state.counts[className] = 0; this.setState({ data: [], - lastMax: MAX_ROWS_FETCHED, + lastMax: this.state.limit, selection: {}, }); } @@ -1359,7 +1359,7 @@ class Browser extends DashboardView { // If after deletion, the remaining elements on the table is lesser than the maximum allowed elements // we fetch more data to fill the table - if (this.state.data.length < MAX_ROWS_FETCHED) { + if (this.state.data.length < this.state.limit) { this.prefetchData(this.props, this.context); } else { this.forceUpdate(); @@ -2002,91 +2002,91 @@ class Browser extends DashboardView { browser = ( <> this.saveFilters(...args)} - onRemoveColumn={this.showRemoveColumn} - onDeleteRows={this.showDeleteRows} - onDropClass={this.showDropClass} - onExport={this.showExport} - onChangeCLP={this.handleCLPChange} - onRefresh={this.refresh} - onAttachRows={this.showAttachRowsDialog} - onAttachSelectedRows={this.showAttachSelectedRowsDialog} - onExecuteScriptRows={this.showExecuteScriptRowsDialog} - onCloneSelectedRows={this.showCloneSelectedRowsDialog} - onEditSelectedRow={this.showEditRowDialog} - onEditPermissions={this.onDialogToggle} - onExportSelectedRows={this.showExportSelectedRowsDialog} - onExportSchema={this.showExportSchemaDialog} - onSaveNewRow={this.saveNewRow} - onShowPointerKey={this.showPointerKeyDialog} - onAbortAddRow={this.abortAddRow} - onSaveEditCloneRow={this.saveEditCloneRow} - onAbortEditCloneRow={this.abortEditCloneRow} - onCancelPendingEditRows={this.cancelPendingEditRows} - currentUser={this.state.currentUser} - useMasterKey={this.state.useMasterKey} - login={this.login} - logout={this.logout} - toggleMasterKeyUsage={this.toggleMasterKeyUsage} - markRequiredFieldRow={this.state.markRequiredFieldRow} - requiredColumnFields={this.state.requiredColumnFields} - columns={columns} - className={className} - fetchNextPage={this.fetchNextPage} - maxFetched={this.state.lastMax} - selectRow={this.selectRow} - selection={this.state.selection} - data={this.state.data} - ordering={this.state.ordering} - newObject={this.state.newObject} - editCloneRows={this.state.editCloneRows} - relation={this.state.relation} - disableKeyControls={this.hasExtras()} - updateRow={this.updateRow} - updateOrdering={this.updateOrdering} - onPointerClick={this.handlePointerClick} - onPointerCmdClick={this.handlePointerCmdClick} - setRelation={this.setRelation} - onAddColumn={this.showAddColumn} - onAddRow={this.addRow} - onAddRowWithModal={this.addRowWithModal} - onAddClass={this.showCreateClass} - showNote={this.showNote} - onMouseDownRowCheckBox={this.onMouseDownRowCheckBox} - 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} - appName = {this.props.params.appId} - /> - { - this.setState({ skip }); - this.updateOrdering(this.state.ordering); - }} - count={this.state.counts[className]} - limit={this.state.limit} - setLimit={(limit) => { - this.setState({ limit }) - this.updateOrdering(this.state.ordering); - }} + app={this.context} + ref={this.dataBrowserRef} + isUnique={this.state.isUnique} + uniqueField={this.state.uniqueField} + count={count} + perms={this.state.clp[className]} + schema={this.props.schema} + filters={this.state.filters} + onFilterChange={this.updateFilters} + onFilterSave={(...args) => this.saveFilters(...args)} + onRemoveColumn={this.showRemoveColumn} + onDeleteRows={this.showDeleteRows} + onDropClass={this.showDropClass} + onExport={this.showExport} + onChangeCLP={this.handleCLPChange} + onRefresh={this.refresh} + onAttachRows={this.showAttachRowsDialog} + onAttachSelectedRows={this.showAttachSelectedRowsDialog} + onExecuteScriptRows={this.showExecuteScriptRowsDialog} + onCloneSelectedRows={this.showCloneSelectedRowsDialog} + onEditSelectedRow={this.showEditRowDialog} + onEditPermissions={this.onDialogToggle} + onExportSelectedRows={this.showExportSelectedRowsDialog} + onExportSchema={this.showExportSchemaDialog} + onSaveNewRow={this.saveNewRow} + onShowPointerKey={this.showPointerKeyDialog} + onAbortAddRow={this.abortAddRow} + onSaveEditCloneRow={this.saveEditCloneRow} + onAbortEditCloneRow={this.abortEditCloneRow} + onCancelPendingEditRows={this.cancelPendingEditRows} + currentUser={this.state.currentUser} + useMasterKey={this.state.useMasterKey} + login={this.login} + logout={this.logout} + toggleMasterKeyUsage={this.toggleMasterKeyUsage} + markRequiredFieldRow={this.state.markRequiredFieldRow} + requiredColumnFields={this.state.requiredColumnFields} + columns={columns} + className={className} + fetchNextPage={this.fetchNextPage} + maxFetched={this.state.lastMax} + selectRow={this.selectRow} + selection={this.state.selection} + data={this.state.data} + ordering={this.state.ordering} + newObject={this.state.newObject} + editCloneRows={this.state.editCloneRows} + relation={this.state.relation} + disableKeyControls={this.hasExtras()} + updateRow={this.updateRow} + updateOrdering={this.updateOrdering} + onPointerClick={this.handlePointerClick} + onPointerCmdClick={this.handlePointerCmdClick} + setRelation={this.setRelation} + onAddColumn={this.showAddColumn} + onAddRow={this.addRow} + onAddRowWithModal={this.addRowWithModal} + onAddClass={this.showCreateClass} + showNote={this.showNote} + onMouseDownRowCheckBox={this.onMouseDownRowCheckBox} + 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} + appName = {this.props.params.appId} + /> + { + this.setState({ skip }); + this.updateOrdering(this.state.ordering); + }} + count={this.state.counts[className]} + limit={this.state.limit} + setLimit={(limit) => { + this.setState({ limit }) + this.updateOrdering(this.state.ordering); + }} /> ); diff --git a/src/dashboard/Data/Browser/BrowserFooter.react.js b/src/dashboard/Data/Browser/BrowserFooter.react.js index 644992cdb9..90e1fcea7a 100644 --- a/src/dashboard/Data/Browser/BrowserFooter.react.js +++ b/src/dashboard/Data/Browser/BrowserFooter.react.js @@ -1,4 +1,4 @@ -import React from "react"; +import React from 'react'; import styles from './BrowserFooter.scss'; import Button from 'components/Button/Button.react'; @@ -11,7 +11,7 @@ class BrowserFooter extends React.Component { const newLimit = parseInt(event.target.value, 10); this.props.setLimit(newLimit); this.props.setSkip(0); - this.setState({ pageInput: "1" }); + this.setState({ pageInput: '1' }); }; handlePageChange = (newSkip) => { @@ -23,9 +23,9 @@ class BrowserFooter extends React.Component { handleInputChange = (e) => { const value = e.target.value; - + // Allow user to type freely but validate only on blur/Enter - if (value === "" || /^\d*$/.test(value)) { + if (value === '' || /^\d*$/.test(value)) { this.setState({ pageInput: value }); } }; @@ -45,7 +45,7 @@ class BrowserFooter extends React.Component { }; handleKeyDown = (e) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { this.validateAndApplyPage(); } }; @@ -69,7 +69,7 @@ class BrowserFooter extends React.Component { items per page Date: Mon, 3 Mar 2025 02:59:33 +0100 Subject: [PATCH 03/13] minor corrections --- src/dashboard/Data/Browser/Browser.react.js | 2 +- src/dashboard/Data/Browser/BrowserFooter.react.js | 8 ++++---- src/dashboard/Data/Browser/BrowserFooter.scss | 4 +++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 46cf11cada..949e5bdc79 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -76,7 +76,7 @@ class Browser extends DashboardView { filters: new List(), ordering: '-createdAt', skip: 0, - limit: 20, + limit: 100, selection: {}, exporting: false, exportingCount: 0, diff --git a/src/dashboard/Data/Browser/BrowserFooter.react.js b/src/dashboard/Data/Browser/BrowserFooter.react.js index 90e1fcea7a..3794ec45ac 100644 --- a/src/dashboard/Data/Browser/BrowserFooter.react.js +++ b/src/dashboard/Data/Browser/BrowserFooter.react.js @@ -1,6 +1,6 @@ +import Button from 'components/Button/Button.react'; import React from 'react'; import styles from './BrowserFooter.scss'; -import Button from 'components/Button/Button.react'; class BrowserFooter extends React.Component { state = { @@ -57,16 +57,16 @@ class BrowserFooter extends React.Component { return (
- {count?.toLocaleString() || 0} items + {count?.toLocaleString() || 0} objects - items per page + per page Date: Tue, 4 Mar 2025 22:24:40 +1100 Subject: [PATCH 04/13] review feedback --- src/dashboard/Data/Browser/Browser.react.js | 12 ++++++++++- .../Data/Browser/BrowserFooter.react.js | 21 +++++++++++++------ .../Data/Browser/BrowserTable.react.js | 6 +++--- .../Data/Browser/EditRowDialog.react.js | 2 ++ .../Data/Browser/ObjectPickerDialog.react.js | 9 ++++---- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 949e5bdc79..455e04bc8e 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -54,6 +54,7 @@ class Browser extends DashboardView { this.section = 'Core'; this.subsection = 'Browser'; this.noteTimeout = null; + const limit = window.localStorage?.getItem('browserLimit') this.state = { showCreateClassDialog: false, @@ -76,7 +77,7 @@ class Browser extends DashboardView { filters: new List(), ordering: '-createdAt', skip: 0, - limit: 100, + limit: limit ? parseInt(limit) : 100, selection: {}, exporting: false, exportingCount: 0, @@ -194,6 +195,7 @@ class Browser extends DashboardView { relation: null, }); }); + } componentWillMount() { @@ -907,6 +909,8 @@ class Browser extends DashboardView { query.skip(skip); query.limit(limit); + localStorage?.setItem('browserLimit', limit); + this.excludeFields(query, source); let promise = query.find({ useMasterKey }); let isUnique = false; @@ -1052,6 +1056,10 @@ class Browser extends DashboardView { // filters param change is making the fetch call this.props.navigate(generatePath(this.context, url)); } + + this.setState({ + skip: 0, + }) } saveFilters(filters, name) { @@ -2074,6 +2082,7 @@ class Browser extends DashboardView { setErrorAggregatedData={this.setErrorAggregatedData} errorAggregatedData={this.state.errorAggregatedData} appName = {this.props.params.appId} + limit={this.state.limit} /> ); } else if (this.state.rowsToExport) { diff --git a/src/dashboard/Data/Browser/BrowserFooter.react.js b/src/dashboard/Data/Browser/BrowserFooter.react.js index 3794ec45ac..6dc6a38b02 100644 --- a/src/dashboard/Data/Browser/BrowserFooter.react.js +++ b/src/dashboard/Data/Browser/BrowserFooter.react.js @@ -7,6 +7,17 @@ class BrowserFooter extends React.Component { pageInput: (Math.floor(this.props.skip / this.props.limit) + 1).toString(), }; + componentDidUpdate(prevProps) { + if (prevProps.count && this.props.count === 0 && prevProps.count !== 0) { + this.props.setSkip(0); + this.setState({ pageInput: '1' }); + } + + if (prevProps.skip !== this.props.skip) { + this.setState({ pageInput: (Math.floor(this.props.skip / this.props.limit) + 1).toString() }); + } + } + handleLimitChange = (event) => { const newLimit = parseInt(event.target.value, 10); this.props.setLimit(newLimit); @@ -19,15 +30,13 @@ class BrowserFooter extends React.Component { this.props.setSkip(newSkip); this.setState({ pageInput: (Math.floor(newSkip / this.props.limit) + 1).toString() }); } + + const table = document.getElementById('browser-table'); + table.scrollTo({ top: 0 }); }; handleInputChange = (e) => { - const value = e.target.value; - - // Allow user to type freely but validate only on blur/Enter - if (value === '' || /^\d*$/.test(value)) { - this.setState({ pageInput: value }); - } + this.setState({ pageInput: e.target.value }); }; validateAndApplyPage = () => { diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js index 59298a337b..d1b0dc3158 100644 --- a/src/dashboard/Data/Browser/BrowserTable.react.js +++ b/src/dashboard/Data/Browser/BrowserTable.react.js @@ -17,7 +17,6 @@ import styles from 'dashboard/Data/Browser/Browser.scss'; import Button from 'components/Button/Button.react'; import { CurrentApp } from 'context/currentApp'; -const MAX_ROWS = 200; // Number of rows to render at any time const ROW_HEIGHT = 30; const READ_ONLY = ['objectId', 'createdAt', 'updatedAt']; @@ -298,7 +297,7 @@ export default class BrowserTable extends React.Component { ); } const rows = []; - const end = Math.min(this.state.offset + MAX_ROWS, this.props.data.length); + const end = Math.min(this.state.offset + this.props.limit, this.props.data.length); for (let i = this.state.offset; i < end; i++) { const index = i - this.state.offset; const obj = this.props.data[i]; @@ -493,7 +492,7 @@ export default class BrowserTable extends React.Component { style={{ height: Math.max( 0, - (this.props.data.length - this.state.offset - MAX_ROWS) * ROW_HEIGHT + (this.props.data.length - this.state.offset - this.props.limit) * ROW_HEIGHT ), }} /> @@ -535,6 +534,7 @@ export default class BrowserTable extends React.Component { return (
this.handleChange(newValue, name, type, targetClass)} onCancel={() => this.toggleObjectPicker(name, false)} useMasterKey={useMasterKey} + limit={this.props.limit} /> ) : (
this.toggleObjectPicker(name, false)} useMasterKey={useMasterKey} + limit={this.props.limit} /> ) : ( selectedObject.id && ( diff --git a/src/dashboard/Data/Browser/ObjectPickerDialog.react.js b/src/dashboard/Data/Browser/ObjectPickerDialog.react.js index f77ccf5cd4..a722d5a5c7 100644 --- a/src/dashboard/Data/Browser/ObjectPickerDialog.react.js +++ b/src/dashboard/Data/Browser/ObjectPickerDialog.react.js @@ -16,7 +16,6 @@ import stylesFooter from 'components/Modal/Modal.scss'; import { CurrentApp } from 'context/currentApp'; // The initial and max amount of rows fetched by lazy loading -const MAX_ROWS_FETCHED = 200; const SELECTION_INPUT_ID = 'selectionInput'; export default class ObjectPickerDialog extends React.Component { @@ -90,7 +89,7 @@ export default class ObjectPickerDialog extends React.Component { this.setState({ data: data, filters, - lastMax: MAX_ROWS_FETCHED, + lastMax: this.props.limit, filteredCounts: filteredCounts, }); } @@ -107,7 +106,7 @@ export default class ObjectPickerDialog extends React.Component { query.ascending(field); } - query.limit(MAX_ROWS_FETCHED); + query.limit(this.props.limit); const promise = query.find({ useMasterKey }); @@ -167,7 +166,7 @@ export default class ObjectPickerDialog extends React.Component { query.lessThan('createdAt', this.state.data[this.state.data.length - 1].get('createdAt')); query.addDescending('createdAt'); } - query.limit(MAX_ROWS_FETCHED); + query.limit(this.props.limit); const { useMasterKey } = this.props; query.find({ useMasterKey: useMasterKey }).then(nextPage => { @@ -177,7 +176,7 @@ export default class ObjectPickerDialog extends React.Component { })); } }); - this.setState({ lastMax: this.state.lastMax + MAX_ROWS_FETCHED }); + this.setState({ lastMax: this.state.lastMax + this.props.limit }); } async updateFilters(filters) { From 1218a1cdbf9fff3c3063c1d441a2c6cca4a76d32 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 4 Mar 2025 22:26:59 +1100 Subject: [PATCH 05/13] run prettier --- .../AggregationPanel/AggregationPanel.js | 43 +++++++------ .../AggregationPanelComponents.js | 12 +++- .../BrowserCell/BrowserCell.react.js | 2 +- src/components/BrowserRow/BrowserRow.react.js | 2 +- .../DataBrowserHeaderBar.react.js | 10 +-- src/components/Filter/Filter.react.js | 7 ++- src/components/Label/Label.react.js | 3 +- src/components/Toolbar/Toolbar.react.js | 32 +++++++--- src/dashboard/Dashboard.js | 17 +++--- src/dashboard/DashboardView.react.js | 12 ++-- src/dashboard/Data/Browser/Browser.react.js | 61 +++++++++++-------- .../Data/Browser/BrowserFooter.react.js | 10 +-- .../Data/Browser/BrowserToolbar.react.js | 4 +- .../Data/Browser/DataBrowser.react.js | 50 +++++++++------ .../Browser/ExecuteScriptRowsDialog.react.js | 6 +- src/dashboard/Data/Config/Config.react.js | 54 +++++++++------- .../Data/Config/ConfigDialog.react.js | 27 ++++---- .../Settings/Security/Security.react.js | 36 +++++++---- src/lib/ParseApp.js | 2 +- 19 files changed, 235 insertions(+), 155 deletions(-) diff --git a/src/components/AggregationPanel/AggregationPanel.js b/src/components/AggregationPanel/AggregationPanel.js index 1b64c0adb0..bd1ead90e3 100644 --- a/src/components/AggregationPanel/AggregationPanel.js +++ b/src/components/AggregationPanel/AggregationPanel.js @@ -38,16 +38,20 @@ const AggregationPanel = ({ } }, [errorAggregatedData, setSelectedObjectId, setErrorAggregatedData]); - const isLoading = useMemo(() => - depth === 0 && selectedObjectId && isLoadingCloudFunction && showAggregatedData, - [depth, selectedObjectId, isLoadingCloudFunction, showAggregatedData] + const isLoading = useMemo( + () => depth === 0 && selectedObjectId && isLoadingCloudFunction && showAggregatedData, + [depth, selectedObjectId, isLoadingCloudFunction, showAggregatedData] ); - const shouldShowAggregatedData = useMemo(() => - depth === 0 - ? (selectedObjectId && showAggregatedData && Object.keys(data).length !== 0 && Object.keys(errorAggregatedData).length === 0) - : true, - [depth, selectedObjectId, showAggregatedData, data, errorAggregatedData] + const shouldShowAggregatedData = useMemo( + () => + depth === 0 + ? selectedObjectId && + showAggregatedData && + Object.keys(data).length !== 0 && + Object.keys(errorAggregatedData).length === 0 + : true, + [depth, selectedObjectId, showAggregatedData, data, errorAggregatedData] ); const fetchNestedData = useCallback(async () => { @@ -137,8 +141,13 @@ const AggregationPanel = ({ if (depth > 0) { return (
-
- {panelTitle} +
+ + {panelTitle} +
{isExpanded && ( - )} - {isExpanded ? '▼' : '▲'} + {isExpanded ? '▼' : '▲'}
{isExpanded && ( @@ -160,7 +168,8 @@ const AggregationPanel = ({
) : ( - nestedData && nestedData.panel.segments.map((segment, index) => + nestedData && + nestedData.panel.segments.map((segment, index) => renderSegmentContent(segment, index) ) )} @@ -178,14 +187,10 @@ const AggregationPanel = ({
) : shouldShowAggregatedData ? (
- {data.panel.segments.map((segment, index) => - renderSegmentContent(segment, index) - )} + {data.panel.segments.map((segment, index) => renderSegmentContent(segment, index))}
) : ( -
- No object selected. -
+
No object selected.
)}
); diff --git a/src/components/AggregationPanel/AggregationPanelComponents.js b/src/components/AggregationPanel/AggregationPanelComponents.js index d81ca31569..d85d05eee6 100644 --- a/src/components/AggregationPanel/AggregationPanelComponents.js +++ b/src/components/AggregationPanel/AggregationPanelComponents.js @@ -12,7 +12,13 @@ export const TextElement = ({ text }) => ( export const KeyValueElement = ({ item, appName }) => (
{item.key}: - {item.url ? {item.value} : {item.value}} + {item.url ? ( + + {item.value} + + ) : ( + {item.value} + )}
); @@ -80,10 +86,10 @@ export const ButtonElement = ({ item, showNote }) => { .then(response => response.json()) .then(data => { const formattedData = JSON.stringify(data, null, 2); - showNote(`${formattedData}`,false) + showNote(`${formattedData}`, false); }) .catch(error => { - showNote(`${error}`,true) + showNote(`${error}`, true); }); }; diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index b794f13f61..1af38e72a0 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -577,7 +577,7 @@ export default class BrowserCell extends Component { markRequiredFieldRow, handleCellClick, selectedCells, - setShowAggregatedData + setShowAggregatedData, } = this.props; const classes = [...this.state.classes]; diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js index 0e6f40a694..7a3df557db 100644 --- a/src/components/BrowserRow/BrowserRow.react.js +++ b/src/components/BrowserRow/BrowserRow.react.js @@ -79,7 +79,7 @@ export default class BrowserRow extends Component { type="checkbox" checked={selection['*'] || selection[obj.id]} onChange={e => selectRow(obj.id, e.target.checked)} - onMouseDown={(e) => onMouseDownRowCheckBox(e.target.checked)} + onMouseDown={e => onMouseDownRowCheckBox(e.target.checked)} /> {order.map(({ name, width, visible }, j) => { diff --git a/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js b/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js index 1287e62ae5..a1daee294f 100644 --- a/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js +++ b/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js @@ -25,7 +25,7 @@ export default class DataBrowserHeaderBar extends React.Component { selected, isDataLoaded, setSelectedObjectId, - setCurrent + setCurrent, } = this.props; const elements = [
@@ -50,11 +50,11 @@ export default class DataBrowserHeaderBar extends React.Component { !preventSort && (type === 'String' || type === 'Number' || type === 'Date' || type === 'Boolean') ) { - onClick = () =>{ + onClick = () => { updateOrdering((order === 'descending' ? '' : '-') + name); setSelectedObjectId(null); - setCurrent(null) - } + setCurrent(null); + }; } let className = styles.wrap; @@ -83,7 +83,7 @@ export default class DataBrowserHeaderBar extends React.Component { const finalStyle = {}; if (headers.length % 2) { finalStyle.background = '#726F85'; - } else{ + } else { finalStyle.background = '#66637A'; } diff --git a/src/components/Filter/Filter.react.js b/src/components/Filter/Filter.react.js index 29397f4213..67592444ec 100644 --- a/src/components/Filter/Filter.react.js +++ b/src/components/Filter/Filter.react.js @@ -61,7 +61,8 @@ function changeConstraint(schema, currentClassName, filters, index, newConstrain class: currentClassName, field: field, constraint: newConstraint, - compareTo: (compareType && prevCompareTo) ? prevCompareTo : Filters.DefaultComparisons[compareType], + compareTo: + compareType && prevCompareTo ? prevCompareTo : Filters.DefaultComparisons[compareType], }); return filters.set(index, newFilter); } @@ -88,7 +89,7 @@ const Filter = ({ const [compare, setCompare] = useState(false); const hasCompareTo = filters.some(filter => filter.get('compareTo') !== undefined); - if(compare !== hasCompareTo){ + if (compare !== hasCompareTo) { setCompare(hasCompareTo); } const currentApp = React.useContext(CurrentApp); @@ -108,7 +109,7 @@ const Filter = ({ gap: '10px', padding: '12px 15px 0px 15px', color: '#343445', - 'font-weight': '600' + 'font-weight': '600', }} >
Class
diff --git a/src/components/Label/Label.react.js b/src/components/Label/Label.react.js index d48f5d276c..b2b3bb0021 100644 --- a/src/components/Label/Label.react.js +++ b/src/components/Label/Label.react.js @@ -15,7 +15,8 @@ const Label = props => { return (
+ style={{ padding: '0 ' + padding, ...props.style }} + >
{props.text}
{props.description ?
{props.description}
: null}
diff --git a/src/components/Toolbar/Toolbar.react.js b/src/components/Toolbar/Toolbar.react.js index 670cd205dc..e5e6642534 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, classwiseCloudFunctions, className, appId , appName}) => { +const Stats = ({ data, classwiseCloudFunctions, className, appId, appName }) => { const [selected, setSelected] = React.useState(null); const [open, setOpen] = React.useState(false); const buttonRef = React.useRef(); @@ -98,7 +98,12 @@ const Stats = ({ data, classwiseCloudFunctions, className, appId , appName}) => setSelected(statsOptions[0]); }, []); - const rightMarginStyle = classwiseCloudFunctions && classwiseCloudFunctions[`${appId}${appName}`] && classwiseCloudFunctions[`${appId}${appName}`][className] ? '120px' : 'initial'; + const rightMarginStyle = + classwiseCloudFunctions && + classwiseCloudFunctions[`${appId}${appName}`] && + classwiseCloudFunctions[`${appId}${appName}`][className] + ? '120px' + : 'initial'; return ( <> @@ -140,22 +145,29 @@ const Toolbar = props => {
- {props?.selectedData?.length ? : null} + {props?.selectedData?.length ? ( + + ) : null}
{props.children}
- {props.classwiseCloudFunctions && props.classwiseCloudFunctions[`${props.appId}${props.appName}`] && props.classwiseCloudFunctions[`${props.appId}${props.appName}`][props.className] && ( - diff --git a/src/dashboard/Dashboard.js b/src/dashboard/Dashboard.js index 0aebec1f25..96be0559be 100644 --- a/src/dashboard/Dashboard.js +++ b/src/dashboard/Dashboard.js @@ -180,7 +180,6 @@ export default class Dashboard extends React.Component { configLoadingState: AsyncStatus.FAILED, }); }); - } render() { @@ -216,14 +215,14 @@ export default class Dashboard extends React.Component { const SettingsRoute = ( }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> ); diff --git a/src/dashboard/DashboardView.react.js b/src/dashboard/DashboardView.react.js index 00ceb39223..061bcbf1de 100644 --- a/src/dashboard/DashboardView.react.js +++ b/src/dashboard/DashboardView.react.js @@ -195,16 +195,18 @@ export default class DashboardView extends React.Component { } */ - const settingsSections = [{ - name: 'Dashboard', - link: '/settings/dashboard' - }]; + const settingsSections = [ + { + name: 'Dashboard', + link: '/settings/dashboard', + }, + ]; if (this.context.enableSecurityChecks) { settingsSections.push({ name: 'Security', link: '/settings/security', - }) + }); } // Settings - nothing remotely like this in parse-server yet. Maybe it will arrive soon. diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 455e04bc8e..febab8041f 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -54,7 +54,7 @@ class Browser extends DashboardView { this.section = 'Core'; this.subsection = 'Browser'; this.noteTimeout = null; - const limit = window.localStorage?.getItem('browserLimit') + const limit = window.localStorage?.getItem('browserLimit'); this.state = { showCreateClassDialog: false, @@ -195,7 +195,6 @@ class Browser extends DashboardView { relation: null, }); }); - } componentWillMount() { @@ -279,7 +278,8 @@ class Browser extends DashboardView { useMasterKey: true, }; const appName = this.props.params.appId; - const cloudCodeFunction = this.state.classwiseCloudFunctions[`${appId}${appName}`]?.[className][0].cloudCodeFunction; + const cloudCodeFunction = + this.state.classwiseCloudFunctions[`${appId}${appName}`]?.[className][0].cloudCodeFunction; Parse.Cloud.run(cloudCodeFunction, params, options).then( result => { if (result && result.panel && result.panel && result.panel.segments) { @@ -289,7 +289,7 @@ class Browser extends DashboardView { isLoading: false, errorAggregatedData: 'Improper JSON format', }); - this.showNote(this.state.errorAggregatedData,true) + this.showNote(this.state.errorAggregatedData, true); } }, error => { @@ -297,7 +297,7 @@ class Browser extends DashboardView { isLoading: false, errorAggregatedData: error.message, }); - this.showNote(this.state.errorAggregatedData,true) + this.showNote(this.state.errorAggregatedData, true); } ); } @@ -335,18 +335,19 @@ class Browser extends DashboardView { data.apps.forEach(app => { const appName = app.appName; classMap[`${app.appId}${appName}`] = {}; - app.infoPanel && app.infoPanel.forEach(panel => { - panel.classes.forEach(className => { - if (!classMap[`${app.appId}${appName}`][className]) { - classMap[`${app.appId}${appName}`][className] = []; - } - classMap[`${app.appId}${appName}`][className].push({ - title: panel.title, - cloudCodeFunction: panel.cloudCodeFunction, - classes: panel.classes, + app.infoPanel && + app.infoPanel.forEach(panel => { + panel.classes.forEach(className => { + if (!classMap[`${app.appId}${appName}`][className]) { + classMap[`${app.appId}${appName}`][className] = []; + } + classMap[`${app.appId}${appName}`][className].push({ + title: panel.title, + cloudCodeFunction: panel.cloudCodeFunction, + classes: panel.classes, + }); }); }); - }); }); this.setState({ classwiseCloudFunctions: classMap }); @@ -896,7 +897,7 @@ class Browser extends DashboardView { } async fetchParseData(source, filters) { - const { useMasterKey, skip, limit } = this.state; + const { useMasterKey, skip, limit } = this.state; const query = await queryFromFilters(source, filters); const sortDir = this.state.ordering[0] === '-' ? '-' : '+'; const field = this.state.ordering.substr(sortDir === '-' ? 1 : 0); @@ -1059,7 +1060,7 @@ class Browser extends DashboardView { this.setState({ skip: 0, - }) + }); } saveFilters(filters, name) { @@ -1171,7 +1172,11 @@ class Browser extends DashboardView { }, ]); window.open( - generatePath(this.context, `browser/${className}?filters=${encodeURIComponent(filters)}`, true), + generatePath( + this.context, + `browser/${className}?filters=${encodeURIComponent(filters)}`, + true + ), '_blank' ); } @@ -1380,7 +1385,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 ' + @@ -1548,17 +1553,19 @@ class Browser extends DashboardView { this.setState(prevState => ({ processedScripts: prevState.processedScripts + 1, })); - const note = (typeof response === 'object' ? JSON.stringify(response) : response) || `Ran script "${script.title}" on "${object.id}".`; + const note = + (typeof response === 'object' ? JSON.stringify(response) : response) || + `Ran script "${script.title}" on "${object.id}".`; this.showNote(note); } this.refresh(); } catch (e) { this.showNote(e.message, true); console.log(`Could not run ${script.title}: ${e}`); - } finally{ - this.setState(({ + } finally { + this.setState({ processedScripts: 0, - })); + }); } } @@ -2081,19 +2088,19 @@ class Browser extends DashboardView { setAggregationPanelData={this.setAggregationPanelData} setErrorAggregatedData={this.setErrorAggregatedData} errorAggregatedData={this.state.errorAggregatedData} - appName = {this.props.params.appId} + appName={this.props.params.appId} limit={this.state.limit} /> { + setSkip={skip => { this.setState({ skip }); this.updateOrdering(this.state.ordering); }} count={this.state.counts[className]} limit={this.state.limit} - setLimit={(limit) => { - this.setState({ limit }) + setLimit={limit => { + this.setState({ limit }); this.updateOrdering(this.state.ordering); }} /> diff --git a/src/dashboard/Data/Browser/BrowserFooter.react.js b/src/dashboard/Data/Browser/BrowserFooter.react.js index 6dc6a38b02..52df67a788 100644 --- a/src/dashboard/Data/Browser/BrowserFooter.react.js +++ b/src/dashboard/Data/Browser/BrowserFooter.react.js @@ -18,14 +18,14 @@ class BrowserFooter extends React.Component { } } - handleLimitChange = (event) => { + handleLimitChange = event => { const newLimit = parseInt(event.target.value, 10); this.props.setLimit(newLimit); this.props.setSkip(0); this.setState({ pageInput: '1' }); }; - handlePageChange = (newSkip) => { + handlePageChange = newSkip => { if (newSkip >= 0 && newSkip < this.props.count) { this.props.setSkip(newSkip); this.setState({ pageInput: (Math.floor(newSkip / this.props.limit) + 1).toString() }); @@ -35,7 +35,7 @@ class BrowserFooter extends React.Component { table.scrollTo({ top: 0 }); }; - handleInputChange = (e) => { + handleInputChange = e => { this.setState({ pageInput: e.target.value }); }; @@ -53,7 +53,7 @@ class BrowserFooter extends React.Component { this.handlePageChange((newPage - 1) * limit); }; - handleKeyDown = (e) => { + handleKeyDown = e => { if (e.key === 'Enter') { this.validateAndApplyPage(); } @@ -69,7 +69,7 @@ class BrowserFooter extends React.Component { {count?.toLocaleString() || 0} objects {[10, 20, 50, 100, 200, 500, 1000].map(size => (