Skip to content

Commit 1e6b56d

Browse files
NinoZXdavimacedo
authored andcommitted
View/Edit single row modal (parse-community#1448)
* EditRowDialog - initial version * TextInput - change focus to new input/textarea field when multiline props has changed * EditRowDialg - removed custom FormModal width * EditRowDialog - initial version * TextInput - change focus to new input/textarea field when multiline props has changed * EditRowDialg - removed custom FormModal width * package-lock.json sync after rebase from master * EditRowDialog - moved objectId, createdAt, updatedAt to dialog title. ACL added as confim button in footer. Also removed these fields from content * EditRowDialog - creating new objects with modal implemented * EditRowDialog: - fixed bug when creating new objects with modal; - ACL modal visible without closing editRowDialog; - fixed bug when relation clicked after newly saved object - FormModal replaced with Modal component * EditRowDialog - picker for pointer and relation Co-authored-by: Antonio Davi Macedo Coelho de Castro <adavimacedo@gmail.com>
1 parent 2e72e7a commit 1e6b56d

24 files changed

+2069
-570
lines changed

package-lock.json

+874-492
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/BrowserCell/BrowserCell.react.js

+14-5
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ export default class BrowserCell extends Component {
2424
componentDidUpdate() {
2525
if (this.props.current) {
2626
const node = this.cellRef.current;
27+
const { setRelation } = this.props;
2728
const { left, right, bottom, top } = node.getBoundingClientRect();
2829

2930
// Takes into consideration Sidebar width when over 980px wide.
30-
const leftBoundary = window.innerWidth > 980 ? 300 : 0;
31+
// If setRelation is undefined, DataBrowser is used as ObjectPicker, so it does not have a sidebar.
32+
const leftBoundary = window.innerWidth > 980 && setRelation ? 300 : 0;
3133

3234
// BrowserToolbar + DataBrowserHeader height
3335
const topBoundary = 126;
@@ -63,7 +65,7 @@ export default class BrowserCell extends Component {
6365
}
6466

6567
render() {
66-
let { type, value, hidden, width, current, onSelect, onEditChange, setCopyableValue, setRelation, onPointerClick, row, col } = this.props;
68+
let { type, value, hidden, width, current, onSelect, onEditChange, setCopyableValue, setRelation, onPointerClick, row, col, name, onEditSelectedRow } = this.props;
6769
let content = value;
6870
this.copyableValue = content;
6971
let classes = [styles.cell, unselectable];
@@ -89,10 +91,12 @@ export default class BrowserCell extends Component {
8991
object.id = value.objectId;
9092
value = object;
9193
}
92-
content = (
94+
content = onPointerClick ? (
9395
<a href='javascript:;' onClick={onPointerClick.bind(undefined, value)}>
9496
<Pill value={value.id} />
9597
</a>
98+
) : (
99+
value.id
96100
);
97101
this.copyableValue = value.id;
98102
} else if (type === 'Date') {
@@ -136,10 +140,12 @@ export default class BrowserCell extends Component {
136140
} else if (type === 'Polygon') {
137141
this.copyableValue = content = value.coordinates.map(coord => `(${coord})`)
138142
} else if (type === 'Relation') {
139-
content = (
143+
content = setRelation ? (
140144
<div style={{ textAlign: 'center', cursor: 'pointer' }}>
141145
<Pill onClick={() => setRelation(value)} value='View relation' />
142146
</div>
147+
) : (
148+
'Relation'
143149
);
144150
this.copyableValue = undefined;
145151
}
@@ -157,7 +163,10 @@ export default class BrowserCell extends Component {
157163
setCopyableValue(hidden ? undefined : this.copyableValue);
158164
}}
159165
onDoubleClick={() => {
160-
if (type !== 'Relation') {
166+
// Since objectId can't be edited, double click event opens edit row dialog
167+
if (name === 'objectId' && onEditSelectedRow) {
168+
onEditSelectedRow(true, value);
169+
} else if (type !== 'Relation') {
161170
onEditChange(true)
162171
}
163172
}}

src/components/BrowserFilter/BrowserFilter.react.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@ import styles from 'components/BrowserFilter/BrowserFilter.scss';
1818
import { List, Map } from 'immutable';
1919

2020
const BLACKLISTED_FILTERS = [ 'containsAny', 'doesNotContainAny' ];
21+
const POPOVER_CONTENT_ID = 'browserFilterPopover';
2122

2223
export default class BrowserFilter extends React.Component {
23-
constructor() {
24-
super();
24+
constructor(props) {
25+
super(props);
2526

2627
this.state = {
2728
open: false,
2829
filters: new List(),
30+
blacklistedFilters: BLACKLISTED_FILTERS.concat(props.blacklistedFilters)
2931
};
3032
this.toggle = this.toggle.bind(this)
3133
}
@@ -43,7 +45,7 @@ export default class BrowserFilter extends React.Component {
4345
toggle() {
4446
let filters = this.props.filters;
4547
if (this.props.filters.size === 0) {
46-
let available = Filters.availableFilters(this.props.schema, null, BLACKLISTED_FILTERS);
48+
let available = Filters.availableFilters(this.props.schema, null, this.state.blacklistedFilters);
4749
let field = Object.keys(available)[0];
4850
filters = new List([new Map({ field: field, constraint: available[field][0] })]);
4951
}
@@ -55,7 +57,7 @@ export default class BrowserFilter extends React.Component {
5557
}
5658

5759
addRow() {
58-
let available = Filters.availableFilters(this.props.schema, this.state.filters, BLACKLISTED_FILTERS);
60+
let available = Filters.availableFilters(this.props.schema, this.state.filters, this.state.blacklistedFilters);
5961
let field = Object.keys(available)[0];
6062
this.setState(({ filters }) => ({
6163
filters: filters.push(new Map({ field: field, constraint: available[field][0] })),
@@ -92,12 +94,12 @@ export default class BrowserFilter extends React.Component {
9294
}
9395
let available = Filters.availableFilters(this.props.schema, this.state.filters);
9496
popover = (
95-
<Popover fixed={true} position={position} onExternalClick={this.toggle}>
96-
<div className={popoverStyle.join(' ')} onClick={() => this.props.setCurrent(null)}>
97+
<Popover fixed={true} position={position} onExternalClick={this.toggle} contentId={POPOVER_CONTENT_ID}>
98+
<div className={popoverStyle.join(' ')} onClick={() => this.props.setCurrent(null)} id={POPOVER_CONTENT_ID}>
9799
<div onClick={this.toggle} style={{ cursor: 'pointer', width: this.node.clientWidth, height: this.node.clientHeight }}></div>
98100
<div className={styles.body}>
99101
<Filter
100-
blacklist={BLACKLISTED_FILTERS}
102+
blacklist={this.state.blacklistedFilters}
101103
schema={this.props.schema}
102104
filters={this.state.filters}
103105
onChange={(filters) => this.setState({ filters: filters })}

src/components/BrowserFilter/BrowserFilter.scss

+6
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@
6868
}
6969
}
7070

71+
.objectPickerContent {
72+
.entry svg {
73+
fill: rgba(0, 0, 0, 0.3);
74+
}
75+
}
76+
7177
.body {
7278
position: absolute;
7379
top: 30px;

src/components/BrowserRow/BrowserRow.react.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default class BrowserRow extends Component {
1919
}
2020

2121
render() {
22-
const { className, columns, currentCol, isUnique, obj, onPointerClick, order, readOnlyFields, row, rowWidth, selection, selectRow, setCopyableValue, setCurrent, setEditing, setRelation } = this.props;
22+
const { className, columns, currentCol, isUnique, obj, onPointerClick, order, readOnlyFields, row, rowWidth, selection, selectRow, setCopyableValue, setCurrent, setEditing, setRelation, onEditSelectedRow } = this.props;
2323
let attributes = obj.attributes;
2424
return (
2525
<div className={styles.tableRow} style={{ minWidth: rowWidth }}>
@@ -61,6 +61,7 @@ export default class BrowserRow extends Component {
6161
return (
6262
<BrowserCell
6363
key={name}
64+
name={name}
6465
row={row}
6566
col={j}
6667
type={type}
@@ -73,7 +74,8 @@ export default class BrowserRow extends Component {
7374
setRelation={setRelation}
7475
value={attr}
7576
hidden={hidden}
76-
setCopyableValue={setCopyableValue} />
77+
setCopyableValue={setCopyableValue}
78+
onEditSelectedRow={onEditSelectedRow} />
7779
);
7880
})}
7981
</div>

src/components/ChromeDropdown/ChromeDropdown.react.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export default class ChromeDropdown extends React.Component {
7777
widthStyle = { width: measuredWidth };
7878
content = (
7979
<Popover fixed={true} position={position} onExternalClick={() => this.setState({ open: false })}>
80-
<div style={widthStyle} className={[styles.menu, styles[color]].join(' ')}>
80+
<div style={widthStyle} className={[styles.menu, styles[color], "chromeDropdown"].join(' ')}>
8181
{this.props.options.map((o) => {
8282
let key = o;
8383
let value = o;

src/components/ColumnsConfiguration/ColumnsConfiguration.react.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import Icon from 'components/Icon/Icon.react';
1010
import Popover from 'components/Popover/Popover.react';
1111
import Position from 'lib/Position';
1212

13+
const POPOVER_CONTENT_ID = 'columnsConfigurationPopover';
14+
1315
export default class ColumnsConfiguration extends React.Component {
1416
constructor() {
1517
super();
@@ -57,8 +59,8 @@ export default class ColumnsConfiguration extends React.Component {
5759
let popover = null;
5860
if (this.state.open) {
5961
popover = (
60-
<Popover fixed={true} position={Position.inDocument(this.node)} onExternalClick={this.toggle.bind(this)}>
61-
<div className={styles.popover}>
62+
<Popover fixed={true} position={Position.inDocument(this.node)} onExternalClick={this.toggle.bind(this)} contentId={POPOVER_CONTENT_ID}>
63+
<div className={styles.popover} id={POPOVER_CONTENT_ID}>
6264
{title}
6365
<div className={styles.body}>
6466
<div className={styles.columnConfigContainer}>

src/components/ColumnsConfiguration/ColumnsConfiguration.scss

+6
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@
4242
}
4343
}
4444

45+
.objectPickerContent {
46+
.entry svg {
47+
fill: rgba(0, 0, 0, 0.3);
48+
}
49+
}
50+
4551
.body {
4652
color: white;
4753
position: absolute;

src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js

+19-17
Original file line numberDiff line numberDiff line change
@@ -60,25 +60,27 @@ export default class DataBrowserHeaderBar extends React.Component {
6060
);
6161
});
6262

63-
let finalStyle = {};
64-
if (headers.length % 2) {
65-
finalStyle.background = 'rgba(224,224,234,0.10)';
63+
if (onAddColumn) {
64+
let finalStyle = {};
65+
if (headers.length % 2) {
66+
finalStyle.background = 'rgba(224,224,234,0.10)';
67+
}
68+
69+
elements.push(
70+
readonly || preventSchemaEdits ? null : (
71+
<div key='add' className={styles.addColumn} style={finalStyle}>
72+
<a
73+
href='javascript:;'
74+
role='button'
75+
className={styles.addColumnButton}
76+
onClick={onAddColumn}>
77+
Add a new column
78+
</a>
79+
</div>
80+
)
81+
);
6682
}
6783

68-
elements.push(
69-
readonly || preventSchemaEdits ? null : (
70-
<div key='add' className={styles.addColumn} style={finalStyle}>
71-
<a
72-
href='javascript:;'
73-
role='button'
74-
className={styles.addColumnButton}
75-
onClick={onAddColumn}>
76-
Add a new column
77-
</a>
78-
</div>
79-
)
80-
);
81-
8284
return (
8385
<DndProvider backend={HTML5Backend}>
8486
<div className={styles.bar}>{elements}</div>

src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.scss

+8
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,11 @@
6464
margin: 0 -4px;
6565
cursor: ew-resize;
6666
}
67+
68+
.pickerPointer {
69+
.check {
70+
input {
71+
display: none;
72+
}
73+
}
74+
}

src/components/FileEditor/FileEditor.react.js

+15-7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default class FileEditor extends React.Component {
2020

2121
this.checkExternalClick = this.checkExternalClick.bind(this);
2222
this.handleKey = this.handleKey.bind(this);
23+
this.removeFile = this.removeFile.bind(this);
2324
}
2425

2526
componentDidMount() {
@@ -33,18 +34,20 @@ export default class FileEditor extends React.Component {
3334
}
3435

3536
checkExternalClick(e) {
36-
if (!hasAncestor(e.target, this.refs.input)) {
37-
this.props.onCommit(this.state.value);
37+
const { onCancel } = this.props;
38+
if (!hasAncestor(e.target, this.refs.input) && onCancel) {
39+
onCancel();
3840
}
3941
}
4042

4143
handleKey(e) {
42-
if (e.keyCode === 13) {
43-
this.props.onCommit(this.state.value);
44+
const { onCancel } = this.props;
45+
if (e.keyCode === 13 && onCancel) {
46+
onCancel();
4447
}
4548
}
4649

47-
getBase64(file){
50+
getBase64(file) {
4851
return new Promise((resolve, reject) => {
4952
const reader = new FileReader();
5053
reader.readAsDataURL(file);
@@ -53,6 +56,11 @@ export default class FileEditor extends React.Component {
5356
});
5457
}
5558

59+
removeFile() {
60+
this.refs.fileInput.value = '';
61+
this.props.onCommit(undefined);
62+
}
63+
5664
async handleChange(e) {
5765
let file = e.target.files[0];
5866
if (file) {
@@ -67,10 +75,10 @@ export default class FileEditor extends React.Component {
6775
<div ref='input' style={{ minWidth: this.props.width }} className={styles.editor}>
6876
{file && file.url() ? <a href={file.url()} target='_blank' role='button' className={styles.download}>Download</a> : null}
6977
<a className={styles.upload}>
70-
<input type='file' onChange={this.handleChange.bind(this)} />
78+
<input ref='fileInput' type='file' onChange={this.handleChange.bind(this)} />
7179
<span>{file ? 'Replace file' : 'Upload file'}</span>
7280
</a>
73-
{file ? <a href='javascript:;' role='button' className={styles.delete} onClick={() => this.props.onCommit(undefined)}>Delete</a> : null}
81+
{file ? <a href='javascript:;' role='button' className={styles.delete} onClick={this.removeFile}>Delete</a> : null}
7482
</div>
7583
);
7684
}

src/components/FileEditor/FileEditor.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141
opacity: 0;
4242
top: 0;
4343
right: 0;
44-
left: -100px;
4544
bottom: 0;
4645
cursor: pointer;
46+
width: 100%;
4747
}
4848
}

src/components/GeoPointEditor/GeoPointEditor.react.js

+18-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* the root directory of this source tree.
77
*/
88
import { GeoPoint } from 'parse';
9-
import hasAncestor from 'lib/hasAncestor';
109
import React from 'react';
1110
import styles from 'components/GeoPointEditor/GeoPointEditor.scss';
1211
import validateNumeric from 'lib/validateNumeric';
@@ -26,23 +25,31 @@ export default class GeoPointEditor extends React.Component {
2625
}
2726

2827
componentDidMount() {
29-
this.refs.latitude.focus();
28+
if (!this.props.disableAutoFocus) {
29+
this.refs.latitude.focus();
30+
}
3031
this.refs.latitude.setSelectionRange(0, String(this.state.latitude).length);
31-
document.body.addEventListener('click', this.checkExternalClick);
3232
this.refs.latitude.addEventListener('keypress', this.handleKeyLatitude);
3333
this.refs.longitude.addEventListener('keypress', this.handleKeyLongitude);
3434
}
3535

3636
componentWillUnmount() {
37-
document.body.removeEventListener('click', this.checkExternalClick);
3837
this.refs.latitude.removeEventListener('keypress', this.handleKeyLatitude);
3938
this.refs.longitude.removeEventListener('keypress', this.handleKeyLongitude);
4039
}
4140

42-
checkExternalClick(e) {
43-
if (!hasAncestor(e.target, this.refs.input)) {
44-
this.commitValue();
45-
}
41+
checkExternalClick() {
42+
// timeout needed because activeElement is set after onBlur event is done
43+
setTimeout(function() {
44+
// check if activeElement is something else from input fields,
45+
// to avoid commiting new value on every switch of focus beetween latitude and longitude fields
46+
if (
47+
document.activeElement !== this.refs.latitude &&
48+
document.activeElement !== this.refs.longitude
49+
) {
50+
this.commitValue();
51+
}
52+
}.bind(this), 1);
4653
}
4754

4855
handleKeyLatitude(e) {
@@ -112,14 +119,16 @@ export default class GeoPointEditor extends React.Component {
112119
this.setState({ [target]: validateNumeric(value) ? value : this.state[target] });
113120
};
114121
return (
115-
<div ref='input' style={{ width: this.props.width }} className={styles.editor}>
122+
<div ref='input' style={{ width: this.props.width, ...this.props.style }} className={styles.editor}>
116123
<input
117124
ref='latitude'
118125
value={this.state.latitude}
126+
onBlur={this.checkExternalClick}
119127
onChange={onChange.bind(this, 'latitude')} />
120128
<input
121129
ref='longitude'
122130
value={this.state.longitude}
131+
onBlur={this.checkExternalClick}
123132
onChange={onChange.bind(this, 'longitude')} />
124133
</div>
125134
);

0 commit comments

Comments
 (0)