Skip to content

Context menu with quick filters and links to related records in other Parse Objects 📃 #1431

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 41 commits into from
Mar 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
2188d3a
Context menu with quick filter initials
Dec 21, 2019
01cdadf
Support for filtering Pointer type of entities
Dec 22, 2019
9f4c8f6
Adding more filters to context menu
Dec 22, 2019
9406b70
Introducing general, reusable ContextMenu component
Dec 24, 2019
1127c64
Move ContextMenu item in pig list to keep alphabetical order
Dec 24, 2019
ec990f2
Reverting changes in parse-dashboard-config.json file
Dec 25, 2019
ab15a95
Merge branch 'master' into context-menu-filter
404-html Dec 25, 2019
c87959a
Showing "Get related records from..." context menu option when clicki…
Dec 26, 2019
2c097e0
Merge branch 'context-menu-filter' of https://github.com/404-html/par…
Dec 26, 2019
0cfee6c
Changing categories to fill context menu horizontally
Dec 26, 2019
a1080f2
Fully functional 'Get related records from...' context menu item
Dec 27, 2019
c08ba6d
Fixing CI build by removing empty test file
Dec 27, 2019
7bfa990
Closing context menu after chosing an option
Dec 27, 2019
80a7ab7
Properly handling 'Get related records from...' logic when executing …
Dec 27, 2019
5d6bb77
Fixing click on empty cell by checking value
Dec 28, 2019
0953350
Closing context menu after clicking outside it
Dec 28, 2019
6c80d43
Adding key attributes to ContextMenu JSX
Dec 28, 2019
65ce779
Positioning menu sections to not go off the screen + animations
Dec 28, 2019
b4270e8
Merge branch 'master' into context-menu-filter
404-html Dec 28, 2019
abee87f
Merge branch 'master' into context-menu-filter
404-html Dec 30, 2019
cc5e0fd
Functional options to add a filter to existing one(s)
Dec 30, 2019
c371d84
Merge branch 'context-menu-filter' of https://github.com/404-html/par…
Dec 30, 2019
7bc7cef
Showing "Add fiter..." context menu option only if there's any filter…
Dec 30, 2019
b61d7af
Generating context menu filters dynamically basing on available ones
Dec 30, 2019
e1248d9
Code cleaning + missing import
Dec 30, 2019
649925d
Passing BLACKLISTED_FILTERS when getting filters available for contex…
Dec 30, 2019
09a510a
Restoring accidentally removed line
Dec 31, 2019
8dce379
Sorting objects listed in context menu item "Get related records from…
Dec 31, 2019
f3af112
Handling context menu filters for "Date" type of data
Jan 3, 2020
a571784
Getting "compareTo" value only for constrains that are comparable
Jan 3, 2020
515bb66
Merge branch 'master' into context-menu-filter
404-html Jan 13, 2020
882c6c5
Merge branch 'master' into context-menu-filter
404-html Jan 23, 2020
65d2e70
Merge branch 'master' into context-menu-filter
404-html Feb 29, 2020
f03d212
Removing duplicated prop
Feb 29, 2020
b599bd9
Fixing lint errors
Feb 29, 2020
be5194c
Adding "Edit row" option to BrowserCell context menu
Feb 29, 2020
d2dbbcd
Keeping BLACKLISTED_FILTERS in single place
Feb 29, 2020
e42b24b
Removing unnecessary onSelect call (as cell is already selected at th…
Feb 29, 2020
6a36e4f
Removing unnecessary setCopyableValue call (as it should be called at…
Feb 29, 2020
4ca48a9
Merge branch 'master' into context-menu-filter
404-html Mar 3, 2020
4595ca6
Merge branch 'master' into context-menu-filter
404-html Mar 3, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Parse-Dashboard/parse-dashboard-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
}
],
"iconsFolder": "icons"
}
}
156 changes: 148 additions & 8 deletions src/components/BrowserCell/BrowserCell.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
import * as Filters from 'lib/Filters';
import { List, Map } from 'immutable';
import { dateStringUTC } from 'lib/DateUtils';
import getFileName from 'lib/getFileName';
import Parse from 'parse';
Expand Down Expand Up @@ -64,8 +66,144 @@ export default class BrowserCell extends Component {
return isRefDifferent;
}

//#region Cell Context Menu related methods

onContextMenu(event) {
if (event.type !== 'contextmenu') { return; }
event.preventDefault();

const { field, hidden, onSelect, setCopyableValue, setContextMenu, row, col } = this.props;

onSelect({ row, col });
setCopyableValue(hidden ? undefined : this.copyableValue);

const available = Filters.availableFilters(this.props.simplifiedSchema, this.props.filters, Filters.BLACKLISTED_FILTERS);
const constraints = available && available[field];

const { pageX, pageY } = event;
const menuItems = this.getContextMenuOptions(constraints);
menuItems.length && setContextMenu(pageX, pageY, menuItems);
}

getContextMenuOptions(constraints) {
let { onEditSelectedRow } = this.props;
const contextMenuOptions = [];

const setFilterContextMenuOption = this.getSetFilterContextMenuOption(constraints);
setFilterContextMenuOption && contextMenuOptions.push(setFilterContextMenuOption);

const addFilterContextMenuOption = this.getAddFilterContextMenuOption(constraints);
addFilterContextMenuOption && contextMenuOptions.push(addFilterContextMenuOption);

const relatedObjectsContextMenuOption = this.getRelatedObjectsContextMenuOption();
relatedObjectsContextMenuOption && contextMenuOptions.push(relatedObjectsContextMenuOption);

onEditSelectedRow && contextMenuOptions.push({
text: 'Edit row',
callback: () => {
let { objectId, onEditSelectedRow } = this.props;
onEditSelectedRow(true, objectId);
}
});

return contextMenuOptions;
}

getSetFilterContextMenuOption(constraints) {
if (constraints) {
return {
text: 'Set filter...', items: constraints.map(constraint => {
const definition = Filters.Constraints[constraint];
const text = `${this.props.field} ${definition.name}${definition.comparable ? (' ' + this.copyableValue) : ''}`;
return {
text,
callback: this.pickFilter.bind(this, constraint)
};
})
};
}
}

getAddFilterContextMenuOption(constraints) {
if (constraints && this.props.filters && this.props.filters.size > 0) {
return {
text: 'Add filter...', items: constraints.map(constraint => {
const definition = Filters.Constraints[constraint];
const text = `${this.props.field} ${definition.name}${definition.comparable ? (' ' + this.copyableValue) : ''}`;
return {
text,
callback: this.pickFilter.bind(this, constraint, true)
};
})
};
}
}

/**
* Returns "Get related records from..." context menu item if cell holds a Pointer
* or objectId and there's a class in relation.
*/
getRelatedObjectsContextMenuOption() {
const { value, schema, onPointerClick } = this.props;

const pointerClassName = (value && value.className)
|| (this.props.field === 'objectId' && this.props.className);
if (pointerClassName) {
const relatedRecordsMenuItem = { text: 'Get related records from...', items: [] };
schema.data.get('classes').sortBy((v, k) => k).forEach((cl, className) => {
cl.forEach((column, field) => {
if (column.targetClass !== pointerClassName) { return; }
relatedRecordsMenuItem.items.push({
text: className, callback: () => {
let relatedObject = value;
if (this.props.field === 'objectId') {
relatedObject = new Parse.Object(pointerClassName);
relatedObject.id = value;
}
onPointerClick({ className, id: relatedObject.toPointer(), field })
}
})
});
});

return relatedRecordsMenuItem.items.length ? relatedRecordsMenuItem : undefined;
}
}

pickFilter(constraint, addToExistingFilter) {
const definition = Filters.Constraints[constraint];
const { filters, type, value, field } = this.props;
const newFilters = addToExistingFilter ? filters : new List();
let compareTo;
if (definition.comparable) {
switch (type) {
case 'Pointer':
compareTo = value.toPointer()
break;
case 'Date':
compareTo = value.__type ? value : {
__type: 'Date',
iso: value
};
break;

default:
compareTo = value;
break;
}
}

this.props.onFilterChange(newFilters.push(new Map({
field,
constraint,
compareTo
})));
}

//#endregion

render() {
let { type, value, hidden, width, current, onSelect, onEditChange, setCopyableValue, setRelation, onPointerClick, row, col, name, onEditSelectedRow } = this.props;
let { type, value, hidden, width, current, onSelect, onEditChange, setCopyableValue, setRelation, onPointerClick, row, col, field, onEditSelectedRow } = this.props;
let content = value;
this.copyableValue = content;
let classes = [styles.cell, unselectable];
Expand Down Expand Up @@ -96,8 +234,8 @@ export default class BrowserCell extends Component {
<Pill value={value.id} />
</a>
) : (
value.id
);
value.id
);
this.copyableValue = value.id;
} else if (type === 'Date') {
if (typeof value === 'object' && value.__type) {
Expand Down Expand Up @@ -145,11 +283,11 @@ export default class BrowserCell extends Component {
<Pill onClick={() => setRelation(value)} value='View relation' />
</div>
) : (
'Relation'
);
'Relation'
);
this.copyableValue = undefined;
}

if (current) {
classes.push(styles.current);
}
Expand All @@ -164,7 +302,7 @@ export default class BrowserCell extends Component {
}}
onDoubleClick={() => {
// Since objectId can't be edited, double click event opens edit row dialog
if (name === 'objectId' && onEditSelectedRow) {
if (field === 'objectId' && onEditSelectedRow) {
onEditSelectedRow(true, value);
} else if (type !== 'Relation') {
onEditChange(true)
Expand All @@ -178,7 +316,9 @@ export default class BrowserCell extends Component {
}
onEditChange(true);
}
}}>
}}
onContextMenu={this.onContextMenu.bind(this)}
>
{content}
</span>
);
Expand Down
3 changes: 1 addition & 2 deletions src/components/BrowserFilter/BrowserFilter.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import ReactDOM from 'react-dom';
import styles from 'components/BrowserFilter/BrowserFilter.scss';
import { List, Map } from 'immutable';

const BLACKLISTED_FILTERS = [ 'containsAny', 'doesNotContainAny' ];
const POPOVER_CONTENT_ID = 'browserFilterPopover';

export default class BrowserFilter extends React.Component {
Expand All @@ -27,7 +26,7 @@ export default class BrowserFilter extends React.Component {
this.state = {
open: false,
filters: new List(),
blacklistedFilters: BLACKLISTED_FILTERS.concat(props.blacklistedFilters)
blacklistedFilters: Filters.BLACKLISTED_FILTERS.concat(props.blacklistedFilters)
};
this.toggle = this.toggle.bind(this);
}
Expand Down
11 changes: 9 additions & 2 deletions src/components/BrowserRow/BrowserRow.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default class BrowserRow extends Component {
}

render() {
const { className, columns, currentCol, isUnique, obj, onPointerClick, order, readOnlyFields, row, rowWidth, selection, selectRow, setCopyableValue, setCurrent, setEditing, setRelation, onEditSelectedRow } = this.props;
const { className, columns, currentCol, isUnique, obj, onPointerClick, order, readOnlyFields, row, rowWidth, selection, selectRow, setCopyableValue, setCurrent, setEditing, setRelation, onEditSelectedRow, setContextMenu, onFilterChange } = this.props;
let attributes = obj.attributes;
return (
<div className={styles.tableRow} style={{ minWidth: rowWidth }}>
Expand Down Expand Up @@ -61,7 +61,11 @@ export default class BrowserRow extends Component {
return (
<BrowserCell
key={name}
name={name}
schema={this.props.schema}
simplifiedSchema={this.props.simplifiedSchema}
filters={this.props.filters}
className={className}
field={name}
row={row}
col={j}
type={type}
Expand All @@ -71,10 +75,13 @@ export default class BrowserRow extends Component {
onSelect={setCurrent}
onEditChange={setEditing}
onPointerClick={onPointerClick}
onFilterChange={onFilterChange}
setRelation={setRelation}
objectId={obj.id}
value={attr}
hidden={hidden}
setCopyableValue={setCopyableValue}
setContextMenu={setContextMenu}
onEditSelectedRow={onEditSelectedRow} />
);
})}
Expand Down
48 changes: 48 additions & 0 deletions src/components/ContextMenu/ContextMenu.example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2016-present, Parse, LLC
* All rights reserved.
*
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
import React from 'react';
import ContextMenu from 'components/ContextMenu/ContextMenu.react';

export const component = ContextMenu;

export const demos = [
{
name: 'Context menu',
render: () => (
<div style={{
position: 'relative',
height: '100px'
}}>
<ContextMenu
x={0}
y={0}
items={[
{
text: 'Category 1', items: [
{ text: 'C1 Item 1', callback: () => { alert('C1 Item 1 clicked!') } },
{ text: 'C1 Item 2', callback: () => { alert('C1 Item 2 clicked!') } },
{
text: 'Sub Category 1', items: [
{ text: 'SC1 Item 1', callback: () => { alert('SC1 Item 1 clicked!') } },
{ text: 'SC1 Item 2', callback: () => { alert('SC1 Item 2 clicked!') } },
]
}
]
},
{
text: 'Category 2', items: [
{ text: 'C2 Item 1', callback: () => { alert('C2 Item 1 clicked!') } },
{ text: 'C2 Item 2', callback: () => { alert('C2 Item 2 clicked!') } }
]
}
]}
/>
</div>
)
}
];
Loading