Added clipboardinput, sharing logic, CSS fixes

pull/357/head
Piero Toffanin 2017-12-03 13:37:13 -05:00
rodzic 2552b82e4f
commit 39c391451f
12 zmienionych plików z 186 dodań i 22 usunięć

Wyświetl plik

@ -189,7 +189,7 @@ pre.prettyprint,
}
/* Success */
.task-list-item .status-label.done{
.task-list-item .status-label.done, .theme-background-success{
background-color: theme("success");
}

Wyświetl plik

@ -53,6 +53,16 @@ export default {
clone: function(obj){
return JSON.parse(JSON.stringify(obj));
},
// "/a/b" --> http://localhost/a/b
absoluteUrl: function(path, href = window.location.href){
if (path[0] === '/') path = path.slice(1);
let parser = document.createElement('a');
parser.href = window.location.href;
return `${parser.protocol}//${parser.host}/${path}`;
}
};

Wyświetl plik

@ -0,0 +1,42 @@
import React from 'react';
import PropTypes from 'prop-types';
import Clipboard from 'clipboard';
import '../css/ClipboardInput.scss';
class ClipboardInput extends React.Component{
constructor(props){
super(props);
this.state = {
showCopied: false
};
}
componentDidMount(){
this.clipboard = new Clipboard(this.dom, {
target: () => this.dom
}).on('success', () => {
this.setState({showCopied: true});
});
}
componentWillUnmount(){
this.clipboard.destroy();
}
render(){
return (
<div className="clipboardInput">
<input
{...this.props}
ref={(domNode) => { this.dom = domNode; }}
onBlur={() => { this.setState({showCopied: false}); }}
/>
<div style={{position: 'relative', 'width': '100%'}}>
<div className={"copied theme-background-success " + (this.state.showCopied ? "show" : "")}>Copied to clipboard!</div>
</div>
</div>);
}
}
export default ClipboardInput;

Wyświetl plik

@ -51,6 +51,7 @@ class Map extends React.Component {
this.loadImageryLayers = this.loadImageryLayers.bind(this);
this.updatePopupFor = this.updatePopupFor.bind(this);
this.handleMapMouseDown = this.handleMapMouseDown.bind(this);
}
updatePopupFor(layer){
@ -257,16 +258,29 @@ class Map extends React.Component {
}
}
handleMapMouseDown(e){
// Make sure the share popup closes
this.shareButton.hidePopup();
}
render() {
return (
<div style={{height: "100%"}} className="map">
<ErrorMessage bind={[this, 'error']} />
<div
style={{height: "100%"}}
ref={(domNode) => (this.container = domNode)}>
ref={(domNode) => (this.container = domNode)}
onMouseDown={this.handleMapMouseDown}
>
</div>
<div className="actionButtons">
{this.state.singleTask !== null ?
<ShareButton task={this.state.singleTask} />
<ShareButton
ref={(ref) => { this.shareButton = ref; }}
task={this.state.singleTask}
/>
: ""}
<SwitchModeButton
task={this.state.singleTask}

Wyświetl plik

@ -15,26 +15,41 @@ class ShareButton extends React.Component {
constructor(props){
super(props);
this.state = { showPopup: false };
this.state = {
showPopup: false,
task: props.task
};
this.handleClick = this.handleClick.bind(this);
this.handleTaskChanged = this.handleTaskChanged.bind(this);
}
handleClick(){
this.setState({ showPopup: !this.state.showPopup });
}
hidePopup(){
this.setState({showPopup: false});
}
handleTaskChanged(task){
this.setState({task});
}
render() {
return (
<div className="shareButton">
<div className="shareButton" onClick={e => { e.stopPropagation(); }}>
{this.state.showPopup ?
<SharePopup task={this.props.task} />
<SharePopup
task={this.state.task}
taskChanged={this.handleTaskChanged}
/>
: ""}
<button
ref={(domNode) => { this.shareButton = domNode; }}
type="button"
onClick={this.handleClick}
className={"shareButton btn btn-sm " + (this.props.task.public ? "btn-primary" : "btn-secondary")}>
className={"shareButton btn btn-sm " + (this.state.task.public ? "btn-primary" : "btn-secondary")}>
<i className="fa fa-share-alt"></i> Share
</button>
</div>

Wyświetl plik

@ -2,12 +2,16 @@ import React from 'react';
import '../css/SharePopup.scss';
import PropTypes from 'prop-types';
import ErrorMessage from './ErrorMessage';
import Utils from '../classes/Utils';
import ClipboardInput from './ClipboardInput';
class SharePopup extends React.Component{
static propTypes = {
task: PropTypes.object.isRequired
task: PropTypes.object.isRequired,
taskChanged: PropTypes.func
};
static defaultProps = {
taskChanged: () => {}
};
constructor(props){
@ -22,6 +26,12 @@ class SharePopup extends React.Component{
this.handleEnableSharing = this.handleEnableSharing.bind(this);
}
componentDidMount(){
if (!this.state.task.public){
this.handleEnableSharing();
}
}
handleEnableSharing(e){
const { task } = this.state;
@ -31,13 +41,14 @@ class SharePopup extends React.Component{
url: `/api/projects/${task.project}/tasks/${task.id}/`,
contentType: 'application/json',
data: JSON.stringify({
public: e.target.checked
public: !this.state.task.public
}),
dataType: 'json',
type: 'PATCH'
})
.done((task) => {
this.setState({task});
this.props.taskChanged(task);
})
.fail(() => this.setState({error: "An error occurred. Check your connection and permissions."}))
.always(() => {
@ -46,13 +57,17 @@ class SharePopup extends React.Component{
}
render(){
const shareLink = Utils.absoluteUrl(`/public/task/${this.state.task.id}/map/`);
const iframeUrl = Utils.absoluteUrl(`public/task/${this.state.task.id}/iframe/`);
const iframeCode = `<iframe>${iframeUrl}</iframe>`;
return (<div className="sharePopup popover top in">
<div className="arrow"></div>
<h3 className="popover-title theme-background-highlight">Share</h3>
<h3 className="popover-title theme-background-highlight">Share This Task</h3>
<div className="popover-content theme-secondary">
<ErrorMessage bind={[this, 'error']} />
<div className="checkbox">
<label>
<label onClick={this.handleEnableSharing}>
{this.state.togglingShare ?
<i className="fa fa-refresh fa-spin fa-fw"></i>
: ""}
@ -61,18 +76,38 @@ class SharePopup extends React.Component{
className={this.state.togglingShare ? "hide" : ""}
type="checkbox"
checked={this.state.task.public}
onChange={this.handleEnableSharing}
onChange={() => {}}
/>
Enable sharing
Enabled
</label>
</div>
{this.state.task.public ?
<div>A large<br/>bunch<br/>bunch<br/>bunch<br/>bunch<br/>bunch<br/></div>
: ""}
<div className={"share-links " + (this.state.task.public ? "show" : "")}>
<div className="form-group">
<label>
Link:
<ClipboardInput
type="text"
className="form-control"
value={shareLink}
readOnly={true}
/>
</label>
</div>
<div className="form-group">
<label>
HTML iframe:
<ClipboardInput
type="text"
className="form-control"
value={iframeCode}
readOnly={true}
/>
</label>
</div>
</div>
</div>
</div>);
}
}
}
export default SharePopup;

Wyświetl plik

@ -0,0 +1,10 @@
import React from 'react';
import { shallow } from 'enzyme';
import ClipboardInput from '../ClipboardInput';
describe('<ClipboardInput />', () => {
it('renders without exploding', () => {
const wrapper = shallow(<ClipboardInput type="text" />);
expect(wrapper.exists()).toBe(true);
})
});

Wyświetl plik

@ -0,0 +1,21 @@
.clipboardInput{
text-align: center;
.copied{
position: absolute;
font-size: 70%;
left: 50%;
width: 110px;
margin-left: -55px;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
padding: 2px;
opacity: 0;
transition: opacity .2s ease-in-out;
&.show{
opacity: 1;
}
}
}

Wyświetl plik

@ -1,8 +1,7 @@
.shareButton{
position: absolute;
z-index: 2000;
bottom: 12px;
bottom: -11px;
right: 38px;
button{

Wyświetl plik

@ -1,6 +1,6 @@
.sharePopup{
position: relative;
top: -56px;
top: -32px;
display: block;
&.popover.top > .arrow{
@ -20,5 +20,22 @@
margin-left: -25px;
margin-right: 5px;
}
.share-links{
& > div{
margin-top: 8px;
&:first-child{
margin-top: 4px;
}
}
max-height: 0;
overflow: hidden;
transition: max-height 1s ease-in-out;
&.show{
max-height: 800px;
height: auto;
}
}
}

Wyświetl plik

@ -2,6 +2,6 @@
border-width: 1px;
position: absolute;
z-index: 2000;
bottom: 24px;
bottom: -22px;
right: 12px;
}

Wyświetl plik

@ -29,6 +29,7 @@
"babel-plugin-transform-class-properties": "^6.18.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"clipboard": "^1.7.1",
"css-loader": "^0.25.0",
"d3": "^3.5.5",
"enzyme": "^2.9.1",