-
Notifications
You must be signed in to change notification settings - Fork 14.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[explore] refactor slice action button group (#1074)
* pull explore actions button group into component * use button component * make sure we render all action buttons * test that embed code is correct * don't need before each * generalize modal trigger for use with plain links or icons
- Loading branch information
Alanna Scott
authored
Sep 20, 2016
1 parent
32980a6
commit 0e7af8d
Showing
17 changed files
with
592 additions
and
203 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
caravel/assets/javascripts/components/CopyToClipboard.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import React, { PropTypes } from 'react'; | ||
import { Button, Tooltip, OverlayTrigger } from 'react-bootstrap'; | ||
|
||
const propTypes = { | ||
copyNode: PropTypes.node, | ||
onCopyEnd: PropTypes.func, | ||
shouldShowText: PropTypes.bool, | ||
text: PropTypes.string.isRequired, | ||
}; | ||
|
||
const defaultProps = { | ||
copyNode: <span>Copy</span>, | ||
onCopyEnd: () => {}, | ||
shouldShowText: true, | ||
}; | ||
|
||
export default class CopyToClipboard extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
hasCopied: false, | ||
}; | ||
|
||
// bindings | ||
this.copyToClipboard = this.copyToClipboard.bind(this); | ||
this.resetTooltipText = this.resetTooltipText.bind(this); | ||
this.onMouseOut = this.onMouseOut.bind(this); | ||
} | ||
|
||
onMouseOut() { | ||
// delay to avoid flash of text change on tooltip | ||
setTimeout(this.resetTooltipText, 200); | ||
} | ||
|
||
resetTooltipText() { | ||
this.setState({ hasCopied: false }); | ||
} | ||
|
||
copyToClipboard() { | ||
const textToCopy = this.props.text; | ||
const textArea = document.createElement('textarea'); | ||
|
||
textArea.style.position = 'fixed'; | ||
textArea.style.left = '-1000px'; | ||
textArea.value = textToCopy; | ||
|
||
document.body.appendChild(textArea); | ||
textArea.select(); | ||
|
||
try { | ||
if (!document.execCommand('copy')) { | ||
throw new Error('Not successful'); | ||
} | ||
} catch (err) { | ||
window.alert('Sorry, your browser does not support copying. Use Ctrl / Cmd + C!'); // eslint-disable-line | ||
} | ||
|
||
document.body.removeChild(textArea); | ||
|
||
this.setState({ hasCopied: true }); | ||
this.props.onCopyEnd(); | ||
} | ||
|
||
tooltipText() { | ||
let tooltipText; | ||
if (this.state.hasCopied) { | ||
tooltipText = 'Copied!'; | ||
} else { | ||
tooltipText = 'Copy text'; | ||
} | ||
return tooltipText; | ||
} | ||
|
||
render() { | ||
const tooltip = ( | ||
<Tooltip id="copy-to-clipboard-tooltip"> | ||
{this.tooltipText()} | ||
</Tooltip> | ||
); | ||
|
||
return ( | ||
<div> | ||
{this.props.shouldShowText && | ||
<span>{this.props.text}</span> | ||
} | ||
| ||
<OverlayTrigger placement="top" overlay={tooltip} trigger={['hover']}> | ||
<Button | ||
bsStyle="link" | ||
onClick={this.copyToClipboard} | ||
onMouseOut={this.onMouseOut} | ||
> | ||
{this.props.copyNode} | ||
</Button> | ||
</OverlayTrigger> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
CopyToClipboard.propTypes = propTypes; | ||
CopyToClipboard.defaultProps = defaultProps; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import React, { PropTypes } from 'react'; | ||
import { Modal } from 'react-bootstrap'; | ||
import cx from 'classnames'; | ||
|
||
const propTypes = { | ||
triggerNode: PropTypes.node.isRequired, | ||
modalTitle: PropTypes.string.isRequired, | ||
modalBody: PropTypes.node.isRequired, | ||
beforeOpen: PropTypes.func, | ||
isButton: PropTypes.bool, | ||
}; | ||
|
||
const defaultProps = { | ||
beforeOpen: () => {}, | ||
isButton: false, | ||
}; | ||
|
||
export default class ModalTrigger extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
showModal: false, | ||
}; | ||
this.open = this.open.bind(this); | ||
this.close = this.close.bind(this); | ||
} | ||
|
||
close() { | ||
this.setState({ showModal: false }); | ||
} | ||
|
||
open(e) { | ||
e.preventDefault(); | ||
this.props.beforeOpen(); | ||
this.setState({ showModal: true }); | ||
} | ||
|
||
render() { | ||
const classNames = cx({ | ||
'btn btn-default btn-sm': this.props.isButton, | ||
}); | ||
return ( | ||
<a href="#" className={classNames} onClick={this.open}> | ||
{this.props.triggerNode} | ||
<Modal show={this.state.showModal} onHide={this.close}> | ||
<Modal.Header closeButton> | ||
<Modal.Title>{this.props.modalTitle}</Modal.Title> | ||
</Modal.Header> | ||
<Modal.Body> | ||
{this.props.modalBody} | ||
</Modal.Body> | ||
</Modal> | ||
</a> | ||
); | ||
} | ||
} | ||
|
||
ModalTrigger.propTypes = propTypes; | ||
ModalTrigger.defaultProps = defaultProps; |
37 changes: 37 additions & 0 deletions
37
caravel/assets/javascripts/explore/components/DisplayQueryButton.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import React, { PropTypes } from 'react'; | ||
import ModalTrigger from './../../components/ModalTrigger'; | ||
|
||
const propTypes = { | ||
slice: PropTypes.object.isRequired, | ||
}; | ||
|
||
export default class DisplayQueryButton extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
viewSqlQuery: '', | ||
}; | ||
this.beforeOpen = this.beforeOpen.bind(this); | ||
} | ||
|
||
beforeOpen() { | ||
this.setState({ | ||
viewSqlQuery: this.props.slice.viewSqlQuery, | ||
}); | ||
} | ||
|
||
render() { | ||
const modalBody = (<pre>{this.state.viewSqlQuery}</pre>); | ||
return ( | ||
<ModalTrigger | ||
isButton | ||
triggerNode={<span>Query</span>} | ||
modalTitle="Query" | ||
modalBody={modalBody} | ||
beforeOpen={this.beforeOpen} | ||
/> | ||
); | ||
} | ||
} | ||
|
||
DisplayQueryButton.propTypes = propTypes; |
105 changes: 105 additions & 0 deletions
105
caravel/assets/javascripts/explore/components/EmbedCodeButton.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import React, { PropTypes } from 'react'; | ||
import CopyToClipboard from './../../components/CopyToClipboard'; | ||
import { Popover, OverlayTrigger } from 'react-bootstrap'; | ||
|
||
const propTypes = { | ||
slice: PropTypes.object.isRequired, | ||
}; | ||
|
||
export default class EmbedCodeButton extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
height: '400', | ||
width: '600', | ||
srcLink: window.location.origin + props.slice.data.standalone_endpoint, | ||
}; | ||
this.handleInputChange = this.handleInputChange.bind(this); | ||
} | ||
|
||
handleInputChange(e) { | ||
const value = e.currentTarget.value; | ||
const name = e.currentTarget.name; | ||
const data = {}; | ||
data[name] = value; | ||
this.setState(data); | ||
} | ||
|
||
generateEmbedHTML() { | ||
const { width, height, srcLink } = this.state; | ||
/* eslint max-len: 0 */ | ||
const embedHTML = | ||
`<iframe src="${srcLink}" width="${width}" height="${height}" seamless frameBorder="0" scrolling="no"></iframe>`; | ||
return embedHTML; | ||
} | ||
|
||
renderPopover() { | ||
const html = this.generateEmbedHTML(); | ||
return ( | ||
<Popover id="embed-code-popover"> | ||
<div> | ||
<div className="row"> | ||
<div className="col-sm-10"> | ||
<textarea name="embedCode" value={html} rows="4" readOnly className="form-control input-sm"></textarea> | ||
</div> | ||
<div className="col-sm-2"> | ||
<CopyToClipboard | ||
shouldShowText={false} | ||
text={html} | ||
copyNode={<i className="fa fa-clipboard" title="Copy to clipboard"></i>} | ||
/> | ||
</div> | ||
</div> | ||
<br /> | ||
<div className="row"> | ||
<div className="col-md-6 col-sm-12"> | ||
<div className="form-group"> | ||
<small> | ||
<label className="control-label" htmlFor="embed-height">Height</label> | ||
</small> | ||
<input | ||
className="form-control input-sm" | ||
type="text" | ||
defaultValue={this.state.height} | ||
name="height" | ||
onChange={this.handleInputChange} | ||
/> | ||
</div> | ||
</div> | ||
<div className="col-md-6 col-sm-12"> | ||
<div className="form-group"> | ||
<small> | ||
<label className="control-label" htmlFor="embed-width">Width</label> | ||
</small> | ||
<input | ||
className="form-control input-sm" | ||
type="text" | ||
defaultValue={this.state.width} | ||
name="width" | ||
onChange={this.handleInputChange} | ||
id="embed-width" | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</Popover> | ||
); | ||
} | ||
render() { | ||
return ( | ||
<OverlayTrigger | ||
trigger="click" | ||
rootClose | ||
placement="left" | ||
overlay={this.renderPopover()} | ||
> | ||
<span className="btn btn-default btn-sm"> | ||
<i className="fa fa-code"></i> | ||
</span> | ||
</OverlayTrigger> | ||
); | ||
} | ||
} | ||
|
||
EmbedCodeButton.propTypes = propTypes; |
46 changes: 46 additions & 0 deletions
46
caravel/assets/javascripts/explore/components/ExploreActionButtons.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import React, { PropTypes } from 'react'; | ||
import cx from 'classnames'; | ||
import URLShortLinkButton from './URLShortLinkButton'; | ||
import EmbedCodeButton from './EmbedCodeButton'; | ||
import DisplayQueryButton from './DisplayQueryButton'; | ||
|
||
const propTypes = { | ||
canDownload: PropTypes.string.isRequired, | ||
slice: PropTypes.object.isRequired, | ||
}; | ||
|
||
export default function ExploreActionButtons({ canDownload, slice }) { | ||
const exportToCSVClasses = cx('btn btn-default btn-sm', { | ||
'disabled disabledButton': !canDownload, | ||
}); | ||
|
||
return ( | ||
<div className="btn-group results" role="group"> | ||
<URLShortLinkButton slice={slice} /> | ||
|
||
<EmbedCodeButton slice={slice} /> | ||
|
||
<a | ||
href={slice.data.json_endpoint} | ||
className="btn btn-default btn-sm" | ||
title="Export to .json" | ||
target="_blank" | ||
> | ||
<i className="fa fa-file-code-o"></i> .json | ||
</a> | ||
|
||
<a | ||
href={slice.data.csv_endpoint} | ||
className={exportToCSVClasses} | ||
title="Export to .csv format" | ||
target="_blank" | ||
> | ||
<i className="fa fa-file-text-o"></i> .csv | ||
</a> | ||
|
||
<DisplayQueryButton slice={slice} /> | ||
</div> | ||
); | ||
} | ||
|
||
ExploreActionButtons.propTypes = propTypes; |
Oops, something went wrong.