@@ -13,7 +13,7 @@ import interpolateComponents from "interpolate-components";
13
13
import ArrowForwardIcon from "material-ui/svg-icons/navigation/arrow-forward" ;
14
14
import ArrowBackwardIcon from "material-ui/svg-icons/navigation/arrow-back" ;
15
15
import CloseIcon from "material-ui/svg-icons/navigation/close" ;
16
-
16
+ import isUndefined from "lodash/isUndefined" ;
17
17
18
18
/**
19
19
* The OnboardingWizard class.
@@ -36,8 +36,22 @@ class OnboardingWizard extends React.Component {
36
36
errorMessage : "" ,
37
37
} ;
38
38
39
+ this . postStep = this . postStep . bind ( this ) ;
39
40
this . setNextStep = this . setNextStep . bind ( this ) ;
40
41
this . setPreviousStep = this . setPreviousStep . bind ( this ) ;
42
+ this . listenToHashChange = this . listenToHashChange . bind ( this ) ;
43
+ window . addEventListener ( "hashchange" , this . listenToHashChange , false ) ;
44
+ }
45
+
46
+ /**
47
+ * Remove the prepended hashtag from the passed string.
48
+ *
49
+ * @param {string } stringWithHashtag The string to remove the prepended hashtag from.
50
+ *
51
+ * @returns {string } The string without prepended hashtag.
52
+ */
53
+ removePrependedHashtag ( stringWithHashtag ) {
54
+ return stringWithHashtag . substring ( 1 ) ;
41
55
}
42
56
43
57
/**
@@ -131,6 +145,13 @@ class OnboardingWizard extends React.Component {
131
145
* @returns {Object } The first step object
132
146
*/
133
147
getFirstStep ( steps ) {
148
+ // Use the hash from the url without the hashtag.
149
+ const firstStep = this . removePrependedHashtag ( window . location . hash ) ;
150
+
151
+ if ( firstStep !== "" ) {
152
+ return firstStep ;
153
+ }
154
+ // When window.location doesn't have a hash, use the first step of the wizard as default.
134
155
return Object . getOwnPropertyNames ( steps ) [ 0 ] ;
135
156
}
136
157
@@ -204,7 +225,16 @@ class OnboardingWizard extends React.Component {
204
225
* @returns {Object } The current step.
205
226
*/
206
227
getCurrentStep ( ) {
207
- return this . state . steps [ this . state . currentStepId ] ;
228
+ const steps = this . state . steps ;
229
+ const currentStep = steps [ this . state . currentStepId ] ;
230
+
231
+ // If the currentStep is undefined because the stepId is invalid, return the first step of the Wizard.
232
+ if ( isUndefined ( currentStep ) ) {
233
+ const firstStepId = Object . keys ( steps ) [ 0 ] ;
234
+ this . setState ( { currentStepId : firstStepId } ) ;
235
+ return steps [ firstStepId ] ;
236
+ }
237
+ return currentStep ;
208
238
}
209
239
210
240
/**
@@ -259,6 +289,59 @@ class OnboardingWizard extends React.Component {
259
289
return ( hideButton ) ? "" : < RaisedButton className = { className } { ...attributes } /> ;
260
290
}
261
291
292
+ /**
293
+ * Updates the currentStepId in the state when the hash in the URL changes.
294
+ *
295
+ * @returns {void }
296
+ */
297
+ listenToHashChange ( ) {
298
+ // Because the hash starts with a hashtag, we need to do remove the hastag before using it.
299
+ this . setState ( { currentStepId : this . removePrependedHashtag ( window . location . hash ) } ) ;
300
+ }
301
+
302
+ /**
303
+ * When the currentStepId in the state changes, return a snapshot with the new currentStepId.
304
+ *
305
+ * @param {Object } prevProps The previous props.
306
+ * @param {Object } prevState The previous state.
307
+ *
308
+ * @returns {string|null } The currentStepId from after the update.
309
+ */
310
+ getSnapshotBeforeUpdate ( prevProps , prevState ) {
311
+ const currentStepIdAfterUpdate = this . state . currentStepId ;
312
+ // If there is no change in the currentStepId in the state, do nothing.
313
+ if ( prevState . currentStepId === currentStepIdAfterUpdate ) {
314
+ return null ;
315
+ }
316
+
317
+ // If the new currentStepId is the same as the current location hash, do nothing.
318
+ if ( this . removePrependedHashtag ( window . location . hash ) === currentStepIdAfterUpdate ) {
319
+ return null ;
320
+ }
321
+
322
+ return currentStepIdAfterUpdate ;
323
+ }
324
+
325
+ /**
326
+ * Push the new hash to the history.
327
+ *
328
+ * Only do this when the new hash differs from the current hash. If we wouldn't check against
329
+ * the current hash, it would lead to double hashes when using the browser's previous and next
330
+ * buttons.
331
+ *
332
+ * @param {Object } prevProps The previous props.
333
+ * @param {Object } prevState The previous state.
334
+ * @param {string } snapshot The currentStepId from after the update.
335
+ *
336
+ * @returns {void }
337
+ */
338
+ componentDidUpdate ( prevProps , prevState , snapshot ) {
339
+ if ( snapshot !== null ) {
340
+ window . history . pushState ( null , null , "#" + snapshot ) ;
341
+ }
342
+ }
343
+
344
+
262
345
/**
263
346
* Renders the wizard.
264
347
*
@@ -302,7 +385,7 @@ class OnboardingWizard extends React.Component {
302
385
< Header headerTitle = { headerTitle } icon = { this . props . headerIcon } />
303
386
< StepIndicator
304
387
steps = { this . props . steps } stepIndex = { this . getCurrentStepNumber ( ) - 1 }
305
- onClick = { ( stepNumber , evt ) => this . postStep ( stepNumber , evt ) }
388
+ onClick = { this . postStep }
306
389
/>
307
390
< main className = "yoast-wizard-container" >
308
391
< div className = "yoast-wizard" >
0 commit comments