Skip to content

Commit 7f6aa05

Browse files
authored
Merge pull request #452 from sbates-idrc/remember-program-speed
Store the program speed in a new programSpeed state variable (fixes #451)
2 parents 0d0493b + 3cd3cdd commit 7f6aa05

3 files changed

Lines changed: 78 additions & 63 deletions

File tree

src/App.js

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ export type AppState = {
119119
sceneDimensions: SceneDimensions,
120120
drawingEnabled: boolean,
121121
runningState: RunningState,
122+
// programSpeed is a number in the range 1..(speedLookUp.length)
123+
programSpeed: number,
122124
disallowedActions: ActionToggleRegister,
123125
keyBindingsEnabled: boolean,
124126
keyboardInputSchemeName: KeyboardInputSchemeName,
@@ -154,7 +156,6 @@ export class App extends React.Component<AppProps, AppState> {
154156
customBackgroundSerializer: CustomBackgroundSerializer;
155157
speedLookUp: Array<number>;
156158
pushStateTimeoutID: ?TimeoutID;
157-
speedControlRef: { current: null | HTMLElement };
158159
programBlockEditorRef: { current: any };
159160
sequenceInProgress: Array<KeyboardEvent>;
160161
announcementBuilder: AnnouncementBuilder;
@@ -216,6 +217,8 @@ export class App extends React.Component<AppProps, AppState> {
216217
sceneDimensions: this.sceneDimensions,
217218
drawingEnabled: true,
218219
runningState: 'stopped',
220+
// Default to the middle speed
221+
programSpeed: Math.ceil(this.speedLookUp.length / 2),
219222
disallowedActions: {},
220223
keyBindingsEnabled: false,
221224
showKeyboardModal: false,
@@ -262,12 +265,16 @@ export class App extends React.Component<AppProps, AppState> {
262265
this.programChangeController = new ProgramChangeController(this,
263266
this.props.intl, this.audioManager);
264267

265-
this.speedControlRef = React.createRef();
266268
this.programBlockEditorRef = React.createRef();
267269

268270
const actionsHandler = new ActionsHandler(this, this.audioManager,
269271
this.sceneDimensions, this.props.intl);
270-
this.interpreter = new Interpreter(1000, this, actionsHandler);
272+
273+
this.interpreter = new Interpreter(
274+
this.speedLookUp[this.state.programSpeed - 1],
275+
this,
276+
actionsHandler
277+
);
271278
}
272279

273280
setStateSettings(settings: $Shape<AppSettings>) {
@@ -660,10 +667,24 @@ export class App extends React.Component<AppProps, AppState> {
660667
}
661668
break;
662669
case("decreaseProgramSpeed"):
663-
this.changeProgramSpeedIndex(this.speedLookUp.indexOf(this.interpreter.stepTimeMs) - 1);
670+
this.setState((state) => {
671+
return {
672+
programSpeed: Math.max(
673+
1,
674+
state.programSpeed - 1
675+
)
676+
};
677+
});
664678
break;
665679
case("increaseProgramSpeed"):
666-
this.changeProgramSpeedIndex(this.speedLookUp.indexOf(this.interpreter.stepTimeMs) + 1);
680+
this.setState((state) => {
681+
return {
682+
programSpeed: Math.min(
683+
this.speedLookUp.length,
684+
state.programSpeed + 1
685+
)
686+
};
687+
});
667688
break;
668689
case("selectForward1"):
669690
this.setState({ "selectedAction": "forward1" });
@@ -964,20 +985,14 @@ export class App extends React.Component<AppProps, AppState> {
964985
});
965986
}
966987

967-
changeProgramSpeedIndex = (newSpeedIndex: number) => {
968-
if (newSpeedIndex >= 0 && newSpeedIndex <= (this.speedLookUp.length - 1)) {
969-
this.interpreter.setStepTime(this.speedLookUp[newSpeedIndex]);
970-
if (this.speedControlRef.current) {
971-
// $FlowFixMe: Flow doesn't believe that we have sufficiently ensured that current !== null.
972-
this.speedControlRef.current.value = (newSpeedIndex + 1).toString();
973-
}
988+
handleChangeProgramSpeed = (value: number) => {
989+
if (value >= 1 && value <= this.speedLookUp.length) {
990+
this.setState({
991+
programSpeed: value
992+
});
974993
}
975994
}
976995

977-
handleChangeProgramSpeed = (stepTimeMs: number) => {
978-
this.interpreter.setStepTime(stepTimeMs);
979-
}
980-
981996
renderCommandBlocks = (commands: Array<DisplayedCommandName>) => {
982997
const commandBlocks = [];
983998

@@ -1570,8 +1585,8 @@ export class App extends React.Component<AppProps, AppState> {
15701585
onClick={this.handleStop}
15711586
/>
15721587
<ProgramSpeedController
1573-
rangeControlRef={this.speedControlRef}
1574-
values={this.speedLookUp}
1588+
numValues={this.speedLookUp.length}
1589+
value={this.state.programSpeed}
15751590
onChange={this.handleChangeProgramSpeed}
15761591
/>
15771592
</div>
@@ -1993,6 +2008,16 @@ export class App extends React.Component<AppProps, AppState> {
19932008
document.body.className = `${this.state.settings.theme}-theme`;
19942009
}
19952010

2011+
if (this.state.programSpeed !== prevState.programSpeed) {
2012+
if (this.speedLookUp.length > 0
2013+
&& this.state.programSpeed >= 1
2014+
&& this.state.programSpeed <= this.speedLookUp.length) {
2015+
this.interpreter.setStepTime(
2016+
this.speedLookUp[this.state.programSpeed - 1]
2017+
);
2018+
}
2019+
}
2020+
19962021
/* Dash connection removed for version 0.5
19972022
if (this.state.dashConnectionStatus !== prevState.dashConnectionStatus) {
19982023
console.log(this.state.dashConnectionStatus);

src/ProgramSpeedController.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@ import './ProgramSpeedController.scss';
1010

1111
type ProgramSpeedControllerProps = {
1212
intl: IntlShape,
13-
values: Array<number>,
14-
rangeControlRef: { current: null | HTMLElement };
13+
numValues: number,
14+
value: number,
1515
onChange: (value: number) => void
1616
};
1717

1818
class ProgramSpeedController extends React.Component<ProgramSpeedControllerProps, {}> {
19-
onChangeInput = (e: SyntheticInputEvent<HTMLInputElement>) => {
20-
this.props.onChange(this.props.values[parseInt(e.target.value) - 1]);
19+
handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
20+
this.props.onChange(parseInt(e.target.value));
2121
}
22+
2223
render() {
2324
return (
2425
<div className='ProgramSpeedController__container'>
@@ -31,10 +32,11 @@ class ProgramSpeedController extends React.Component<ProgramSpeedControllerProps
3132
aria-label={`${this.props.intl.formatMessage({id:'ProgramSpeedController.slider'})}`}
3233
className='ProgramSpeedController__slider'
3334
type='range'
34-
min='1'
35-
max={this.props.values.length}
36-
ref={this.props.rangeControlRef}
37-
onChange={this.onChangeInput} />
35+
min={1}
36+
max={this.props.numValues}
37+
value={this.props.value}
38+
onChange={this.handleChange}
39+
/>
3840
}
3941
<FastIcon aria-hidden={true} />
4042
</div>

src/ProgramSpeedController.test.js

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,19 @@ import { IntlProvider } from 'react-intl';
77
import ProgramSpeedController from './ProgramSpeedController';
88
import messages from './messages.json';
99

10-
configure({ adapter: new Adapter()});
10+
configure({ adapter: new Adapter() });
1111

12-
const defaultSpeedControllerProps = {
13-
rangeControlRef: React.createRef()
14-
};
12+
function createMountProgramSpeedController(numValues: number, value: number) {
13+
const changeHandler = jest.fn();
1514

16-
function createMountProgramSpeedController(props) {
1715
const wrapper = mount(
1816
React.createElement(
1917
ProgramSpeedController,
20-
Object.assign(
21-
{},
22-
defaultSpeedControllerProps,
23-
props
24-
)
18+
{
19+
numValues: numValues,
20+
value: value,
21+
onChange: changeHandler
22+
}
2523
),
2624
{
2725
wrappingComponent: IntlProvider,
@@ -33,39 +31,29 @@ function createMountProgramSpeedController(props) {
3331
}
3432
);
3533

36-
return {
37-
wrapper
38-
};
34+
return { wrapper, changeHandler };
3935
}
4036

4137
function getProgramSpeedController(programSpeedControllerWrapper) {
4238
return programSpeedControllerWrapper.find('.ProgramSpeedController__slider').at(0);
4339
}
4440

45-
describe('When value from the controller changes', () => {
46-
test('Should call onChange callback with value from speedLookUp at index value-1', () => {
47-
expect.assertions(2);
48-
const mockOnChange = jest.fn();
49-
const values = [2000, 1500, 1000, 500, 250];
50-
const { wrapper } = createMountProgramSpeedController({
51-
onChange: mockOnChange,
52-
values
53-
});
54-
const programSpeedController = getProgramSpeedController(wrapper);
55-
const targetValue = 4;
56-
programSpeedController.simulate('change', { target: { value: targetValue}});
57-
expect(mockOnChange.mock.calls.length).toBe(1);
58-
expect(mockOnChange.mock.calls[0][0]).toBe(values[targetValue-1]);
59-
})
41+
test('Renders with the specified numValues and value', () => {
42+
expect.assertions(3);
43+
const { wrapper } = createMountProgramSpeedController(5, 3);
44+
const programSpeedController = getProgramSpeedController(wrapper);
45+
expect(programSpeedController.get(0).props.min).toBe(1);
46+
expect(programSpeedController.get(0).props.max).toBe(5);
47+
expect(programSpeedController.get(0).props.value).toBe(3);
6048
});
6149

62-
test('Maximum value of the controller should be equal to the length of values property', () => {
63-
expect.assertions(1);
64-
const values = [2000, 1500, 1000, 500, 250];
65-
const { wrapper } = createMountProgramSpeedController({
66-
onChange: () => {},
67-
values
68-
});
50+
test('When the controller changes, should call the onChange callback with the new value', () => {
51+
expect.assertions(2);
52+
const { wrapper, changeHandler } = createMountProgramSpeedController(5, 3);
53+
6954
const programSpeedController = getProgramSpeedController(wrapper);
70-
expect(programSpeedController.get(0).props.max).toBe(values.length);
71-
})
55+
programSpeedController.simulate('change', { target: { value: 4 } });
56+
57+
expect(changeHandler.mock.calls.length).toBe(1);
58+
expect(changeHandler.mock.calls[0][0]).toBe(4);
59+
});

0 commit comments

Comments
 (0)