diff --git a/package.json b/package.json index 576ebd9aeb..4efa7f3c34 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "commander": "3.0.1", "connect-flash": "0.1.1", "cookie-session": "2.0.0-beta.3", + "copy-to-clipboard": "^3.2.0", "create-react-class": "15.6.3", "csurf": "1.10.0", "express": "4.17.1", diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index bfb6d9801a..b2045d6d4a 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -18,6 +18,7 @@ export default class BrowserCell extends Component { super(); this.cellRef = React.createRef(); + this.copyableValue = undefined; } componentDidUpdate() { @@ -36,6 +37,10 @@ export default class BrowserCell extends Component { } else if (top < topBoundary || bottom > window.innerHeight) { node.scrollIntoView({ block: 'nearest', inline: 'nearest' }); } + + if (!this.props.hidden) { + this.props.setCopyableValue(this.copyableValue); + } } } @@ -58,21 +63,22 @@ export default class BrowserCell extends Component { } render() { - let { type, value, hidden, width, current, onSelect, onEditChange, setRelation, onPointerClick, row, col } = this.props; + let { type, value, hidden, width, current, onSelect, onEditChange, setCopyableValue, setRelation, onPointerClick, row, col } = this.props; let content = value; + this.copyableValue = content; let classes = [styles.cell, unselectable]; if (hidden) { content = '(hidden)'; classes.push(styles.empty); } else if (value === undefined) { if (type === 'ACL') { - content = 'Public Read + Write'; + this.copyableValue = content = 'Public Read + Write'; } else { - content = '(undefined)'; + this.copyableValue = content = '(undefined)'; classes.push(styles.empty); } } else if (value === null) { - content = '(null)'; + this.copyableValue = content = '(null)'; classes.push(styles.empty); } else if (value === '') { content =  ; @@ -88,23 +94,22 @@ export default class BrowserCell extends Component { ); + this.copyableValue = value.id; } else if (type === 'Date') { if (typeof value === 'object' && value.__type) { value = new Date(value.iso); } else if (typeof value === 'string') { value = new Date(value); } - content = dateStringUTC(value); + this.copyableValue = content = dateStringUTC(value); } else if (type === 'Boolean') { - content = value ? 'True' : 'False'; + this.copyableValue = content = value ? 'True' : 'False'; } else if (type === 'Object' || type === 'Bytes' || type === 'Array') { - content = JSON.stringify(value); + this.copyableValue = content = JSON.stringify(value); } else if (type === 'File') { - if (value.url()) { - content = ; - } else { - content = ; - } + const fileName = value.url() ? getFileName(value) : 'Uploading\u2026'; + content = ; + this.copyableValue = fileName; } else if (type === 'ACL') { let pieces = []; let json = value.toJSON(); @@ -125,17 +130,18 @@ export default class BrowserCell extends Component { if (pieces.length === 0) { pieces.push('Master Key Only'); } - content = pieces.join(', '); + this.copyableValue = content = pieces.join(', '); } else if (type === 'GeoPoint') { - content = `(${value.latitude}, ${value.longitude})`; + this.copyableValue = content = `(${value.latitude}, ${value.longitude})`; } else if (type === 'Polygon') { - content = value.coordinates.map(coord => `(${coord})`) + this.copyableValue = content = value.coordinates.map(coord => `(${coord})`) } else if (type === 'Relation') { content = (
setRelation(value)} value='View relation' />
); + this.copyableValue = undefined; } if (current) { @@ -146,7 +152,10 @@ export default class BrowserCell extends Component { ref={this.cellRef} className={classes.join(' ')} style={{ width }} - onClick={() => onSelect({ row, col })} + onClick={() => { + onSelect({ row, col }); + setCopyableValue(hidden ? undefined : this.copyableValue); + }} onDoubleClick={() => { if (type !== 'Relation') { onEditChange(true) diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js index ad15e697d9..5b7cf8d4d0 100644 --- a/src/components/BrowserRow/BrowserRow.react.js +++ b/src/components/BrowserRow/BrowserRow.react.js @@ -18,7 +18,7 @@ export default class BrowserRow extends Component { } render() { - const { className, columns, currentCol, isUnique, obj, onPointerClick, order, readOnlyFields, row, rowWidth, selection, selectRow, setCurrent, setEditing, setRelation } = this.props; + const { className, columns, currentCol, isUnique, obj, onPointerClick, order, readOnlyFields, row, rowWidth, selection, selectRow, setCopyableValue, setCurrent, setEditing, setRelation } = this.props; let attributes = obj.attributes; return (
@@ -71,7 +71,8 @@ export default class BrowserRow extends Component { onPointerClick={onPointerClick} setRelation={setRelation} value={attr} - hidden={hidden} /> + hidden={hidden} + setCopyableValue={setCopyableValue} /> ); })}
diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index dcbe2baaec..1f6da6ed1a 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -113,6 +113,7 @@ class Browser extends DashboardView { this.createClass = this.createClass.bind(this); this.addColumn = this.addColumn.bind(this); this.removeColumn = this.removeColumn.bind(this); + this.showNote = this.showNote.bind(this); } componentWillMount() { @@ -974,7 +975,8 @@ class Browser extends DashboardView { setRelation={this.setRelation} onAddColumn={this.showAddColumn} onAddRow={this.addRow} - onAddClass={this.showCreateClass} /> + onAddClass={this.showCreateClass} + showNote={this.showNote} /> ); } } diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js index f88ce875a1..414c2659d8 100644 --- a/src/dashboard/Data/Browser/BrowserTable.react.js +++ b/src/dashboard/Data/Browser/BrowserTable.react.js @@ -135,7 +135,8 @@ export default class BrowserTable extends React.Component { selectRow={this.props.selectRow} setCurrent={this.props.setCurrent} setEditing={this.props.setEditing} - setRelation={this.props.setRelation} /> + setRelation={this.props.setRelation} + setCopyableValue={this.props.setCopyableValue} /> ); } @@ -167,7 +168,8 @@ export default class BrowserTable extends React.Component { selectRow={this.props.selectRow} setCurrent={this.props.setCurrent} setEditing={this.props.setEditing} - setRelation={this.props.setRelation} /> + setRelation={this.props.setRelation} + setCopyableValue={this.props.setCopyableValue} /> } if (this.props.editing) { diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index b79f9174b3..87434cdca9 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -5,6 +5,7 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ +import copy from 'copy-to-clipboard'; import BrowserTable from 'dashboard/Data/Browser/BrowserTable.react'; import BrowserToolbar from 'dashboard/Data/Browser/BrowserToolbar.react'; import * as ColumnPreferences from 'lib/ColumnPreferences'; @@ -32,6 +33,7 @@ export default class DataBrowser extends React.Component { order: order, current: null, editing: false, + copyableValue: undefined }; this.handleKey = this.handleKey.bind(this); @@ -40,13 +42,14 @@ export default class DataBrowser extends React.Component { this.setCurrent = this.setCurrent.bind(this); this.setEditing = this.setEditing.bind(this); this.handleColumnsOrder = this.handleColumnsOrder.bind(this); + this.setCopyableValue = this.setCopyableValue.bind(this); this.saveOrderTimeout = null; } shouldComponentUpdate(nextProps, nextState) { const shallowVerifyStates = [...new Set(Object.keys(this.state).concat(Object.keys(nextState)))] - .filter(stateName => stateName !== 'order'); + .filter(stateName => stateName !== 'order' && stateName !== 'copyableValue'); if (shallowVerifyStates.some(stateName => this.state[stateName] !== nextState[stateName])) { return true; } @@ -195,6 +198,13 @@ export default class DataBrowser extends React.Component { }); e.preventDefault(); break; + case 67: // C + if ((e.ctrlKey || e.metaKey) && this.state.copyableValue !== undefined) { + copy(this.state.copyableValue); // Copies current cell value to clipboard + this.props.showNote('Value copied to clipboard', false) + e.preventDefault() + } + break; } } @@ -209,6 +219,12 @@ export default class DataBrowser extends React.Component { this.setState({ current }); } } + + setCopyableValue(copyableValue) { + if (this.state.copyableValue !== copyableValue) { + this.setState({ copyableValue }); + } + } handleColumnsOrder(order) { this.setState({ order: [ ...order ] }, () => { @@ -230,6 +246,7 @@ export default class DataBrowser extends React.Component { handleResize={this.handleResize} setEditing={this.setEditing} setCurrent={this.setCurrent} + setCopyableValue={this.setCopyableValue} {...other} />