-
Notifications
You must be signed in to change notification settings - Fork 14.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Play scrubber #4336
Play scrubber #4336
Changes from all commits
c31e642
4ba5e6e
e66ce7a
a112a62
b870862
5d6f62f
4caa673
52369ac
387ce72
bd2d45e
ef59605
cdd7139
dcf9561
4946c33
6718276
932b2fb
62ddd0e
813d1b0
a5697a1
f9b3c65
f8e9ce6
5f34f63
01be0dc
a18f8e0
1f0e026
1afa41e
f3c1a45
092f1e0
ec92c63
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
.play-slider { | ||
height: 100px; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. counldn't figure out how the DeckGL container knows how to set itself to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's the space currently left in the bottom, below the DeckGL container. TBH I wasn't really sure about the best way to place this. |
||
margin-top: -5px; | ||
} | ||
|
||
.slider-selection { | ||
background: #efefef; | ||
} | ||
|
||
.slider-handle { | ||
background: #b3b3b3; | ||
} | ||
|
||
.slider.slider-horizontal { | ||
width: 100% !important; | ||
} | ||
|
||
.slider-button { | ||
color: #b3b3b3; | ||
margin-right: 5px; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { Row, Col } from 'react-bootstrap'; | ||
|
||
import Mousetrap from 'mousetrap'; | ||
|
||
import 'bootstrap-slider/dist/css/bootstrap-slider.min.css'; | ||
import ReactBootstrapSlider from 'react-bootstrap-slider'; | ||
import './PlaySlider.css'; | ||
|
||
import { t } from '../javascripts/locales'; | ||
|
||
const propTypes = { | ||
start: PropTypes.number.isRequired, | ||
step: PropTypes.number.isRequired, | ||
end: PropTypes.number.isRequired, | ||
values: PropTypes.array.isRequired, | ||
onChange: PropTypes.func, | ||
loopDuration: PropTypes.number, | ||
maxFrames: PropTypes.number, | ||
orientation: PropTypes.oneOf(['horizontal', 'vertical']), | ||
reversed: PropTypes.bool, | ||
disabled: PropTypes.bool, | ||
}; | ||
|
||
const defaultProps = { | ||
onChange: () => {}, | ||
loopDuration: 15000, | ||
maxFrames: 100, | ||
orientation: 'horizontal', | ||
reversed: false, | ||
disabled: false, | ||
}; | ||
|
||
export default class PlaySlider extends React.PureComponent { | ||
constructor(props) { | ||
super(props); | ||
this.state = { intervalId: null }; | ||
|
||
const range = props.end - props.start; | ||
const frames = Math.min(props.maxFrames, range / props.step); | ||
const width = range / frames; | ||
this.intervalMilliseconds = props.loopDuration / frames; | ||
this.increment = width < props.step ? props.step : width - (width % props.step); | ||
|
||
this.onChange = this.onChange.bind(this); | ||
this.play = this.play.bind(this); | ||
this.pause = this.pause.bind(this); | ||
this.step = this.step.bind(this); | ||
this.getPlayClass = this.getPlayClass.bind(this); | ||
this.formatter = this.formatter.bind(this); | ||
} | ||
componentDidMount() { | ||
Mousetrap.bind(['space'], this.play); | ||
} | ||
componentWillUnmount() { | ||
Mousetrap.unbind(['space']); | ||
} | ||
onChange(event) { | ||
this.props.onChange(event.target.value); | ||
if (this.state.intervalId != null) { | ||
this.pause(); | ||
} | ||
} | ||
getPlayClass() { | ||
if (this.state.intervalId == null) { | ||
return 'fa fa-play fa-lg slider-button'; | ||
} | ||
return 'fa fa-pause fa-lg slider-button'; | ||
} | ||
play() { | ||
if (this.props.disabled) { | ||
return; | ||
} | ||
if (this.state.intervalId != null) { | ||
this.pause(); | ||
} else { | ||
const id = setInterval(this.step, this.intervalMilliseconds); | ||
this.setState({ intervalId: id }); | ||
} | ||
} | ||
pause() { | ||
clearInterval(this.state.intervalId); | ||
this.setState({ intervalId: null }); | ||
} | ||
step() { | ||
if (this.props.disabled) { | ||
return; | ||
} | ||
let values = this.props.values.map(value => value + this.increment); | ||
if (values[1] > this.props.end) { | ||
const cr = values[0] - this.props.start; | ||
values = values.map(value => value - cr); | ||
} | ||
this.props.onChange(values); | ||
} | ||
formatter(values) { | ||
if (this.props.disabled) { | ||
return t('Data has no time steps'); | ||
} | ||
|
||
let parts = values; | ||
if (!Array.isArray(values)) { | ||
parts = [values]; | ||
} else if (values[0] === values[1]) { | ||
parts = [values[0]]; | ||
} | ||
return parts.map(value => (new Date(value)).toUTCString()).join(' : '); | ||
} | ||
render() { | ||
return ( | ||
<Row className="play-slider"> | ||
<Col md={1} className="padded"> | ||
<i className={this.getPlayClass()} onClick={this.play} /> | ||
<i className="fa fa-step-forward fa-lg slider-button " onClick={this.step} /> | ||
</Col> | ||
<Col md={11} className="padded"> | ||
<ReactBootstrapSlider | ||
value={this.props.values} | ||
formatter={this.formatter} | ||
change={this.onChange} | ||
min={this.props.start} | ||
max={this.props.end} | ||
step={this.props.step} | ||
orientation={this.props.orientation} | ||
reversed={this.props.reversed} | ||
disabled={this.props.disabled ? 'disabled' : 'enabled'} | ||
/> | ||
</Col> | ||
</Row> | ||
); | ||
} | ||
} | ||
|
||
PlaySlider.propTypes = propTypes; | ||
PlaySlider.defaultProps = defaultProps; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import DeckGLContainer from './DeckGLContainer'; | ||
import PlaySlider from '../PlaySlider'; | ||
|
||
const propTypes = { | ||
getLayers: PropTypes.func.isRequired, | ||
start: PropTypes.number.isRequired, | ||
end: PropTypes.number.isRequired, | ||
step: PropTypes.number.isRequired, | ||
values: PropTypes.array.isRequired, | ||
disabled: PropTypes.bool, | ||
viewport: PropTypes.object.isRequired, | ||
}; | ||
|
||
const defaultProps = { | ||
disabled: false, | ||
}; | ||
|
||
export default class AnimatableDeckGLContainer extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
const { getLayers, start, end, step, values, disabled, viewport, ...other } = props; | ||
this.state = { values, viewport }; | ||
this.other = other; | ||
} | ||
componentWillReceiveProps(nextProps) { | ||
this.setState({ values: nextProps.values, viewport: nextProps.viewport }); | ||
} | ||
render() { | ||
const layers = this.props.getLayers(this.state.values); | ||
return ( | ||
<div> | ||
<DeckGLContainer | ||
{...this.other} | ||
viewport={this.state.viewport} | ||
layers={layers} | ||
onViewportChange={newViewport => this.setState({ viewport: newViewport })} | ||
/> | ||
{!this.props.disabled && | ||
<PlaySlider | ||
start={this.props.start} | ||
end={this.props.end} | ||
step={this.props.step} | ||
values={this.state.values} | ||
onChange={newValues => this.setState({ values: newValues })} | ||
/> | ||
} | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
AnimatableDeckGLContainer.propTypes = propTypes; | ||
AnimatableDeckGLContainer.defaultProps = defaultProps; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, looks suited to support a nice SliderControl eventually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it should be straightforward to add the SliderControl — I actually added it at some point, and then removed.
(The latest version of the component is incompatible with other dependencies, which is why I fixed the version to 2.0.1, BTW.)