Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 2 additions & 11 deletions src/containers/gui.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import {
getIsShowingProject
} from '../reducers/project-state';
import {setProjectTitle} from '../reducers/project-title';
import {detectTutorialId} from '../lib/tutorial-from-url';
import {activateDeck} from '../reducers/cards';
import {
activateTab,
BLOCKS_TAB_INDEX,
Expand All @@ -31,6 +29,7 @@ import FontLoaderHOC from '../lib/font-loader-hoc.jsx';
import LocalizationHOC from '../lib/localization-hoc.jsx';
import ProjectFetcherHOC from '../lib/project-fetcher-hoc.jsx';
import ProjectSaverHOC from '../lib/project-saver-hoc.jsx';
import QueryParserHOC from '../lib/query-parser-hoc.jsx';
import storage from '../lib/storage';
import vmListenerHOC from '../lib/vm-listener-hoc.jsx';
import vmManagerHOC from '../lib/vm-manager-hoc.jsx';
Expand All @@ -50,7 +49,6 @@ class GUI extends React.Component {
componentDidMount () {
this.setReduxTitle(this.props.projectTitle);
this.props.onStorageInit(storage);
this.setActiveCards(detectTutorialId());
}
componentDidUpdate (prevProps) {
if (this.props.projectId !== prevProps.projectId && this.props.projectId !== null) {
Expand All @@ -69,11 +67,6 @@ class GUI extends React.Component {
this.props.onUpdateReduxProjectTitle(newTitle);
}
}
setActiveCards (tutorialId) {
if (tutorialId && tutorialId !== 'all') {
this.props.onUpdateReduxDeck(tutorialId);
}
}
render () {
if (this.props.isError) {
throw new Error(
Expand All @@ -88,7 +81,6 @@ class GUI extends React.Component {
isShowingProject,
onStorageInit,
onUpdateProjectId,
onUpdateReduxDeck,
onUpdateReduxProjectTitle,
projectHost,
projectId,
Expand Down Expand Up @@ -127,7 +119,6 @@ GUI.propTypes = {
onStorageInit: PropTypes.func,
onUpdateProjectId: PropTypes.func,
onUpdateProjectTitle: PropTypes.func,
onUpdateReduxDeck: PropTypes.func,
onUpdateReduxProjectTitle: PropTypes.func,
previewInfoVisible: PropTypes.bool,
projectHost: PropTypes.string,
Expand Down Expand Up @@ -178,7 +169,6 @@ const mapDispatchToProps = dispatch => ({
onActivateSoundsTab: () => dispatch(activateTab(SOUNDS_TAB_INDEX)),
onRequestCloseBackdropLibrary: () => dispatch(closeBackdropLibrary()),
onRequestCloseCostumeLibrary: () => dispatch(closeCostumeLibrary()),
onUpdateReduxDeck: tutorialId => dispatch(activateDeck(tutorialId)),
onUpdateReduxProjectTitle: title => dispatch(setProjectTitle(title))
});

Expand All @@ -194,6 +184,7 @@ const WrappedGui = compose(
LocalizationHOC,
ErrorBoundaryHOC('Top Level App'),
FontLoaderHOC,
QueryParserHOC,
ProjectFetcherHOC,
ProjectSaverHOC,
vmListenerHOC,
Expand Down
17 changes: 2 additions & 15 deletions src/lib/app-state-hoc.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ 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;

Expand Down Expand Up @@ -52,8 +51,7 @@ const AppStateHOC = function (WrappedComponent, localesOnly) {
guiMiddleware,
initFullScreen,
initPlayer,
initPreviewInfo,
initTutorialLibrary
initPreviewInfo
} = guiRedux;
const {ScratchPaintReducer} = require('scratch-paint');

Expand All @@ -66,18 +64,7 @@ const AppStateHOC = function (WrappedComponent, localesOnly) {
initializedGui = initPlayer(initializedGui);
}
} else {
const tutorialId = detectTutorialId();
if (tutorialId === null) {
if (props.showPreviewInfo) {
// Show preview info if requested and no tutorial ID found
initializedGui = initPreviewInfo(initializedGui);
}
} else if (tutorialId === 'all') {
// Specific tutorials are set in setActiveCards in the GUI container.
// Handle ?tutorial=all here for beta, if we decide to keep this for the
// project page, this functionality should move to GUI container also.
initializedGui = initTutorialLibrary(initializedGui);
}
initializedGui = initPreviewInfo(initializedGui);
}
reducers = {
locales: localesReducer,
Expand Down
70 changes: 70 additions & 0 deletions src/lib/query-parser-hoc.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react';
import PropTypes from 'prop-types';
import queryString from 'query-string';
import {connect} from 'react-redux';

import {detectTutorialId} from './tutorial-from-url';

import {activateDeck} from '../reducers/cards';
import {openTipsLibrary, closePreviewInfo} from '../reducers/modals';

/* Higher Order Component to get parameters from the URL query string and initialize redux state
* @param {React.Component} WrappedComponent: component to render
* @returns {React.Component} component with query parsing behavior
*/
const QueryParserHOC = function (WrappedComponent) {
class QueryParserComponent extends React.Component {
constructor (props) {
super(props);
const queryParams = queryString.parse(location.search);
const tutorialId = detectTutorialId(queryParams);
if (tutorialId) {
if (tutorialId === 'all') {
this.openTutorials();
} else {
this.setActiveCards(tutorialId);
}
}
}
setActiveCards (tutorialId) {
this.props.onUpdateReduxDeck(tutorialId);
}
openTutorials () {
this.props.onOpenTipsLibrary();
}
render () {
const {
onOpenTipsLibrary, // eslint-disable-line no-unused-vars
onUpdateReduxDeck, // eslint-disable-line no-unused-vars
...componentProps
} = this.props;
return (
<WrappedComponent
{...componentProps}
/>
);
}
}
QueryParserComponent.propTypes = {
onOpenTipsLibrary: PropTypes.func,
onUpdateReduxDeck: PropTypes.func
};
const mapDispatchToProps = dispatch => ({
onOpenTipsLibrary: () => {
dispatch(openTipsLibrary());
dispatch(closePreviewInfo());
},
onUpdateReduxDeck: tutorialId => {
dispatch(activateDeck(tutorialId));
dispatch(closePreviewInfo());
}
});
return connect(
null,
mapDispatchToProps
)(QueryParserComponent);
};

export {
QueryParserHOC as default
};
5 changes: 2 additions & 3 deletions src/lib/tutorial-from-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import tutorials from './libraries/decks/index.jsx';
import analytics from './analytics';
import queryString from 'query-string';

/**
* Get the tutorial id from the given numerical id (representing the
Expand All @@ -31,11 +30,11 @@ const getDeckIdFromUrlId = urlId => {
/**
* 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.
* @param {object} queryParams the results of parsing the query string
* @return {string} The ID of the requested tutorial or null if no tutorial was
* requested or found.
*/
const detectTutorialId = () => {
const queryParams = queryString.parse(location.search);
const detectTutorialId = queryParams => {
const tutorialID = Array.isArray(queryParams.tutorial) ?
queryParams.tutorial[0] :
queryParams.tutorial;
Expand Down
15 changes: 1 addition & 14 deletions src/reducers/gui.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,6 @@ const initTutorialCard = function (currentState, deckId) {
);
};

const initTutorialLibrary = function (currentState) {
return Object.assign(
{},
currentState,
{
modals: {
tipsLibrary: true
}
}
);
};

const initPreviewInfo = function (currentState) {
return Object.assign(
{},
Expand Down Expand Up @@ -150,6 +138,5 @@ export {
initFullScreen,
initPlayer,
initPreviewInfo,
initTutorialCard,
initTutorialLibrary
initTutorialCard
};
35 changes: 15 additions & 20 deletions test/unit/util/tutorial-from-url.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,40 @@ jest.mock('../../../src/lib/libraries/decks/index.jsx', () => ({
noUrlIdSandwich: {}
}));

import queryString from 'query-string';
import {detectTutorialId} from '../../../src/lib/tutorial-from-url.js';

Object.defineProperty(
window.location,
'search',
{value: '', writable: true}
);

test('returns the tutorial ID if the urlId matches', () => {
window.location.search = '?tutorial=one';
expect(detectTutorialId()).toBe('foo');
const queryParams = queryString.parse('?tutorial=one');
expect(detectTutorialId(queryParams)).toBe('foo');
});

test('returns null if no matching urlId', () => {
window.location.search = '?tutorial=10';
expect(detectTutorialId()).toBe(null);
const queryParams = queryString.parse('?tutorial=10');
expect(detectTutorialId(queryParams)).toBe(null);
});

test('returns null if empty template', () => {
window.location.search = '?tutorial=';
expect(detectTutorialId()).toBe(null);
const queryParams = queryString.parse('?tutorial=');
expect(detectTutorialId(queryParams)).toBe(null);
});

test('returns null if no query param', () => {
window.location.search = '';
expect(detectTutorialId()).toBe(null);
const queryParams = queryString.parse('');
expect(detectTutorialId(queryParams)).toBe(null);
});

test('returns null if unrecognized template', () => {
window.location.search = '?tutorial=asdf';
expect(detectTutorialId()).toBe(null);
const queryParams = queryString.parse('?tutorial=asdf');
expect(detectTutorialId(queryParams)).toBe(null);
});

test('takes the first of multiple', () => {
window.location.search = '?tutorial=one&tutorial=two';
expect(detectTutorialId()).toBe('foo');
const queryParams = queryString.parse('?tutorial=one&tutorial=two');
expect(detectTutorialId(queryParams)).toBe('foo');
});

test('returns all for the tutorial library shortcut', () => {
window.location.search = '?tutorial=all';
expect(detectTutorialId()).toBe('all');
const queryParams = queryString.parse('?tutorial=all');
expect(detectTutorialId(queryParams)).toBe('all');
});