Skip to content

Commit 5e6deea

Browse files
committed
Finalized form
1 parent 70abbfb commit 5e6deea

15 files changed

Lines changed: 355 additions & 88 deletions

File tree

web/client/actions/playback.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const FRAMES_LOADING = "PLAYBACK:FRAMES_LOADING";
88
const SET_CURRENT_FRAME = "PLAYBACK:SET_CURRENT_FRAME";
99
const SELECT_PLAYBACK_RANGE = "PLAYBACK:SELECT_PLAYBACK_RANGE";
1010

11+
const CHANGE_SETTING = "PLAYBACK:SETTINGS_CHANGE";
12+
1113
const STATUS = {
1214
PLAY: "PLAY",
1315
STOP: "STOP",
@@ -22,6 +24,7 @@ const setCurrentFrame = frame => ({ type: SET_CURRENT_FRAME, frame});
2224
const appendFrames = (frames) => ({ type: APPEND_FRAMES, frames});
2325
const framesLoading = loading => ({ type: FRAMES_LOADING, loading});
2426
const selectPlaybackRange = range => ({ type: SELECT_PLAYBACK_RANGE, range});
27+
const changeSetting = (name, value) => ({type: CHANGE_SETTING, name, value });
2528

2629
module.exports = {
2730
play,
@@ -32,6 +35,7 @@ module.exports = {
3235
framesLoading,
3336
setCurrentFrame,
3437
selectPlaybackRange,
38+
changeSetting,
3539
PLAY,
3640
PAUSE,
3741
STOP,
@@ -40,5 +44,6 @@ module.exports = {
4044
APPEND_FRAMES,
4145
FRAMES_LOADING,
4246
SET_CURRENT_FRAME,
43-
SELECT_PLAYBACK_RANGE
47+
SELECT_PLAYBACK_RANGE,
48+
CHANGE_SETTING
4449
};

web/client/components/misc/switch/SwitchButton.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
const React = require('react');
22
const PropTypes = require('prop-types');
3-
3+
/**
4+
* Graphical Switch button.
5+
* @param {boolean} [checked=false] the status of the button
6+
* @prop {function} onChange handler for change
7+
*/
48
class SwitchButton extends React.Component {
59

610
static propTypes = {

web/client/components/playback/Playback.jsx

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ const collapsible = compose(
3333
const PlaybackSettings = connect(() => ({}))(require("./PlaybackSettings"));
3434

3535
module.exports = collapsible(({
36-
frameDuration,
36+
settings,
37+
onSettingChange = () => { },
3738
setPlaybackRange = () => { },
38-
3939
// loading,
4040
selectedLayer,
4141
status,
@@ -45,39 +45,41 @@ module.exports = collapsible(({
4545
stop = () => {},
4646
showSettings,
4747
onShowSettings = () => {}
48-
}) => ( <div style={{display: 'flex'}}>
49-
{showSettings &&
50-
<PlaybackSettings
51-
animationPeriod={frameDuration}
52-
setPlaybackRange={setPlaybackRange}/>}
53-
<Toolbar
54-
btnDefaultProps={{
55-
className: 'square-button-md',
56-
bsStyle: 'primary'
57-
}}
58-
buttons={[
59-
{
60-
glyph: "step-backward",
61-
tooltip: 'Step backward'
62-
}, {
63-
glyph: status === statusMap.PLAY ? "pause" : "play",
64-
onClick: () => status === statusMap.PLAY ? pause() : selectedLayer && play(),
65-
tooltip: 'Play'
66-
}, {
67-
glyph: "stop",
68-
onClick: stop,
69-
tooltip: 'Stop'
70-
}, {
71-
glyph: "step-forward",
72-
tooltip: 'Step forward'
73-
}, {
74-
glyph: "wrench",
75-
bsStyle: showSettings ? 'success' : 'primary',
76-
active: !!showSettings,
77-
onClick: () => onShowSettings(!showSettings),
78-
tooltip: 'Playback settings'
79-
}
80-
]}/>
81-
</div>
48+
}) =>
49+
( <div style={{display: 'flex'}}>
50+
{showSettings &&
51+
<PlaybackSettings
52+
{...settings}
53+
onSettingChange={onSettingChange}
54+
setPlaybackRange={setPlaybackRange}/>}
55+
<Toolbar
56+
btnDefaultProps={{
57+
className: 'square-button-md',
58+
bsStyle: 'primary'
59+
}}
60+
buttons={[
61+
{
62+
glyph: "step-backward",
63+
tooltip: 'Step backward'
64+
}, {
65+
glyph: status === statusMap.PLAY ? "pause" : "play",
66+
onClick: () => status === statusMap.PLAY ? pause() : selectedLayer && play(),
67+
tooltip: 'Play'
68+
}, {
69+
glyph: "stop",
70+
onClick: stop,
71+
tooltip: 'Stop'
72+
}, {
73+
glyph: "step-forward",
74+
tooltip: 'Step forward'
75+
}, {
76+
glyph: "wrench",
77+
bsStyle: showSettings ? 'success' : 'primary',
78+
active: !!showSettings,
79+
onClick: () => onShowSettings(!showSettings),
80+
tooltip: 'Playback settings'
81+
}
82+
]}/>
83+
</div>
8284
)
8385
);

web/client/components/playback/PlaybackSettings.jsx

Lines changed: 88 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,39 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88
const React = require('react');
9-
const moment = require('moment');
109

11-
const {Form, FormGroup, ControlLabel, FormControl, InputGroup} = require('react-bootstrap');
10+
const moment = require('moment');
11+
const tooltip = require('../misc/enhancers/tooltip');
12+
const { isNaN } = require('lodash');
13+
const { Form, FormGroup, ControlLabel, FormControl, InputGroup, Glyphicon, Button: BButton } = require('react-bootstrap');
1214
const Message = require('../I18N/Message');
13-
15+
const InfoPopover = require('../widgets/widget/InfoPopover');
1416

1517
const InlineDateTimeSelector = require('../time/InlineDateTimeSelector');
1618
const SwitchButton = require('../misc/switch/SwitchButton');
19+
const Button = tooltip(BButton);
20+
21+
/**
22+
*
23+
* @param {number} v the number to evaluate
24+
* @param {function} fun the function to execute with number as parameter, if the string is va valid number
25+
* @param {function} validate the function to execute with number as parameter, if the string is va valid number
26+
* @param {function} errFun the function to execute in case of error or not valid number
27+
*/
28+
const onValidInteger = (v, fun, errFun = () => { }) => {
29+
try {
30+
if (!isNaN(parseInt(v, 10))) {
31+
const value = parseInt(v, 10);
32+
if (value < 1) {
33+
return fun(1);
34+
}
35+
return fun(value);
36+
}
37+
return errFun();
38+
} catch (e) {
39+
return errFun(e);
40+
}
41+
};
1742
const getPlaybackRange = ({ startPlaybackTime, endPlaybackTime }) => {
1843
const diff = moment(startPlaybackTime).diff(endPlaybackTime);
1944
return {
@@ -26,67 +51,93 @@ const getPlaybackRange = ({ startPlaybackTime, endPlaybackTime }) => {
2651
* Form div for settings of the playback
2752
*/
2853
module.exports = ({
54+
following,
2955
frameDuration,
3056
timeStep,
31-
setPlaybackRange = () => { },
57+
stepUnit,
58+
onSettingChange = () => { },
59+
/*
60+
* Boolean step for animation
61+
*/
62+
fixedStep = false,
63+
3264
playbackRange = {
3365
startPlaybackTime: new Date().toISOString(),
3466
endPlaybackTime: new Date().toISOString()
3567
},
68+
setPlaybackRange = () => { },
69+
3670
dateSelectorStyle = {
3771
padding: 0,
3872
margin: 0,
3973
border: 'none'
4074
}
4175

4276
}) => (<div className="ms-playback-settings">
43-
<h4><Message msgId="playback.settings.title" /></h4>
44-
<FormGroup controlId="formPlaybackTime">
77+
<h4><Message msgId="playback.settings.title" /></h4>
78+
<FormGroup controlId="frameDuration" >
4579
<ControlLabel><Message msgId="playback.settings.frameDuration" /></ControlLabel>
46-
<InputGroup>
47-
<FormControl componentClass="input" type="number" value={frameDuration} /><InputGroup.Addon>s</InputGroup.Addon>
80+
<InputGroup>
81+
<FormControl
82+
componentClass="input"
83+
type="number"
84+
value={frameDuration}
85+
86+
onChange={({ target = {} } = {}) => onValidInteger(
87+
target.value,
88+
v => {
89+
onSettingChange("frameDuration", v);
90+
}
91+
)} /><InputGroup.Addon>s</InputGroup.Addon>
4892
</InputGroup>
4993

5094
</FormGroup>
51-
<ControlLabel><Message msgId="playback.settings.step.label" /></ControlLabel>
95+
<ControlLabel><Message msgId="playback.settings.step.label" /></ControlLabel>
96+
<FormGroup controlId="formPlaybackStep">
5297
<Form componentClass="fieldset" inline>
53-
<FormGroup controlId="formPlaybackStep" >
54-
<ControlLabel><Message msgId="Fixed" /></ControlLabel>
55-
<span><SwitchButton /></span>
56-
<FormControl componentClass="input" type="number" value={timeStep} />
57-
<FormControl componentClass="select" placeholder="Select mode">
58-
<option value="years"><Message msgId="playback.settings.step.year" msgParams={{ number: timeStep || 1 }}/></option>
59-
<option value="weeks"><Message msgId="playback.settings.step.week" msgParams={{ number: timeStep || 1 }}/></option>
60-
<option value="days"><Message msgId="playback.settings.step.day" msgParams={{ number: timeStep || 1 }}/></option>
61-
<option value="hour"><Message msgId="playback.settings.step.hour" msgParams={{ number: timeStep || 1 }}/></option>
62-
<option value="minutes"><Message msgId="playback.settings.step.minute" msgParams={{ number: timeStep || 1 }}/></option>
63-
<option value="seconds"><Message msgId="playback.settings.step.second" msgParams={{number: timeStep || 1}}/></option>
64-
</FormControl>
65-
</FormGroup>
66-
</Form>
67-
<FormGroup controlId="formPlaybackMode">
68-
<ControlLabel>Mode</ControlLabel>
69-
<FormControl componentClass="select" placeholder="Select mode">
70-
<option value="normal">Normal</option>
71-
<option value="cumulative">Cumulative</option>
72-
<option value="ranged">Ranged</option>
98+
<ControlLabel><Message msgId="playback.settings.step.fixed" /></ControlLabel>
99+
<span><SwitchButton checked={fixedStep} onChange={v => onSettingChange("fixedStep", v)} /></span>
100+
<FormControl
101+
disabled={!fixedStep}
102+
componentClass="input"
103+
type="number"
104+
style={{ maxWidth: 120 }}
105+
value={timeStep}
106+
onChange={({ target = {} } = {}) => onValidInteger(
107+
target.value,
108+
v => {
109+
onSettingChange("timeStep", v);
110+
}
111+
)} />
112+
<FormControl disabled={!fixedStep} componentClass="select" value={stepUnit} onChange={({ target = {} }) => onSettingChange("stepUnit", target.value)} >
113+
<option value="years"><Message msgId="playback.settings.step.year" msgParams={{ number: timeStep || 1 }} /></option>
114+
<option value="weeks"><Message msgId="playback.settings.step.week" msgParams={{ number: timeStep || 1 }} /></option>
115+
<option value="days"><Message msgId="playback.settings.step.day" msgParams={{ number: timeStep || 1 }} /></option>
116+
<option value="hour"><Message msgId="playback.settings.step.hour" msgParams={{ number: timeStep || 1 }} /></option>
117+
<option value="minutes"><Message msgId="playback.settings.step.minute" msgParams={{ number: timeStep || 1 }} /></option>
118+
<option value="seconds"><Message msgId="playback.settings.step.second" msgParams={{ number: timeStep || 1 }} /></option>
73119
</FormControl>
74-
</FormGroup>
75-
76-
120+
</Form>
121+
</FormGroup>
77122
<FormGroup controlId="formPlaybackMode">
78-
<ControlLabel>Range</ControlLabel>
123+
<ControlLabel><Message msgId="playback.settings.range.title" /><Button className="no-border" bsSize="xs" tooltipId="playback.settings.range.zoomTooltip" ><Glyphicon glyph="search" /></Button></ControlLabel>
79124
<InlineDateTimeSelector
80125
tooltipId="playback.settings.range.animationStart"
81126
glyph="play"
82127
date={playbackRange.startPlaybackTime}
83-
onUpdate={startPlaybackTime => setPlaybackRange(getPlaybackRange({...playbackRange, startPlaybackTime}))}
84-
style={dateSelectorStyle}/>
128+
onUpdate={startPlaybackTime => setPlaybackRange(getPlaybackRange({ ...playbackRange, startPlaybackTime }))}
129+
style={dateSelectorStyle} />
85130
<InlineDateTimeSelector
86131
glyph="stop"
87132
tooltipId="playback.settings.range.animationEnd"
88133
date={playbackRange.endPlaybackTime}
89-
onUpdate={endPlaybackTime => setPlaybackRange(getPlaybackRange({...playbackRange, endPlaybackTime}))}
90-
style={dateSelectorStyle}/>
134+
onUpdate={endPlaybackTime => setPlaybackRange(getPlaybackRange({ ...playbackRange, endPlaybackTime }))}
135+
style={dateSelectorStyle} />
136+
</FormGroup>
137+
<FormGroup controlId="formPlaybackFollowingMode">
138+
<Form componentClass="fieldset" inline>
139+
<ControlLabel><Message msgId="playback.settings.mode.following" /><InfoPopover text={<Message msgId="playback.settings.mode.followingDescription" />} /></ControlLabel>
140+
<span><SwitchButton checked={following} onChange={v => onSettingChange("following", v)}/></span>
141+
</Form>
91142
</FormGroup>
92143
</div>);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
const React = require('react');
2+
const ReactDOM = require('react-dom');
3+
const ReactTestUtils = require('react-dom/test-utils');
4+
const expect = require('expect');
5+
const PlaybackSettings = require('../PlaybackSettings');
6+
describe('PlaybackSettings component', () => {
7+
beforeEach((done) => {
8+
document.body.innerHTML = '<div id="container"></div>';
9+
setTimeout(done);
10+
});
11+
afterEach((done) => {
12+
ReactDOM.unmountComponentAtNode(document.getElementById("container"));
13+
document.body.innerHTML = '';
14+
setTimeout(done);
15+
});
16+
it('PlaybackSettings rendering with defaults', () => {
17+
ReactDOM.render(<PlaybackSettings />, document.getElementById("container"));
18+
const container = document.getElementById('container');
19+
const el = container.querySelector('.ms-playback-settings');
20+
expect(el).toExist();
21+
});
22+
it('PlaybackSettings rendering with values', () => {
23+
ReactDOM.render(<PlaybackSettings following stepUnit="days" timeStep={1} frameDuration={1} />, document.getElementById("container"));
24+
const container = document.getElementById('container');
25+
const el = container.querySelector('.ms-playback-settings');
26+
expect(el).toExist();
27+
expect(document.querySelector('#frameDuration').value).toBe('1');
28+
expect(document.querySelector('input#formPlaybackStep').value).toBe('1');
29+
expect(document.querySelector('select#formPlaybackStep').value).toBe('days');
30+
});
31+
32+
it('Test PlaybackSettings onChangeSetting', () => {
33+
const actions = {
34+
onSettingChange: () => {}
35+
};
36+
const spyonChangeSetting = expect.spyOn(actions, 'onSettingChange');
37+
ReactDOM.render(<PlaybackSettings onSettingChange={actions.onSettingChange} />, document.getElementById("container"));
38+
const element = document.querySelector('#frameDuration');
39+
ReactTestUtils.Simulate.change(element, { target: { value: "2" } });
40+
expect(spyonChangeSetting).toHaveBeenCalled();
41+
expect(spyonChangeSetting.calls[0].arguments[0]).toBe("frameDuration");
42+
expect(spyonChangeSetting.calls[0].arguments[1]).toBe(2);
43+
});
44+
it('Test PlaybackSettings onChangeSetting default values to 1', () => {
45+
const actions = {
46+
onSettingChange: () => { }
47+
};
48+
const spyonChangeSetting = expect.spyOn(actions, 'onSettingChange');
49+
ReactDOM.render(<PlaybackSettings onSettingChange={actions.onSettingChange} />, document.getElementById("container"));
50+
const element = document.querySelector('#frameDuration');
51+
ReactTestUtils.Simulate.change(element, { target: { value: "-2" } });
52+
expect(spyonChangeSetting).toHaveBeenCalled();
53+
expect(spyonChangeSetting.calls[0].arguments[1]).toBe(1);
54+
});
55+
});

web/client/plugins/Playback.jsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ const React = require('react');
1010
const assign = require('object-assign');
1111
const { defaultProps, compose } = require('recompose');
1212
const {createSelector} = require('reselect');
13-
const { play, pause, stop, STATUS, selectPlaybackRange } = require('../actions/playback');
13+
const { play, pause, stop, STATUS, selectPlaybackRange, changeSetting } = require('../actions/playback');
1414
const {currentTimeSelector} = require('../selectors/dimension');
1515
const {selectedLayerSelector} = require('../selectors/timeline');
16-
const { statusSelector, loadingSelector, playbackRangeSelector } = require('../selectors/playback');
16+
const { statusSelector, loadingSelector, playbackRangeSelector, playbackSettingsSelector } = require('../selectors/playback');
1717

1818
const { connect } = require('react-redux');
1919

@@ -28,18 +28,21 @@ const Playback = compose(
2828
currentTimeSelector,
2929
loadingSelector,
3030
playbackRangeSelector,
31-
(selectedLayer, status, currentTime, loading, playbackRange) => ({
31+
playbackSettingsSelector,
32+
(selectedLayer, status, currentTime, loading, playbackRange, settings) => ({
3233
selectedLayer,
3334
loading,
3435
currentTime,
3536
status,
36-
playbackRange
37+
playbackRange,
38+
settings
3739
})
3840
), {
3941
play,
4042
pause,
4143
stop,
42-
setPlaybackRange: selectPlaybackRange
44+
setPlaybackRange: selectPlaybackRange,
45+
onSettingChange: changeSetting
4346
}
4447
)
4548
)(require('../components/playback/Playback'));

0 commit comments

Comments
 (0)