Skip to content

Commit 87dd271

Browse files
douglasmuraokadavimacedo
authored andcommitted
feat(Database Browser): Copy cell value using CTRL+C (#1272)
1 parent b1819df commit 87dd271

File tree

6 files changed

+54
-22
lines changed

6 files changed

+54
-22
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"commander": "3.0.1",
4242
"connect-flash": "0.1.1",
4343
"cookie-session": "2.0.0-beta.3",
44+
"copy-to-clipboard": "^3.2.0",
4445
"create-react-class": "15.6.3",
4546
"csurf": "1.10.0",
4647
"express": "4.17.1",

src/components/BrowserCell/BrowserCell.react.js

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default class BrowserCell extends Component {
1818
super();
1919

2020
this.cellRef = React.createRef();
21+
this.copyableValue = undefined;
2122
}
2223

2324
componentDidUpdate() {
@@ -36,6 +37,10 @@ export default class BrowserCell extends Component {
3637
} else if (top < topBoundary || bottom > window.innerHeight) {
3738
node.scrollIntoView({ block: 'nearest', inline: 'nearest' });
3839
}
40+
41+
if (!this.props.hidden) {
42+
this.props.setCopyableValue(this.copyableValue);
43+
}
3944
}
4045
}
4146

@@ -58,21 +63,22 @@ export default class BrowserCell extends Component {
5863
}
5964

6065
render() {
61-
let { type, value, hidden, width, current, onSelect, onEditChange, setRelation, onPointerClick, row, col } = this.props;
66+
let { type, value, hidden, width, current, onSelect, onEditChange, setCopyableValue, setRelation, onPointerClick, row, col } = this.props;
6267
let content = value;
68+
this.copyableValue = content;
6369
let classes = [styles.cell, unselectable];
6470
if (hidden) {
6571
content = '(hidden)';
6672
classes.push(styles.empty);
6773
} else if (value === undefined) {
6874
if (type === 'ACL') {
69-
content = 'Public Read + Write';
75+
this.copyableValue = content = 'Public Read + Write';
7076
} else {
71-
content = '(undefined)';
77+
this.copyableValue = content = '(undefined)';
7278
classes.push(styles.empty);
7379
}
7480
} else if (value === null) {
75-
content = '(null)';
81+
this.copyableValue = content = '(null)';
7682
classes.push(styles.empty);
7783
} else if (value === '') {
7884
content = <span>&nbsp;</span>;
@@ -88,23 +94,22 @@ export default class BrowserCell extends Component {
8894
<Pill value={value.id} />
8995
</a>
9096
);
97+
this.copyableValue = value.id;
9198
} else if (type === 'Date') {
9299
if (typeof value === 'object' && value.__type) {
93100
value = new Date(value.iso);
94101
} else if (typeof value === 'string') {
95102
value = new Date(value);
96103
}
97-
content = dateStringUTC(value);
104+
this.copyableValue = content = dateStringUTC(value);
98105
} else if (type === 'Boolean') {
99-
content = value ? 'True' : 'False';
106+
this.copyableValue = content = value ? 'True' : 'False';
100107
} else if (type === 'Object' || type === 'Bytes' || type === 'Array') {
101-
content = JSON.stringify(value);
108+
this.copyableValue = content = JSON.stringify(value);
102109
} else if (type === 'File') {
103-
if (value.url()) {
104-
content = <Pill value={getFileName(value)} />;
105-
} else {
106-
content = <Pill value={'Uploading\u2026'} />;
107-
}
110+
const fileName = value.url() ? getFileName(value) : 'Uploading\u2026';
111+
content = <Pill value={fileName} />;
112+
this.copyableValue = fileName;
108113
} else if (type === 'ACL') {
109114
let pieces = [];
110115
let json = value.toJSON();
@@ -125,17 +130,18 @@ export default class BrowserCell extends Component {
125130
if (pieces.length === 0) {
126131
pieces.push('Master Key Only');
127132
}
128-
content = pieces.join(', ');
133+
this.copyableValue = content = pieces.join(', ');
129134
} else if (type === 'GeoPoint') {
130-
content = `(${value.latitude}, ${value.longitude})`;
135+
this.copyableValue = content = `(${value.latitude}, ${value.longitude})`;
131136
} else if (type === 'Polygon') {
132-
content = value.coordinates.map(coord => `(${coord})`)
137+
this.copyableValue = content = value.coordinates.map(coord => `(${coord})`)
133138
} else if (type === 'Relation') {
134139
content = (
135140
<div style={{ textAlign: 'center', cursor: 'pointer' }}>
136141
<Pill onClick={() => setRelation(value)} value='View relation' />
137142
</div>
138143
);
144+
this.copyableValue = undefined;
139145
}
140146

141147
if (current) {
@@ -146,7 +152,10 @@ export default class BrowserCell extends Component {
146152
ref={this.cellRef}
147153
className={classes.join(' ')}
148154
style={{ width }}
149-
onClick={() => onSelect({ row, col })}
155+
onClick={() => {
156+
onSelect({ row, col });
157+
setCopyableValue(hidden ? undefined : this.copyableValue);
158+
}}
150159
onDoubleClick={() => {
151160
if (type !== 'Relation') {
152161
onEditChange(true)

src/components/BrowserRow/BrowserRow.react.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default class BrowserRow extends Component {
1818
}
1919

2020
render() {
21-
const { className, columns, currentCol, isUnique, obj, onPointerClick, order, readOnlyFields, row, rowWidth, selection, selectRow, setCurrent, setEditing, setRelation } = this.props;
21+
const { className, columns, currentCol, isUnique, obj, onPointerClick, order, readOnlyFields, row, rowWidth, selection, selectRow, setCopyableValue, setCurrent, setEditing, setRelation } = this.props;
2222
let attributes = obj.attributes;
2323
return (
2424
<div className={styles.tableRow} style={{ minWidth: rowWidth }}>
@@ -71,7 +71,8 @@ export default class BrowserRow extends Component {
7171
onPointerClick={onPointerClick}
7272
setRelation={setRelation}
7373
value={attr}
74-
hidden={hidden} />
74+
hidden={hidden}
75+
setCopyableValue={setCopyableValue} />
7576
);
7677
})}
7778
</div>

src/dashboard/Data/Browser/Browser.react.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class Browser extends DashboardView {
113113
this.createClass = this.createClass.bind(this);
114114
this.addColumn = this.addColumn.bind(this);
115115
this.removeColumn = this.removeColumn.bind(this);
116+
this.showNote = this.showNote.bind(this);
116117
}
117118

118119
componentWillMount() {
@@ -974,7 +975,8 @@ class Browser extends DashboardView {
974975
setRelation={this.setRelation}
975976
onAddColumn={this.showAddColumn}
976977
onAddRow={this.addRow}
977-
onAddClass={this.showCreateClass} />
978+
onAddClass={this.showCreateClass}
979+
showNote={this.showNote} />
978980
);
979981
}
980982
}

src/dashboard/Data/Browser/BrowserTable.react.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ export default class BrowserTable extends React.Component {
135135
selectRow={this.props.selectRow}
136136
setCurrent={this.props.setCurrent}
137137
setEditing={this.props.setEditing}
138-
setRelation={this.props.setRelation} />
138+
setRelation={this.props.setRelation}
139+
setCopyableValue={this.props.setCopyableValue} />
139140
</div>
140141
);
141142
}
@@ -167,7 +168,8 @@ export default class BrowserTable extends React.Component {
167168
selectRow={this.props.selectRow}
168169
setCurrent={this.props.setCurrent}
169170
setEditing={this.props.setEditing}
170-
setRelation={this.props.setRelation} />
171+
setRelation={this.props.setRelation}
172+
setCopyableValue={this.props.setCopyableValue} />
171173
}
172174

173175
if (this.props.editing) {

src/dashboard/Data/Browser/DataBrowser.react.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* This source code is licensed under the license found in the LICENSE file in
66
* the root directory of this source tree.
77
*/
8+
import copy from 'copy-to-clipboard';
89
import BrowserTable from 'dashboard/Data/Browser/BrowserTable.react';
910
import BrowserToolbar from 'dashboard/Data/Browser/BrowserToolbar.react';
1011
import * as ColumnPreferences from 'lib/ColumnPreferences';
@@ -32,6 +33,7 @@ export default class DataBrowser extends React.Component {
3233
order: order,
3334
current: null,
3435
editing: false,
36+
copyableValue: undefined
3537
};
3638

3739
this.handleKey = this.handleKey.bind(this);
@@ -40,13 +42,14 @@ export default class DataBrowser extends React.Component {
4042
this.setCurrent = this.setCurrent.bind(this);
4143
this.setEditing = this.setEditing.bind(this);
4244
this.handleColumnsOrder = this.handleColumnsOrder.bind(this);
45+
this.setCopyableValue = this.setCopyableValue.bind(this);
4346

4447
this.saveOrderTimeout = null;
4548
}
4649

4750
shouldComponentUpdate(nextProps, nextState) {
4851
const shallowVerifyStates = [...new Set(Object.keys(this.state).concat(Object.keys(nextState)))]
49-
.filter(stateName => stateName !== 'order');
52+
.filter(stateName => stateName !== 'order' && stateName !== 'copyableValue');
5053
if (shallowVerifyStates.some(stateName => this.state[stateName] !== nextState[stateName])) {
5154
return true;
5255
}
@@ -195,6 +198,13 @@ export default class DataBrowser extends React.Component {
195198
});
196199
e.preventDefault();
197200
break;
201+
case 67: // C
202+
if ((e.ctrlKey || e.metaKey) && this.state.copyableValue !== undefined) {
203+
copy(this.state.copyableValue); // Copies current cell value to clipboard
204+
this.props.showNote('Value copied to clipboard', false)
205+
e.preventDefault()
206+
}
207+
break;
198208
}
199209
}
200210

@@ -209,6 +219,12 @@ export default class DataBrowser extends React.Component {
209219
this.setState({ current });
210220
}
211221
}
222+
223+
setCopyableValue(copyableValue) {
224+
if (this.state.copyableValue !== copyableValue) {
225+
this.setState({ copyableValue });
226+
}
227+
}
212228

213229
handleColumnsOrder(order) {
214230
this.setState({ order: [ ...order ] }, () => {
@@ -230,6 +246,7 @@ export default class DataBrowser extends React.Component {
230246
handleResize={this.handleResize}
231247
setEditing={this.setEditing}
232248
setCurrent={this.setCurrent}
249+
setCopyableValue={this.setCopyableValue}
233250
{...other} />
234251
<BrowserToolbar
235252
count={count}

0 commit comments

Comments
 (0)