Skip to content

Commit 3cd3cdd

Browse files
committed
Store program speed in new programSpeed state variable
To ensure that the program speed is remembered when switching in and out of design mode. Fixes #451
1 parent 0d0493b commit 3cd3cdd

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)