diff --git a/src/lib/app-state-hoc.jsx b/src/lib/app-state-hoc.jsx index fe7ac023eab97c5df5aea8e5829a6769d28392b5..93d97180ef38526ac05feaf019338aa32ed4496b 100644 --- a/src/lib/app-state-hoc.jsx +++ b/src/lib/app-state-hoc.jsx @@ -10,6 +10,7 @@ import {setPlayer, setFullScreen} from '../reducers/mode.js'; import locales from 'scratch-l10n'; import {detectLocale} from './detect-locale'; +import {detectTutorialId} from './tutorial-from-url'; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; @@ -50,16 +51,27 @@ const AppStateHOC = function (WrappedComponent, localesOnly) { guiInitialState, guiMiddleware, initFullScreen, - initPlayer + initPlayer, + initTutorialCard } = guiRedux; const {ScratchPaintReducer} = require('scratch-paint'); let initializedGui = guiInitialState; - if (props.isFullScreen) { - initializedGui = initFullScreen(initializedGui); - } - if (props.isPlayerOnly) { - initializedGui = initPlayer(initializedGui); + if (props.isFullScreen || props.isPlayerOnly) { + if (props.isFullScreen) { + initializedGui = initFullScreen(initializedGui); + } + if (props.isPlayerOnly) { + initializedGui = initPlayer(initializedGui); + } + } else { + const tutorialId = detectTutorialId(); + if (tutorialId !== null) { + // When loading a tutorial from the URL, + // load w/o preview modal + // open requested tutorial card + initializedGui = initTutorialCard(initializedGui, tutorialId); + } } reducers = { locales: localesReducer, diff --git a/src/lib/libraries/decks/index.jsx b/src/lib/libraries/decks/index.jsx index 096ca15224a7882aa7fe243efad76bf5b8a736cc..f8324f3ae73068221fe0b86bc68b5eb0757a9db5 100644 --- a/src/lib/libraries/decks/index.jsx +++ b/src/lib/libraries/decks/index.jsx @@ -99,7 +99,8 @@ export default { 'add-sprite' ] } - ] + ], + urlId: 1 }, 'animate-a-name': { name: ( @@ -172,7 +173,8 @@ export default { 'glide-around' ] } - ] + ], + urlId: 2 }, 'Make-Music': { name: ( @@ -239,7 +241,8 @@ export default { 'add-sprite' ] } - ] + ], + urlId: 3 }, 'Make-A-Game': { name: ( @@ -323,7 +326,8 @@ export default { 'move-around-with-arrow-keys' ] } - ] + ], + urlId: 4 }, 'Chase-Game': { @@ -425,7 +429,8 @@ export default { 'move-around-with-arrow-keys' ] } - ] + ], + urlId: 5 }, 'add-sprite': { name: ( @@ -453,7 +458,8 @@ export default { 'switch-costume' ] } - ] + ], + urlId: 6 }, 'add-a-backdrop': { name: ( @@ -471,7 +477,8 @@ export default { 'change-size', 'switch-costume' ] - }] + }], + urlId: 7 }, 'change-size': { name: ( @@ -489,7 +496,8 @@ export default { 'glide-around', 'spin-video' ] - }] + }], + urlId: 8 }, 'glide-around': { name: ( @@ -507,7 +515,8 @@ export default { 'add-a-backdrop', 'switch-costume' ] - }] + }], + urlId: 9 }, 'record-a-sound': { @@ -526,8 +535,8 @@ export default { 'Make-Music', 'switch-costume' ] - }] - + }], + urlId: 10 }, 'spin-video': { name: ( @@ -545,7 +554,8 @@ export default { 'add-a-backdrop', 'switch-costume' ] - }] + }], + urlId: 11 }, 'hide-and-show': { name: ( @@ -563,7 +573,8 @@ export default { 'add-a-backdrop', 'switch-costume' ] - }] + }], + urlId: 12 }, 'switch-costume': { @@ -582,7 +593,8 @@ export default { 'add-a-backdrop', 'add-effects' ] - }] + }], + urlId: 13 }, 'move-around-with-arrow-keys': { @@ -601,7 +613,8 @@ export default { 'add-a-backdrop', 'switch-costume' ] - }] + }], + urlId: 14 }, 'add-effects': { name: ( @@ -619,6 +632,7 @@ export default { 'add-a-backdrop', 'switch-costume' ] - }] + }], + urlId: 15 } }; diff --git a/src/lib/tutorial-from-url.js b/src/lib/tutorial-from-url.js new file mode 100644 index 0000000000000000000000000000000000000000..c627b8db7abf1a709719c12add3b6eec64f13257 --- /dev/null +++ b/src/lib/tutorial-from-url.js @@ -0,0 +1,48 @@ +/** + * @fileoverview + * Utility function to detect tutorial id from query paramenter on the URL. + */ + +import tutorials from './libraries/decks/index.jsx'; +import analytics from './analytics'; + +/** + * Get the tutorial id from the given numerical id (representing the + * url id of the tutorial). + * @param {number} urlId The URL Id for the tutorial + * @returns {string} The string id for the tutorial, or null if the URL ID + * was not found. + */ +const getDeckIdFromUrlId = urlId => { + for (const deckId in tutorials) { + if (tutorials[deckId].urlId === urlId) { + analytics.event({ + category: 'how-to', + action: 'load from url', + label: `${deckId}` + }); + return deckId; + } + } + return null; +}; + +/** + * Check if there's a tutorial id provided as a query parameter in the URL. + * Return the corresponding tutorial id or null if not found. + * @return {string} The ID of the requested tutorial or null if no tutorial was + * requested or found. + */ +const detectTutorialId = () => { + if (window.location.search.indexOf('tutorial=') !== -1) { + const urlTutorialId = window.location.search.match(/(?:tutorial)=(\d+)/)[1]; + if (urlTutorialId) { + return getDeckIdFromUrlId(Number(urlTutorialId)); + } + } + return null; +}; + +export { + detectTutorialId +}; diff --git a/src/reducers/gui.js b/src/reducers/gui.js index cfad8f6825ddad3a07a56b4c62757ffbc3a97909..8869c1a35031e1d46b8dd28f4f408d2500946840 100644 --- a/src/reducers/gui.js +++ b/src/reducers/gui.js @@ -21,6 +21,8 @@ import vmReducer, {vmInitialState} from './vm'; import vmStatusReducer, {vmStatusInitialState} from './vm-status'; import throttle from 'redux-throttle'; +import decks from '../lib/libraries/decks/index.jsx'; + const guiMiddleware = compose(applyMiddleware(throttle(300, {leading: true, trailing: true}))); const guiInitialState = { @@ -67,6 +69,27 @@ const initFullScreen = function (currentState) { ); }; +const initTutorialCard = function (currentState, deckId) { + return Object.assign( + {}, + currentState, + { + modals: { + previewInfo: false + }, + cards: { + visible: true, + content: decks, + activeDeckId: deckId, + step: 0, + x: 0, + y: 0, + dragging: false + } + } + ); +}; + const guiReducer = combineReducers({ assetDrag: assetDragReducer, blockDrag: blockDragReducer, @@ -95,5 +118,6 @@ export { guiInitialState, guiMiddleware, initFullScreen, - initPlayer + initPlayer, + initTutorialCard };