From 9742e3e6d1d1c7062d1e8f4ac47fca56a4c414e8 Mon Sep 17 00:00:00 2001 From: dblythy Date: Sat, 11 Mar 2023 12:59:57 +1100 Subject: [PATCH 01/12] feat: create predefined filters --- .../Autocomplete/Autocomplete.react.js | 8 +- .../BrowserFilter/BrowserFilter.react.js | 42 +- .../CategoryList/CategoryList.react.js | 99 +- src/components/CategoryList/CategoryList.scss | 27 +- src/dashboard/Data/Browser/Browser.react.js | 1073 +++++++++++------ .../Data/Browser/BrowserToolbar.react.js | 2 + .../Data/Browser/DataBrowser.react.js | 1 + src/lib/ClassPreferences.js | 41 + 8 files changed, 870 insertions(+), 423 deletions(-) create mode 100644 src/lib/ClassPreferences.js diff --git a/src/components/Autocomplete/Autocomplete.react.js b/src/components/Autocomplete/Autocomplete.react.js index be6d4e953f..785f61656b 100644 --- a/src/components/Autocomplete/Autocomplete.react.js +++ b/src/components/Autocomplete/Autocomplete.react.js @@ -235,7 +235,7 @@ export default class Autocomplete extends Component { // Tab // do not type it e.preventDefault(); - + e.stopPropagation(); // move focus to input this.inputRef.current.focus(); @@ -318,7 +318,7 @@ export default class Autocomplete extends Component { onClick={onClick} /> ); - } + } return ( @@ -372,5 +372,5 @@ Autocomplete.propTypes = { ), error: PropTypes.string.describe( 'Error to be rendered in place of label if defined' - ) -} + ) +} diff --git a/src/components/BrowserFilter/BrowserFilter.react.js b/src/components/BrowserFilter/BrowserFilter.react.js index 17de624df6..75b7b8b1fa 100644 --- a/src/components/BrowserFilter/BrowserFilter.react.js +++ b/src/components/BrowserFilter/BrowserFilter.react.js @@ -11,6 +11,9 @@ import Filter from 'components/Filter/Filter.react'; import FilterRow from 'components/BrowserFilter/FilterRow.react'; import Icon from 'components/Icon/Icon.react'; import Popover from 'components/Popover/Popover.react'; +import Field from 'components/Field/Field.react'; +import TextInput from 'components/TextInput/TextInput.react'; +import Label from 'components/Label/Label.react'; import Position from 'lib/Position'; import React from 'react'; import styles from 'components/BrowserFilter/BrowserFilter.scss'; @@ -25,9 +28,12 @@ export default class BrowserFilter extends React.Component { this.state = { open: false, filters: new List(), + confirmName: false, + name: '', blacklistedFilters: Filters.BLACKLISTED_FILTERS.concat(props.blacklistedFilters) }; this.toggle = this.toggle.bind(this); + this.save = this.save.bind(this); this.wrapRef = React.createRef(); } @@ -48,9 +54,12 @@ export default class BrowserFilter extends React.Component { } this.setState(prevState => ({ open: !prevState.open, - filters: filters + filters: filters, + name: '', + confirmName: false })); this.props.setCurrent(null); + console.log('isOpen', this.state.open); } addRow() { @@ -86,6 +95,19 @@ export default class BrowserFilter extends React.Component { this.props.onChange(formatted); } + save() { + let formatted = this.state.filters.map(filter => { + let isComparable = Filters.Constraints[filter.get('constraint')].comparable; + if (!isComparable) { + return filter.delete('compareTo') + } + return filter; + }); + console.log('save clicked') + this.props.onSaveFilter(formatted, this.state.name); + this.toggle(); + } + render() { let popover = null; let buttonStyle = [styles.entry]; @@ -118,7 +140,11 @@ export default class BrowserFilter extends React.Component { renderRow={props => ( 0} parentContentId={POPOVER_CONTENT_ID} /> )} - /> + /> + {this.state.confirmName && } + input={ this.setState({ name })} />} + />}
diff --git a/src/components/CategoryList/CategoryList.react.js b/src/components/CategoryList/CategoryList.react.js index 286a040cbc..8b3baa804a 100644 --- a/src/components/CategoryList/CategoryList.react.js +++ b/src/components/CategoryList/CategoryList.react.js @@ -5,18 +5,22 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/CategoryList/CategoryList.scss'; -import { Link } from 'react-router-dom'; -import generatePath from 'lib/generatePath'; +import PropTypes from 'lib/PropTypes'; +import React from 'react'; +import styles from 'components/CategoryList/CategoryList.scss'; +import { Link } from 'react-router-dom'; +import generatePath from 'lib/generatePath'; import { CurrentApp } from 'context/currentApp'; +import SidebarAction from 'components/Sidebar/SidebarAction'; export default class CategoryList extends React.Component { static contextType = CurrentApp; constructor() { super(); this.listWrapperRef = React.createRef(); + this.state = { + openClasses: [], + }; } componentDidMount() { @@ -46,7 +50,7 @@ export default class CategoryList extends React.Component { let id = c.id || c.name; if (id === this.props.current) { this.highlight.style.display = 'block'; - this.highlight.style.top = (i * 20) + 'px'; + this.highlight.style.top = i * 20 + 'px'; return; } } @@ -54,13 +58,25 @@ export default class CategoryList extends React.Component { } } + toggleDropdown(e, id) { + e.preventDefault(); + const openClasses = [...this.state.openClasses]; + const index = openClasses.indexOf(id); + if (openClasses.includes(id)) { + openClasses.splice(index, 1); + } else { + openClasses.push(id); + } + this.setState({ openClasses }); + } + render() { if (this.props.categories.length === 0) { return null; } return (
- {this.props.categories.map((c) => { + {this.props.categories.map((c, index) => { let id = c.id || c.name; if (c.type === 'separator') { return
; @@ -72,10 +88,59 @@ export default class CategoryList extends React.Component { (this.props.linkPrefix || '') + (c.link || id) ); return ( - - {count} - {c.name} - +
+ + {this.state.openClasses.includes(id) && + c.filters.map((filterData, index) => { + console.log({ filterData }); + const { name, filter } = filterData; + const url = `${this.props.linkPrefix}${ + c.name + }?filters=${encodeURIComponent(filter)}`; + return ( +
+ { + e.preventDefault(); + this.props.filterClicked(url); + }} + key={name + index} + style={{display: 'flex'}} + > + {name} + + { + e.preventDefault(); + this.props.removeFilter(filterData); + }} + >× +
+ ); + })} +
); })}
@@ -84,7 +149,13 @@ export default class CategoryList extends React.Component { } CategoryList.propTypes = { - categories: PropTypes.arrayOf(PropTypes.object).describe('Array of categories used to populate list.'), - current: PropTypes.string.describe('Id of current category to be highlighted.'), - linkPrefix: PropTypes.string.describe('Link prefix used to generate link path.'), + categories: PropTypes.arrayOf(PropTypes.object).describe( + 'Array of categories used to populate list.' + ), + current: PropTypes.string.describe( + 'Id of current category to be highlighted.' + ), + linkPrefix: PropTypes.string.describe( + 'Link prefix used to generate link path.' + ), }; diff --git a/src/components/CategoryList/CategoryList.scss b/src/components/CategoryList/CategoryList.scss index f24bd69580..c0f0e4fb23 100644 --- a/src/components/CategoryList/CategoryList.scss +++ b/src/components/CategoryList/CategoryList.scss @@ -37,7 +37,6 @@ width: 50px; text-align: right; } - &:last-of-type { margin-right: 50px; overflow: hidden; @@ -62,3 +61,29 @@ border: 0; height: 1px; } + +.close { + font-size: 20px !important; + font-weight: bold !important; +} +.expand { + display: flex !important; + align-items: center; + cursor: pointer; + width: 0px; + margin-right: 20px; + padding-left: 0px !important; + &:after { + @include arrow('down', 10px, 7px, #fff); + content: ''; + margin-left: 10px; + } +} +.link { + display: flex; + a { + &:first-of-type { + flex-grow: 1 + } + } +} diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 46e18e8947..5b819599db 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -5,37 +5,38 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import { ActionTypes } from 'lib/stores/SchemaStore'; -import AddColumnDialog from 'dashboard/Data/Browser/AddColumnDialog.react'; -import CategoryList from 'components/CategoryList/CategoryList.react'; -import CreateClassDialog from 'dashboard/Data/Browser/CreateClassDialog.react'; -import DashboardView from 'dashboard/DashboardView.react'; -import DataBrowser from 'dashboard/Data/Browser/DataBrowser.react'; +import { ActionTypes } from 'lib/stores/SchemaStore'; +import AddColumnDialog from 'dashboard/Data/Browser/AddColumnDialog.react'; +import CategoryList from 'components/CategoryList/CategoryList.react'; +import CreateClassDialog from 'dashboard/Data/Browser/CreateClassDialog.react'; +import DashboardView from 'dashboard/DashboardView.react'; +import DataBrowser from 'dashboard/Data/Browser/DataBrowser.react'; import { DefaultColumns, SpecialClasses } from 'lib/Constants'; -import DeleteRowsDialog from 'dashboard/Data/Browser/DeleteRowsDialog.react'; -import DropClassDialog from 'dashboard/Data/Browser/DropClassDialog.react'; -import EmptyState from 'components/EmptyState/EmptyState.react'; -import ExportDialog from 'dashboard/Data/Browser/ExportDialog.react'; -import AttachRowsDialog from 'dashboard/Data/Browser/AttachRowsDialog.react'; -import AttachSelectedRowsDialog from 'dashboard/Data/Browser/AttachSelectedRowsDialog.react'; -import CloneSelectedRowsDialog from 'dashboard/Data/Browser/CloneSelectedRowsDialog.react'; -import EditRowDialog from 'dashboard/Data/Browser/EditRowDialog.react'; -import ExportSelectedRowsDialog from 'dashboard/Data/Browser/ExportSelectedRowsDialog.react'; -import ExportSchemaDialog from 'dashboard/Data/Browser/ExportSchemaDialog.react'; -import { List, Map } from 'immutable'; -import Notification from 'dashboard/Data/Browser/Notification.react'; -import Parse from 'parse'; -import prettyNumber from 'lib/prettyNumber'; -import queryFromFilters from 'lib/queryFromFilters'; -import React from 'react'; -import RemoveColumnDialog from 'dashboard/Data/Browser/RemoveColumnDialog.react'; -import PointerKeyDialog from 'dashboard/Data/Browser/PointerKeyDialog.react'; -import SidebarAction from 'components/Sidebar/SidebarAction'; -import stringCompare from 'lib/stringCompare'; -import styles from 'dashboard/Data/Browser/Browser.scss'; -import subscribeTo from 'lib/subscribeTo'; -import * as ColumnPreferences from 'lib/ColumnPreferences'; -import { Helmet } from 'react-helmet'; +import DeleteRowsDialog from 'dashboard/Data/Browser/DeleteRowsDialog.react'; +import DropClassDialog from 'dashboard/Data/Browser/DropClassDialog.react'; +import EmptyState from 'components/EmptyState/EmptyState.react'; +import ExportDialog from 'dashboard/Data/Browser/ExportDialog.react'; +import AttachRowsDialog from 'dashboard/Data/Browser/AttachRowsDialog.react'; +import AttachSelectedRowsDialog from 'dashboard/Data/Browser/AttachSelectedRowsDialog.react'; +import CloneSelectedRowsDialog from 'dashboard/Data/Browser/CloneSelectedRowsDialog.react'; +import EditRowDialog from 'dashboard/Data/Browser/EditRowDialog.react'; +import ExportSelectedRowsDialog from 'dashboard/Data/Browser/ExportSelectedRowsDialog.react'; +import ExportSchemaDialog from 'dashboard/Data/Browser/ExportSchemaDialog.react'; +import { List, Map } from 'immutable'; +import Notification from 'dashboard/Data/Browser/Notification.react'; +import Parse from 'parse'; +import prettyNumber from 'lib/prettyNumber'; +import queryFromFilters from 'lib/queryFromFilters'; +import React from 'react'; +import RemoveColumnDialog from 'dashboard/Data/Browser/RemoveColumnDialog.react'; +import PointerKeyDialog from 'dashboard/Data/Browser/PointerKeyDialog.react'; +import SidebarAction from 'components/Sidebar/SidebarAction'; +import stringCompare from 'lib/stringCompare'; +import styles from 'dashboard/Data/Browser/Browser.scss'; +import subscribeTo from 'lib/subscribeTo'; +import * as ColumnPreferences from 'lib/ColumnPreferences'; +import * as ClassPreferences from 'lib/ClassPreferences'; +import { Helmet } from 'react-helmet'; import generatePath from 'lib/generatePath'; import { withRouter } from 'lib/withRouter'; @@ -101,6 +102,9 @@ class Browser extends DashboardView { this.fetchRelationCount = this.fetchRelationCount.bind(this); this.fetchNextPage = this.fetchNextPage.bind(this); this.updateFilters = this.updateFilters.bind(this); + this.saveFilters = this.saveFilters.bind(this); + this.filterClicked = this.filterClicked.bind(this); + this.removeFilter = this.removeFilter.bind(this); this.showRemoveColumn = this.showRemoveColumn.bind(this); this.showDeleteRows = this.showDeleteRows.bind(this); this.showDropClass = this.showDropClass.bind(this); @@ -111,13 +115,16 @@ class Browser extends DashboardView { this.showAttachRowsDialog = this.showAttachRowsDialog.bind(this); this.cancelAttachRows = this.cancelAttachRows.bind(this); this.confirmAttachRows = this.confirmAttachRows.bind(this); - this.showAttachSelectedRowsDialog = this.showAttachSelectedRowsDialog.bind(this); + this.showAttachSelectedRowsDialog = + this.showAttachSelectedRowsDialog.bind(this); this.confirmAttachSelectedRows = this.confirmAttachSelectedRows.bind(this); this.cancelAttachSelectedRows = this.cancelAttachSelectedRows.bind(this); - this.showCloneSelectedRowsDialog = this.showCloneSelectedRowsDialog.bind(this); + this.showCloneSelectedRowsDialog = + this.showCloneSelectedRowsDialog.bind(this); this.confirmCloneSelectedRows = this.confirmCloneSelectedRows.bind(this); this.cancelCloneSelectedRows = this.cancelCloneSelectedRows.bind(this); - this.showExportSelectedRowsDialog = this.showExportSelectedRowsDialog.bind(this); + this.showExportSelectedRowsDialog = + this.showExportSelectedRowsDialog.bind(this); this.showExportSchemaDialog = this.showExportSchemaDialog.bind(this); this.confirmExportSelectedRows = this.confirmExportSelectedRows.bind(this); this.cancelExportSelectedRows = this.cancelExportSelectedRows.bind(this); @@ -158,19 +165,23 @@ class Browser extends DashboardView { window.addEventListener('popstate', () => { this.setState({ - relation: null - }) + relation: null, + }); }); } componentWillMount() { const currentApp = this.context; if (!currentApp.preventSchemaEdits) { - this.action = new SidebarAction('Create a class', this.showCreateClass.bind(this)); + this.action = new SidebarAction( + 'Create a class', + this.showCreateClass.bind(this) + ); } - this.props.schema.dispatch(ActionTypes.FETCH) - .then(() => this.handleFetchedSchema()); + this.props.schema + .dispatch(ActionTypes.FETCH) + .then(() => this.handleFetchedSchema()); if (!this.props.params.className && this.props.schema.data.get('classes')) { this.redirectToFirstClass(this.props.schema.data.get('classes')); } else if (this.props.params.className) { @@ -179,18 +190,29 @@ class Browser extends DashboardView { } componentWillReceiveProps(nextProps, nextContext) { - if (this.props.params.appId !== nextProps.params.appId || this.props.params.className !== nextProps.params.className || this.props.location.search !== nextProps.location.search) { - if (this.props.params.appId !== nextProps.params.appId || !this.props.params.className) { + if ( + this.props.params.appId !== nextProps.params.appId || + this.props.params.className !== nextProps.params.className || + this.props.location.search !== nextProps.location.search + ) { + if ( + this.props.params.appId !== nextProps.params.appId || + !this.props.params.className + ) { this.setState({ counts: {} }); Parse.Object._clearAllState(); - nextProps.schema.dispatch(ActionTypes.FETCH) + nextProps.schema + .dispatch(ActionTypes.FETCH) .then(() => this.handleFetchedSchema()); } this.prefetchData(nextProps, nextContext); } if (!nextProps.params.className && nextProps.schema.data.get('classes')) { - this.redirectToFirstClass(nextProps.schema.data.get('classes'), nextContext); + this.redirectToFirstClass( + nextProps.schema.data.get('classes'), + nextContext + ); } } @@ -205,24 +227,27 @@ class Browser extends DashboardView { const parent = await parentObjectQuery.get(entityId, { useMasterKey }); relation = parent.relation(relationName); } - this.setState({ - data: null, - newObject: null, - lastMax: -1, - ordering: ColumnPreferences.getColumnSort( - false, - context.applicationId, - className, - ), - selection: {}, - relation: isRelationRoute ? relation : null, - }, () => { - if (isRelationRoute) { - this.fetchRelation(relation, filters); - } else if (className) { - this.fetchData(className, filters); + this.setState( + { + data: null, + newObject: null, + lastMax: -1, + ordering: ColumnPreferences.getColumnSort( + false, + context.applicationId, + className + ), + selection: {}, + relation: isRelationRoute ? relation : null, + }, + () => { + if (isRelationRoute) { + this.fetchRelation(relation, filters); + } else if (className) { + this.fetchData(className, filters); + } } - }); + ); } extractFiltersFromQuery(props) { @@ -234,7 +259,9 @@ class Browser extends DashboardView { const query = new URLSearchParams(props.location.search); if (query.has('filters')) { const queryFilters = JSON.parse(query.get('filters')); - queryFilters.forEach((filter) => filters = filters.push(new Map(filter))); + queryFilters.forEach( + (filter) => (filters = filters.push(new Map(filter))) + ); } return filters; } @@ -251,7 +278,10 @@ class Browser extends DashboardView { } return a.toUpperCase() < b.toUpperCase() ? -1 : 1; }); - this.props.navigate(generatePath(context || this.context, 'browser/' + classes[0]), { replace: true }); + this.props.navigate( + generatePath(context || this.context, 'browser/' + classes[0]), + { replace: true } + ); } } @@ -288,12 +318,16 @@ class Browser extends DashboardView { } const currentUser = await Parse.User.logIn(username, password); - this.setState({ currentUser: currentUser, useMasterKey: false }, () => this.refresh()); + this.setState({ currentUser: currentUser, useMasterKey: false }, () => + this.refresh() + ); } async logout() { await Parse.User.logOut(); - this.setState({ currentUser: null, useMasterKey: true }, () => this.refresh()); + this.setState({ currentUser: null, useMasterKey: true }, () => + this.refresh() + ); } toggleMasterKeyUsage() { @@ -302,27 +336,33 @@ class Browser extends DashboardView { } createClass(className) { - this.props.schema.dispatch(ActionTypes.CREATE_CLASS, { className }).then(() => { - this.state.counts[className] = 0; - this.props.navigate(generatePath(this.context, 'browser/' + className)); - }).finally(() => { - this.setState({ showCreateClassDialog: false }); - }); + this.props.schema + .dispatch(ActionTypes.CREATE_CLASS, { className }) + .then(() => { + this.state.counts[className] = 0; + this.props.navigate(generatePath(this.context, 'browser/' + className)); + }) + .finally(() => { + this.setState({ showCreateClassDialog: false }); + }); } dropClass(className) { - this.props.schema.dispatch(ActionTypes.DROP_CLASS, { className }).then(() => { - this.setState({showDropClassDialog: false }); - delete this.state.counts[className]; - this.props.navigate(generatePath(this.context, 'browser')); - }, (error) => { - let msg = typeof error === 'string' ? error : error.message; - if (msg) { - msg = msg[0].toUpperCase() + msg.substr(1); - } + this.props.schema.dispatch(ActionTypes.DROP_CLASS, { className }).then( + () => { + this.setState({ showDropClassDialog: false }); + delete this.state.counts[className]; + this.props.navigate(generatePath(this.context, 'browser')); + }, + (error) => { + let msg = typeof error === 'string' ? error : error.message; + if (msg) { + msg = msg[0].toUpperCase() + msg.substr(1); + } - this.showNote(msg, true); - }); + this.showNote(msg, true); + } + ); } exportClass(className) { @@ -342,16 +382,9 @@ class Browser extends DashboardView { schema = await new Parse.Schema(className).get(); } const element = document.createElement('a'); - const file = new Blob( - [ - JSON.stringify( - schema, - null, - 2, - ), - ], - { type: 'application/json' } - ); + const file = new Blob([JSON.stringify(schema, null, 2)], { + type: 'application/json', + }); element.href = URL.createObjectURL(file); element.download = `${all ? 'schema' : className}.json`; document.body.appendChild(element); // Required for this to work in FireFox @@ -363,12 +396,13 @@ class Browser extends DashboardView { } newColumn(payload, required) { - return this.props.schema.dispatch(ActionTypes.ADD_COLUMN, payload) + return this.props.schema + .dispatch(ActionTypes.ADD_COLUMN, payload) .then(() => { if (required) { let requiredCols = [...this.state.requiredColumnFields, name]; this.setState({ - requiredColumnFields: requiredCols + requiredColumnFields: requiredCols, }); } }) @@ -384,7 +418,7 @@ class Browser extends DashboardView { name: name, targetClass: target, required, - defaultValue + defaultValue, }; this.newColumn(payload, required).finally(() => { this.setState({ showAddColumnDialog: false, keepAddingCols: false }); @@ -398,7 +432,7 @@ class Browser extends DashboardView { name: name, targetClass: target, required, - defaultValue + defaultValue, }; this.newColumn(payload, required).finally(() => { this.setState({ showAddColumnDialog: false, keepAddingCols: false }); @@ -410,27 +444,27 @@ class Browser extends DashboardView { if (!this.state.newObject) { const relation = this.state.relation; this.setState({ - newObject: (relation ? - new Parse.Object(relation.targetClassName) - : new Parse.Object(this.props.params.className) ), + newObject: relation + ? new Parse.Object(relation.targetClassName) + : new Parse.Object(this.props.params.className), }); } } - abortAddRow(){ - if(this.state.newObject){ + abortAddRow() { + if (this.state.newObject) { this.setState({ - newObject: null + newObject: null, }); } if (this.state.markRequiredFieldRow !== 0) { this.setState({ - markRequiredFieldRow: 0 + markRequiredFieldRow: 0, }); } } - saveNewRow(){ + saveNewRow() { const { useMasterKey } = this.state; const obj = this.state.newObject; if (!obj) { @@ -443,21 +477,27 @@ class Browser extends DashboardView { if (className) { let classColumns = this.props.schema.data.get('classes').get(className); classColumns.forEach(({ required }, name) => { - if (name === 'objectId' || this.state.isUnique && name !== this.state.uniqueField) { - return; - } - if (required) { - requiredCols.push(name); - } - if (className === '_User' && (name === 'username' || name === 'password')) { - if (!obj.get('authData')) { - requiredCols.push(name); - } - } - if (className === '_Role' && (name === 'name' || name === 'ACL')) { + if ( + name === 'objectId' || + (this.state.isUnique && name !== this.state.uniqueField) + ) { + return; + } + if (required) { + requiredCols.push(name); + } + if ( + className === '_User' && + (name === 'username' || name === 'password') + ) { + if (!obj.get('authData')) { requiredCols.push(name); } - }); + } + if (className === '_Role' && (name === 'name' || name === 'ACL')) { + requiredCols.push(name); + } + }); } if (requiredCols.length) { for (let idx = 0; idx < requiredCols.length; idx++) { @@ -465,7 +505,7 @@ class Browser extends DashboardView { if (obj.get(name) == null) { this.showNote('Please enter all required fields', true); this.setState({ - markRequiredFieldRow: -1 + markRequiredFieldRow: -1, }); return; } @@ -473,12 +513,13 @@ class Browser extends DashboardView { } if (this.state.markRequiredFieldRow) { this.setState({ - markRequiredFieldRow: 0 + markRequiredFieldRow: 0, }); } obj.save(null, { useMasterKey }).then( - objectSaved => { - let msg = objectSaved.className + ' with id \'' + objectSaved.id + '\' created'; + (objectSaved) => { + let msg = + objectSaved.className + " with id '" + objectSaved.id + "' created"; this.showNote(msg, false); const state = { data: this.state.data }; @@ -496,11 +537,11 @@ class Browser extends DashboardView { relationCount: this.state.relationCount + 1, counts: { ...this.state.counts, - [targetClassName]: this.state.counts[targetClassName] + 1 - } + [targetClassName]: this.state.counts[targetClassName] + 1, + }, }); }, - error => { + (error) => { let msg = typeof error === 'string' ? error : error.message; if (msg) { msg = msg[0].toUpperCase() + msg.substr(1); @@ -520,7 +561,7 @@ class Browser extends DashboardView { this.setState(state); }, - error => { + (error) => { let msg = typeof error === 'string' ? error : error.message; if (msg) { msg = msg[0].toUpperCase() + msg.substr(1); @@ -533,9 +574,10 @@ class Browser extends DashboardView { saveEditCloneRow(rowIndex) { let obj; if (rowIndex < -1) { - obj = this.state.editCloneRows[ - rowIndex + (this.state.editCloneRows.length + 1) - ]; + obj = + this.state.editCloneRows[ + rowIndex + (this.state.editCloneRows.length + 1) + ]; } if (!obj) { return; @@ -547,21 +589,27 @@ class Browser extends DashboardView { if (className) { let classColumns = this.props.schema.data.get('classes').get(className); classColumns.forEach(({ required }, name) => { - if (name === 'objectId' || this.state.isUnique && name !== this.state.uniqueField) { - return; - } - if (required) { - requiredCols.push(name); - } - if (className === '_User' && (name === 'username' || name === 'password')) { - if (!obj.get('authData')) { - requiredCols.push(name); - } - } - if (className === '_Role' && (name === 'name' || name === 'ACL')) { + if ( + name === 'objectId' || + (this.state.isUnique && name !== this.state.uniqueField) + ) { + return; + } + if (required) { + requiredCols.push(name); + } + if ( + className === '_User' && + (name === 'username' || name === 'password') + ) { + if (!obj.get('authData')) { requiredCols.push(name); } - }); + } + if (className === '_Role' && (name === 'name' || name === 'ACL')) { + requiredCols.push(name); + } + }); } if (requiredCols.length) { for (let idx = 0; idx < requiredCols.length; idx++) { @@ -569,7 +617,7 @@ class Browser extends DashboardView { if (obj.get(name) == null) { this.showNote('Please enter all required fields', true); this.setState({ - markRequiredFieldRow: rowIndex + markRequiredFieldRow: rowIndex, }); return; } @@ -577,47 +625,59 @@ class Browser extends DashboardView { } if (this.state.markRequiredFieldRow) { this.setState({ - markRequiredFieldRow: 0 + markRequiredFieldRow: 0, }); } - obj.save(null, { useMasterKey: true }).then((objectSaved) => { - let msg = objectSaved.className + ' with id \'' + objectSaved.id + '\' ' + 'created'; - this.showNote(msg, false); + obj.save(null, { useMasterKey: true }).then( + (objectSaved) => { + let msg = + objectSaved.className + + " with id '" + + objectSaved.id + + "' " + + 'created'; + this.showNote(msg, false); - const state = { data: this.state.data, editCloneRows: this.state.editCloneRows }; - state.editCloneRows = state.editCloneRows.filter( - cloneObj => cloneObj._localId !== obj._localId - ); - if (state.editCloneRows.length === 0) state.editCloneRows = null; - if (this.props.params.className === obj.className) { - this.state.data.unshift(obj); - } - this.state.counts[obj.className] += 1; - this.setState(state); - }, (error) => { - let msg = typeof error === 'string' ? error : error.message; - if (msg) { - msg = msg[0].toUpperCase() + msg.substr(1); - } + const state = { + data: this.state.data, + editCloneRows: this.state.editCloneRows, + }; + state.editCloneRows = state.editCloneRows.filter( + (cloneObj) => cloneObj._localId !== obj._localId + ); + if (state.editCloneRows.length === 0) state.editCloneRows = null; + if (this.props.params.className === obj.className) { + this.state.data.unshift(obj); + } + this.state.counts[obj.className] += 1; + this.setState(state); + }, + (error) => { + let msg = typeof error === 'string' ? error : error.message; + if (msg) { + msg = msg[0].toUpperCase() + msg.substr(1); + } - this.showNote(msg, true); - }); + this.showNote(msg, true); + } + ); } abortEditCloneRow(rowIndex) { let obj; if (rowIndex < -1) { - obj = this.state.editCloneRows[ - rowIndex + (this.state.editCloneRows.length + 1) - ]; + obj = + this.state.editCloneRows[ + rowIndex + (this.state.editCloneRows.length + 1) + ]; } if (!obj) { return; } const state = { editCloneRows: this.state.editCloneRows }; state.editCloneRows = state.editCloneRows.filter( - cloneObj => cloneObj._localId !== obj._localId + (cloneObj) => cloneObj._localId !== obj._localId ); if (state.editCloneRows.length === 0) state.editCloneRows = null; this.setState(state); @@ -631,20 +691,20 @@ class Browser extends DashboardView { cancelPendingEditRows() { this.setState({ - editCloneRows: null + editCloneRows: null, }); } addEditCloneRows(cloneRows) { this.setState({ - editCloneRows: cloneRows + editCloneRows: cloneRows, }); } removeColumn(name) { let payload = { className: this.props.params.className, - name: name + name: name, }; this.props.schema.dispatch(ActionTypes.DROP_COLUMN, payload).finally(() => { let state = { showRemoveColumnDialog: false }; @@ -667,7 +727,7 @@ class Browser extends DashboardView { this.setState({ clp: this.props.schema.data.get('CLPs').toJS(), counts, - computingClassCounts: false + computingClassCounts: false, }); } } @@ -698,12 +758,12 @@ class Browser extends DashboardView { const { useMasterKey } = this.state; const query = queryFromFilters(source, filters); const sortDir = this.state.ordering[0] === '-' ? '-' : '+'; - const field = this.state.ordering.substr(sortDir === '-' ? 1 : 0) + const field = this.state.ordering.substr(sortDir === '-' ? 1 : 0); if (sortDir === '-') { - query.descending(field) + query.descending(field); } else { - query.ascending(field) + query.ascending(field); } query.limit(MAX_ROWS_FETCHED); @@ -727,13 +787,21 @@ class Browser extends DashboardView { } excludeFields(query, className) { - let columns = ColumnPreferences.getPreferences(this.context.applicationId, className); + let columns = ColumnPreferences.getPreferences( + this.context.applicationId, + className + ); if (columns) { - columns = columns.filter(clmn => !clmn.visible).map(clmn => clmn.name); + columns = columns + .filter((clmn) => !clmn.visible) + .map((clmn) => clmn.name); for (let columnsKey in columns) { query.exclude(columns[columnsKey]); } - ColumnPreferences.updateCachedColumns(this.context.applicationId, className); + ColumnPreferences.updateCachedColumns( + this.context.applicationId, + className + ); } } @@ -751,12 +819,20 @@ class Browser extends DashboardView { if (this.state.isUnique) { filteredCounts[source] = data.length; } else { - filteredCounts[source] = await this.fetchParseDataCount(source, filters); + filteredCounts[source] = await this.fetchParseDataCount( + source, + filters + ); } } else { delete filteredCounts[source]; } - this.setState({ data: data, filters, lastMax: MAX_ROWS_FETCHED , filteredCounts: filteredCounts}); + this.setState({ + data: data, + filters, + lastMax: MAX_ROWS_FETCHED, + filteredCounts: filteredCounts, + }); } async fetchRelation(relation, filters = new List()) { @@ -800,9 +876,15 @@ class Browser extends DashboardView { query.greaterThan(field, comp); } if (field === 'createdAt') { - equalityQuery.greaterThan('createdAt', this.state.data[this.state.data.length - 1].get('createdAt')); + equalityQuery.greaterThan( + 'createdAt', + this.state.data[this.state.data.length - 1].get('createdAt') + ); } else { - equalityQuery.lessThan('createdAt', this.state.data[this.state.data.length - 1].get('createdAt')); + equalityQuery.lessThan( + 'createdAt', + this.state.data[this.state.data.length - 1].get('createdAt') + ); equalityQuery.equalTo(field, comp); } query = Parse.Query.or(query, equalityQuery); @@ -812,7 +894,10 @@ class Browser extends DashboardView { query.descending(this.state.ordering.substr(1)); } } else { - query.lessThan('createdAt', this.state.data[this.state.data.length - 1].get('createdAt')); + query.lessThan( + 'createdAt', + this.state.data[this.state.data.length - 1].get('createdAt') + ); query.addDescending('createdAt'); } query.limit(MAX_ROWS_FETCHED); @@ -822,7 +907,7 @@ class Browser extends DashboardView { query.find({ useMasterKey }).then((nextPage) => { if (className === this.props.params.className) { this.setState((state) => ({ - data: state.data.concat(nextPage) + data: state.data.concat(nextPage), })); } }); @@ -836,18 +921,68 @@ class Browser extends DashboardView { } else { const source = this.props.params.className; const _filters = JSON.stringify(filters.toJSON()); - const url = `browser/${source}${(filters.size === 0 ? '' : `?filters=${(encodeURIComponent(_filters))}`)}`; + const url = `browser/${source}${ + filters.size === 0 ? '' : `?filters=${encodeURIComponent(_filters)}` + }`; // filters param change is making the fetch call + console.log(generatePath(this.context, url)); this.props.navigate(generatePath(this.context, url)); } } + saveFilters(filters, name) { + const _filters = JSON.stringify(filters.toJSON()); + const preferences = ClassPreferences.getPreferences( + this.context.applicationId, + this.props.params.className + ); + if (!preferences.filters.includes(_filters)) { + preferences.filters.push({ + name, + filter: _filters, + }); + } + ClassPreferences.updatePreferences( + preferences, + this.context.applicationId, + this.props.params.className + ); + super.forceUpdate(); + } + + filterClicked(url) { + this.props.navigate(generatePath(this.context, url)); + } + + removeFilter(filter) { + const preferences = ClassPreferences.getPreferences( + this.context.applicationId, + this.props.params.className + ); + let i = preferences.filters.length; + while (i--) { + const item = preferences.filters[i]; + if (JSON.stringify(item) === JSON.stringify(filter)) { + preferences.filters.splice(i, 1); + } + } + ClassPreferences.updatePreferences( + preferences, + this.context.applicationId, + this.props.params.className + ); + super.forceUpdate(); + } + updateOrdering(ordering) { let source = this.state.relation || this.props.params.className; - this.setState({ - ordering: ordering, - selection: {} - }, () => this.fetchData(source, this.state.filters)); + this.setState( + { + ordering: ordering, + selection: {}, + }, + () => this.fetchData(source, this.state.filters) + ); ColumnPreferences.getColumnSort( ordering, this.context.applicationId, @@ -860,40 +995,65 @@ class Browser extends DashboardView { const className = this.props.params.className; const entityId = relation.parent.id; const relationName = relation.key; - return generatePath(this.context, `browser/${className}/${entityId}/${relationName}`); + return generatePath( + this.context, + `browser/${className}/${entityId}/${relationName}` + ); } setRelation(relation, filters) { - this.setState({ - relation: relation, - data: null, - }, () => { - let filterQueryString; - if (filters && filters.size) { - filterQueryString = encodeURIComponent(JSON.stringify(filters.toJSON())); + this.setState( + { + relation: relation, + data: null, + }, + () => { + let filterQueryString; + if (filters && filters.size) { + filterQueryString = encodeURIComponent( + JSON.stringify(filters.toJSON()) + ); + } + const url = `${this.getRelationURL()}${ + filterQueryString ? `?filters=${filterQueryString}` : '' + }`; + this.props.navigate(url); } - const url = `${this.getRelationURL()}${filterQueryString ? `?filters=${filterQueryString}` : ''}`; - this.props.navigate(url); - }); + ); this.fetchRelation(relation, filters); } handlePointerClick({ className, id, field = 'objectId' }) { - let filters = JSON.stringify([{ + let filters = JSON.stringify([ + { field, constraint: 'eq', - compareTo: id - }]); - this.props.navigate(generatePath(this.context, `browser/${className}?filters=${encodeURIComponent(filters)}`)); + compareTo: id, + }, + ]); + this.props.navigate( + generatePath( + this.context, + `browser/${className}?filters=${encodeURIComponent(filters)}` + ) + ); } handlePointerCmdClick({ className, id, field = 'objectId' }) { - let filters = JSON.stringify([{ - field, - constraint: 'eq', - compareTo: id - }]); - window.open(generatePath(this.context, `browser/${className}?filters=${encodeURIComponent(filters)}`),'_blank'); + let filters = JSON.stringify([ + { + field, + constraint: 'eq', + compareTo: id, + }, + ]); + window.open( + generatePath( + this.context, + `browser/${className}?filters=${encodeURIComponent(filters)}` + ), + '_blank' + ); } handleCLPChange(clp) { @@ -909,8 +1069,9 @@ class Browser extends DashboardView { let isNewObject = row === -1; let isEditCloneObj = row < -1; let obj = isNewObject ? this.state.newObject : this.state.data[row]; - if(isEditCloneObj){ - obj = this.state.editCloneRows[row + (this.state.editCloneRows.length + 1)]; + if (isEditCloneObj) { + obj = + this.state.editCloneRows[row + (this.state.editCloneRows.length + 1)]; } if (!obj) { return; @@ -927,7 +1088,7 @@ class Browser extends DashboardView { if (isNewObject) { this.setState({ - isNewObject: obj + isNewObject: obj, }); return; } @@ -936,78 +1097,85 @@ class Browser extends DashboardView { let cloneRows = [...this.state.editCloneRows]; cloneRows.splice(editObjIndex, 1, obj); this.setState({ - editCloneRows: cloneRows + editCloneRows: cloneRows, }); return; } const { useMasterKey } = this.state; - obj.save(null, { useMasterKey }).then((objectSaved) => { - let msg = objectSaved.className + ' with id \'' + objectSaved.id + '\' updated'; - this.showNote(msg, false); + obj.save(null, { useMasterKey }).then( + (objectSaved) => { + let msg = + objectSaved.className + " with id '" + objectSaved.id + "' updated"; + this.showNote(msg, false); - const state = { data: this.state.data, editCloneRows: this.state.editCloneRows }; + const state = { + data: this.state.data, + editCloneRows: this.state.editCloneRows, + }; - if (isNewObject) { - const relation = this.state.relation; - if (relation) { - const parent = relation.parent; - const parentRelation = parent.relation(relation.key); - parentRelation.add(obj); - const targetClassName = relation.targetClassName; - parent.save(null, { useMasterKey: true }).then(() => { - this.setState({ - newObject: null, - data: [ - obj, - ...this.state.data, - ], - relationCount: this.state.relationCount + 1, - counts: { - ...this.state.counts, - [targetClassName]: this.state.counts[targetClassName] + 1, + if (isNewObject) { + const relation = this.state.relation; + if (relation) { + const parent = relation.parent; + const parentRelation = parent.relation(relation.key); + parentRelation.add(obj); + const targetClassName = relation.targetClassName; + parent.save(null, { useMasterKey: true }).then( + () => { + this.setState({ + newObject: null, + data: [obj, ...this.state.data], + relationCount: this.state.relationCount + 1, + counts: { + ...this.state.counts, + [targetClassName]: this.state.counts[targetClassName] + 1, + }, + }); }, - }); - }, (error) => { - let msg = typeof error === 'string' ? error : error.message; - if (msg) { - msg = msg[0].toUpperCase() + msg.substr(1); + (error) => { + let msg = typeof error === 'string' ? error : error.message; + if (msg) { + msg = msg[0].toUpperCase() + msg.substr(1); + } + obj.set(attr, prev); + this.setState({ data: this.state.data }); + this.showNote(msg, true); + } + ); + } else { + state.newObject = null; + if (this.props.params.className === obj.className) { + this.state.data.unshift(obj); } - obj.set(attr, prev); - this.setState({ data: this.state.data }); - this.showNote(msg, true); - }); - } else { - state.newObject = null; + this.state.counts[obj.className] += 1; + } + } + if (isEditCloneObj) { + state.editCloneRows = state.editCloneRows.filter( + (cloneObj) => cloneObj._localId !== obj._localId + ); + if (state.editCloneRows.length === 0) state.editCloneRows = null; if (this.props.params.className === obj.className) { this.state.data.unshift(obj); } this.state.counts[obj.className] += 1; } - } - if (isEditCloneObj) { - state.editCloneRows = state.editCloneRows.filter( - cloneObj => cloneObj._localId !== obj._localId - ); - if (state.editCloneRows.length === 0) state.editCloneRows = null; - if (this.props.params.className === obj.className) { - this.state.data.unshift(obj); + this.setState(state); + }, + (error) => { + let msg = typeof error === 'string' ? error : error.message; + if (msg) { + msg = msg[0].toUpperCase() + msg.substr(1); + } + if (!isNewObject && !isEditCloneObj) { + obj.set(attr, prev); + this.setState({ data: this.state.data }); } - this.state.counts[obj.className] += 1; - } - this.setState(state); - }, (error) => { - let msg = typeof error === 'string' ? error : error.message; - if (msg) { - msg = msg[0].toUpperCase() + msg.substr(1); - } - if (!isNewObject && !isEditCloneObj) { - obj.set(attr, prev); - this.setState({ data: this.state.data }); - } - this.showNote(msg, true); - }); + this.showNote(msg, true); + } + ); } deleteRows(rows) { @@ -1028,7 +1196,11 @@ class Browser extends DashboardView { let indexes = []; let toDelete = []; let seeking = Object.keys(rows).length; - for (let i = 0; i < this.state.data.length && indexes.length < seeking; i++) { + for ( + let i = 0; + i < this.state.data.length && indexes.length < seeking; + i++ + ) { let obj = this.state.data[i]; if (!obj || !obj.id) { continue; @@ -1040,7 +1212,9 @@ class Browser extends DashboardView { } const toDeleteObjectIds = []; - toDelete.forEach((obj) => { toDeleteObjectIds.push(obj.id); }); + toDelete.forEach((obj) => { + toDeleteObjectIds.push(obj.id); + }); const { useMasterKey } = this.state; let relation = this.state.relation; @@ -1057,52 +1231,84 @@ class Browser extends DashboardView { } }); } else if (toDelete.length) { - Parse.Object.destroyAll(toDelete, { useMasterKey }).then(() => { - let deletedNote; + Parse.Object.destroyAll(toDelete, { useMasterKey }).then( + () => { + let deletedNote; - if (toDeleteObjectIds.length == 1) { - deletedNote = className + ' with id \'' + toDeleteObjectIds[0] + '\' deleted'; - } else { - deletedNote = toDeleteObjectIds.length + ' ' + className + ' objects deleted'; - } + if (toDeleteObjectIds.length == 1) { + deletedNote = + className + " with id '" + toDeleteObjectIds[0] + "' deleted"; + } else { + deletedNote = + toDeleteObjectIds.length + ' ' + className + ' objects deleted'; + } - this.showNote(deletedNote, false); + this.showNote(deletedNote, false); - if (this.props.params.className === className) { - for (let i = 0; i < indexes.length; i++) { - this.state.data.splice(indexes[i] - i, 1); - } - this.state.counts[className] -= indexes.length; + if (this.props.params.className === className) { + for (let i = 0; i < indexes.length; i++) { + this.state.data.splice(indexes[i] - i, 1); + } + this.state.counts[className] -= indexes.length; - // 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) { - this.prefetchData(this.props, this.context); - } else { - this.forceUpdate(); - } - } - }, (error) => { - let errorDeletingNote = null; - - if (error.code === Parse.Error.AGGREGATE_ERROR) { - if (error.errors.length == 1) { - 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'; - } else { - errorDeletingNote = 'Error deleting all ' + error.errors.length + ' ' + className + ' objects'; + // 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) { + this.prefetchData(this.props, this.context); + } else { + this.forceUpdate(); + } } - } else { - if (toDeleteObjectIds.length == 1) { - errorDeletingNote = 'Error deleting ' + className + ' with id \'' + toDeleteObjectIds[0] + '\''; + }, + (error) => { + let errorDeletingNote = null; + + if (error.code === Parse.Error.AGGREGATE_ERROR) { + if (error.errors.length == 1) { + 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'; + } else { + errorDeletingNote = + 'Error deleting all ' + + error.errors.length + + ' ' + + className + + ' objects'; + } } else { - errorDeletingNote = 'Error deleting ' + toDeleteObjectIds.length + ' ' + className + ' objects'; + if (toDeleteObjectIds.length == 1) { + errorDeletingNote = + 'Error deleting ' + + className + + " with id '" + + toDeleteObjectIds[0] + + "'"; + } else { + errorDeletingNote = + 'Error deleting ' + + toDeleteObjectIds.length + + ' ' + + className + + ' objects'; + } } - } - this.showNote(errorDeletingNote, true); - }); + this.showNote(errorDeletingNote, true); + } + ); } } } @@ -1162,23 +1368,27 @@ class Browser extends DashboardView { if (missedObjectsCount) { const missedObjects = []; objectIds.forEach((objectId) => { - const object = objects.find(x => x.id === objectId); + const object = objects.find((x) => x.id === objectId); if (!object) { missedObjects.push(objectId); } }); - const errorSummary = `${missedObjectsCount === 1 ? 'The object is' : `${missedObjectsCount} Objects are`} not retrieved:`; + const errorSummary = `${ + missedObjectsCount === 1 + ? 'The object is' + : `${missedObjectsCount} Objects are` + } not retrieved:`; throw `${errorSummary} ${JSON.stringify(missedObjects)}`; } parent.relation(relation.key).add(objects); await parent.save(null, { useMasterKey }); // remove duplication - this.state.data.forEach(origin => objects = objects.filter(object => object.id !== origin.id)); + this.state.data.forEach( + (origin) => + (objects = objects.filter((object) => object.id !== origin.id)) + ); this.setState({ - data: [ - ...objects, - ...this.state.data, - ], + data: [...objects, ...this.state.data], relationCount: this.state.relationCount + objects.length, showAttachRowsDialog: false, }); @@ -1196,11 +1406,19 @@ class Browser extends DashboardView { }); } - async confirmAttachSelectedRows(className, targetObjectId, relationName, objectIds, targetClassName) { + async confirmAttachSelectedRows( + className, + targetObjectId, + relationName, + objectIds, + targetClassName + ) { const { useMasterKey } = this.state; const parentQuery = new Parse.Query(className); const parent = await parentQuery.get(targetObjectId, { useMasterKey }); - const query = new Parse.Query(targetClassName || this.props.params.className); + const query = new Parse.Query( + targetClassName || this.props.params.className + ); query.containedIn('objectId', objectIds); const objects = await query.find({ useMasterKey }); parent.relation(relationName).add(objects); @@ -1249,15 +1467,15 @@ class Browser extends DashboardView { showCloneSelectedRowsDialog: false, counts: { ...this.state.counts, - [className]: this.state.counts[className] + toClone.length - } + [className]: this.state.counts[className] + toClone.length, + }, }); } catch (error) { //for duplicate, username missing or required field missing errors if (error.code === 137 || error.code === 200 || error.code === 142) { let failedSaveObj = []; let savedObjects = []; - toClone.forEach(cloneObj => { + toClone.forEach((cloneObj) => { cloneObj.dirty() ? failedSaveObj.push(cloneObj) : savedObjects.push(cloneObj); @@ -1267,14 +1485,14 @@ class Browser extends DashboardView { data: [...savedObjects, ...this.state.data], counts: { ...this.state.counts, - [className]: this.state.counts[className] + savedObjects.length - } + [className]: this.state.counts[className] + savedObjects.length, + }, }); } this.addEditCloneRows(failedSaveObj); } this.setState({ - showCloneSelectedRowsDialog: false + showCloneSelectedRowsDialog: false, }); this.showNote(error.message, true); } @@ -1282,19 +1500,19 @@ class Browser extends DashboardView { showExportSelectedRowsDialog(rows) { this.setState({ - rowsToExport: rows + rowsToExport: rows, }); } showExportSchemaDialog() { this.setState({ - showExportSchemaDialog: true - }) + showExportSchemaDialog: true, + }); } cancelExportSelectedRows() { this.setState({ - rowsToExport: null + rowsToExport: null, }); } @@ -1338,7 +1556,7 @@ class Browser extends DashboardView { return json; }), null, - indentation ? 2 : null, + indentation ? 2 : null ), ], { type: 'application/json' } @@ -1448,29 +1666,34 @@ class Browser extends DashboardView { getClassRelationColumns(className) { const currentClassName = this.props.params.className; return this.getClassColumns(className, false) - .map(column => { - if (column.type === 'Relation' && column.targetClass === currentClassName) { + .map((column) => { + if ( + column.type === 'Relation' && + column.targetClass === currentClassName + ) { return column.name; } }) - .filter(column => column); + .filter((column) => column); } getClassColumns(className, onlyTouchable = true) { let columns = []; const classes = this.props.schema.data.get('classes'); classes.get(className).forEach((field, name) => { - columns.push({ - ...field, - name, - }); + columns.push({ + ...field, + name, + }); }); if (onlyTouchable) { let untouchable = DefaultColumns.All; if (className[0] === '_' && DefaultColumns[className]) { untouchable = untouchable.concat(DefaultColumns[className]); } - columns = columns.filter((column) => untouchable.indexOf(column.name) === -1); + columns = columns.filter( + (column) => untouchable.indexOf(column.name) === -1 + ); } return columns; } @@ -1499,13 +1722,26 @@ class Browser extends DashboardView { special.sort((a, b) => stringCompare(a.name, b.name)); categories.sort((a, b) => stringCompare(a.name, b.name)); if (special.length > 0 && categories.length > 0) { - special.push({ type: 'separator', id: 'classSeparator' }) + special.push({ type: 'separator', id: 'classSeparator' }); } + const allCategories = []; + for (const row of [...special, ...categories]) { + const { filters = [] } = ClassPreferences.getPreferences( + this.context.applicationId, + row.name + ); + row.filters = filters; + allCategories.push(row); + } + return ( + filterClicked={this.filterClicked} + removeFilter={this.removeFilter} + categories={allCategories} + /> ); } @@ -1549,26 +1785,25 @@ class Browser extends DashboardView { }); } - handleShowAcl(row, col){ + handleShowAcl(row, col) { this.dataBrowserRef.current.setEditing(true); this.dataBrowserRef.current.setCurrent({ row, col }); } // skips key controls handling when dialog is opened - onDialogToggle(opened){ - this.setState({showPermissionsDialog: opened}); + onDialogToggle(opened) { + this.setState({ showPermissionsDialog: opened }); } - async onChangeDefaultKey (name) { + async onChangeDefaultKey(name) { ColumnPreferences.setPointerDefaultKey( this.context.applicationId, this.props.params.className, name - ); + ); this.setState({ showPointerKeyDialog: false }); } - renderContent() { let browser = null; let className = this.props.params.className; @@ -1581,37 +1816,49 @@ class Browser extends DashboardView { browser = (
+ title="You have no classes yet" + description={ + 'This is where you can view and edit your app\u2019s data' + } + icon="files-solid" + cta="Create your first class" + action={this.showCreateClass} + />
); } else if (className && classes.get(className)) { - let columns = { - objectId: { type: 'String' } + objectId: { type: 'String' }, }; if (this.state.isUnique) { columns = {}; } - classes.get(className).forEach(({ type, targetClass, required }, name) => { - if (name === 'objectId' || this.state.isUnique && name !== this.state.uniqueField) { - return; - } - const info = { type, required: !!required }; - if (className === '_User' && (name === 'username' || name === 'password' || name === 'authData')) { - info.required = true; - } - if (className === '_Role' && (name === 'name' || name === 'ACL')) { - info.required = true; - } - if (targetClass) { - info.targetClass = targetClass; - } - columns[name] = info; - }); + classes + .get(className) + .forEach(({ type, targetClass, required }, name) => { + if ( + name === 'objectId' || + (this.state.isUnique && name !== this.state.uniqueField) + ) { + return; + } + const info = { type, required: !!required }; + if ( + className === '_User' && + (name === 'username' || + name === 'password' || + name === 'authData') + ) { + info.required = true; + } + if (className === '_Role' && (name === 'name' || name === 'ACL')) { + info.required = true; + } + if (targetClass) { + info.targetClass = targetClass; + } + columns[name] = info; + }); var count; if (this.state.relation) { @@ -1634,6 +1881,7 @@ class Browser extends DashboardView { schema={this.props.schema} filters={this.state.filters} onFilterChange={this.updateFilters} + onFilterSave={this.saveFilters} onRemoveColumn={this.showRemoveColumn} onDeleteRows={this.showDeleteRows} onDropClass={this.showDropClass} @@ -1647,14 +1895,12 @@ class Browser extends DashboardView { 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} @@ -1684,30 +1930,37 @@ class Browser extends DashboardView { onAbortAddRow={this.abortAddRow} onAddRowWithModal={this.addRowWithModal} onAddClass={this.showCreateClass} - showNote={this.showNote} /> + showNote={this.showNote} + /> ); } } let extras = null; - if(this.state.showPointerKeyDialog){ - let currentColumns = this.getClassColumns(className).map(column => column.name); + if (this.state.showPointerKeyDialog) { + let currentColumns = this.getClassColumns(className).map( + (column) => column.name + ); extras = ( this.setState({ showPointerKeyDialog: false })} - onConfirm={this.onChangeDefaultKey} /> + onConfirm={this.onChangeDefaultKey} + /> ); - } - else if (this.state.showCreateClassDialog) { + } else if (this.state.showCreateClassDialog) { extras = ( this.setState({ showCreateClassDialog: false })} - onConfirm={this.createClass} /> + onConfirm={this.createClass} + /> ); } else if (this.state.showAddColumnDialog) { const currentApp = this.context || {}; @@ -1724,15 +1977,21 @@ class Browser extends DashboardView { onConfirm={this.addColumn} onContinue={this.addColumnAndContinue} showNote={this.showNote} - parseServerVersion={currentApp.serverInfo && currentApp.serverInfo.parseServerVersion} /> + parseServerVersion={ + currentApp.serverInfo && currentApp.serverInfo.parseServerVersion + } + /> ); } else if (this.state.showRemoveColumnDialog) { - let currentColumns = this.getClassColumns(className).map(column => column.name); + let currentColumns = this.getClassColumns(className).map( + (column) => column.name + ); extras = ( this.setState({ showRemoveColumnDialog: false })} - onConfirm={this.removeColumn} /> + onConfirm={this.removeColumn} + /> ); } else if (this.state.rowsToDelete) { extras = ( @@ -1741,25 +2000,30 @@ class Browser extends DashboardView { selection={this.state.rowsToDelete} relation={this.state.relation} onCancel={() => this.setState({ rowsToDelete: null })} - onConfirm={() => this.deleteRows(this.state.rowsToDelete)} /> + onConfirm={() => this.deleteRows(this.state.rowsToDelete)} + /> ); } else if (this.state.showDropClassDialog) { extras = ( this.setState({ - showDropClassDialog: false, - lastError: null, - lastNote: null, - })} - onConfirm={() => this.dropClass(className)} /> + onCancel={() => + this.setState({ + showDropClassDialog: false, + lastError: null, + lastNote: null, + }) + } + onConfirm={() => this.dropClass(className)} + /> ); } else if (this.state.showExportDialog) { extras = ( this.setState({ showExportDialog: false })} - onConfirm={() => this.exportClass(className)} /> + onConfirm={() => this.exportClass(className)} + /> ); } else if (this.state.showExportSchemaDialog) { extras = ( @@ -1767,7 +2031,8 @@ class Browser extends DashboardView { className={className} schema={this.props.schema.data.get('classes')} onCancel={() => this.setState({ showExportSchemaDialog: false })} - onConfirm={(...args) => this.exportSchema(...args)} /> + onConfirm={(...args) => this.exportSchema(...args)} + /> ); } else if (this.state.showAttachRowsDialog) { extras = ( @@ -1776,7 +2041,7 @@ class Browser extends DashboardView { onCancel={this.cancelAttachRows} onConfirm={this.confirmAttachRows} /> - ) + ); } else if (this.state.showAttachSelectedRowsDialog) { extras = ( { - columnsObject[column.name] = column + columnsObject[column.name] = column; }); // get ordered list of class columns - const columnPreferences = this.context.columnPreference || {} + const columnPreferences = this.context.columnPreference || {}; const columns = ColumnPreferences.getOrder( columnsObject, this.context.applicationId, @@ -1812,7 +2077,7 @@ class Browser extends DashboardView { columnPreferences[className] ); // extend columns with their type and targetClass properties - columns.forEach(column => { + columns.forEach((column) => { const { type, targetClass } = columnsObject[column.name]; column.type = type; column.targetClass = targetClass; @@ -1832,7 +2097,7 @@ class Browser extends DashboardView { this.selectRow(selectedId, true); } - const row = data.findIndex(d => d.id === selectedId); + const row = data.findIndex((d) => d.id === selectedId); const attributes = selectedId ? data[row].attributes @@ -1841,7 +2106,7 @@ class Browser extends DashboardView { const selectedObject = { row: row, id: selectedId, - ...attributes + ...attributes, }; extras = ( @@ -1858,7 +2123,7 @@ class Browser extends DashboardView { schema={this.props.schema} useMasterKey={this.state.useMasterKey} /> - ) + ); } else if (this.state.rowsToExport) { extras = ( this.confirmExportSelectedRows(this.state.rowsToExport, type, indentation)} + onConfirm={(type, indentation) => + this.confirmExportSelectedRows( + this.state.rowsToExport, + type, + indentation + ) + } /> ); } @@ -1877,16 +2148,18 @@ class Browser extends DashboardView { if (this.state.lastError) { notification = ( - + ); } else if (this.state.lastNote) { notification = ( - + ); - } - else if (this.state.exporting) { + } else if (this.state.exporting) { notification = ( - + ); } return ( diff --git a/src/dashboard/Data/Browser/BrowserToolbar.react.js b/src/dashboard/Data/Browser/BrowserToolbar.react.js index 5076c9a141..73824bc86c 100644 --- a/src/dashboard/Data/Browser/BrowserToolbar.react.js +++ b/src/dashboard/Data/Browser/BrowserToolbar.react.js @@ -31,6 +31,7 @@ let BrowserToolbar = ({ relation, setCurrent, onFilterChange, + onFilterSave, onAddColumn, onAddRow, onAddRowWithModal, @@ -275,6 +276,7 @@ let BrowserToolbar = ({ schema={schemaSimplifiedData} filters={filters} onChange={onFilterChange} + onSaveFilter={onFilterSave} className={classNameForEditors} blacklistedFilters={onAddRow ? [] : ['unique']} disabled={isPendingEditCloneRows} diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index 962ba7e46c..a1bca22cb7 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -316,6 +316,7 @@ export default class DataBrowser extends React.Component { setCopyableValue={this.setCopyableValue} setContextMenu={this.setContextMenu} onFilterChange={this.props.onFilterChange} + onFilterSave={this.props.onFilterSave} {...other} /> Date: Sat, 11 Mar 2023 13:03:51 +1100 Subject: [PATCH 02/12] refactor --- src/components/BrowserFilter/BrowserFilter.react.js | 1 - src/lib/ClassPreferences.js | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/BrowserFilter/BrowserFilter.react.js b/src/components/BrowserFilter/BrowserFilter.react.js index 75b7b8b1fa..57decf7bd6 100644 --- a/src/components/BrowserFilter/BrowserFilter.react.js +++ b/src/components/BrowserFilter/BrowserFilter.react.js @@ -103,7 +103,6 @@ export default class BrowserFilter extends React.Component { } return filter; }); - console.log('save clicked') this.props.onSaveFilter(formatted, this.state.name); this.toggle(); } diff --git a/src/lib/ClassPreferences.js b/src/lib/ClassPreferences.js index 200f14d386..8374e2cc45 100644 --- a/src/lib/ClassPreferences.js +++ b/src/lib/ClassPreferences.js @@ -20,7 +20,6 @@ export function getPreferences(appId, className) { filters: [], }); } catch (e) { - console.log({e}); // Fails in Safari private browsing entry = null; } @@ -38,4 +37,4 @@ export function getPreferences(appId, className) { } function path(appId, className) { return `ParseDashboard:${VERSION}:${appId}:ClassPreference:${className}`; -} \ No newline at end of file +} From 60590186b26f162ac11dd43c04418d555618eea2 Mon Sep 17 00:00:00 2001 From: dblythy Date: Sat, 11 Mar 2023 13:09:15 +1100 Subject: [PATCH 03/12] lint --- .../BrowserFilter/BrowserFilter.react.js | 2 +- .../CategoryList/CategoryList.react.js | 3 +-- src/dashboard/Data/Browser/Browser.react.js | 18 +++++++++--------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/components/BrowserFilter/BrowserFilter.react.js b/src/components/BrowserFilter/BrowserFilter.react.js index 57decf7bd6..f15479186a 100644 --- a/src/components/BrowserFilter/BrowserFilter.react.js +++ b/src/components/BrowserFilter/BrowserFilter.react.js @@ -162,7 +162,7 @@ export default class BrowserFilter extends React.Component {