diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index 9935b3a7e..025a73966 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -36,6 +36,7 @@ import vmManagerHOC from '../lib/vm-manager-hoc.jsx'; import cloudManagerHOC from '../lib/cloud-manager-hoc.jsx'; import GUIComponent from '../components/gui/gui.jsx'; +import {setIsScratchDesktop} from '../lib/isScratchDesktop.js'; const messages = defineMessages({ defaultProjectTitle: { @@ -47,6 +48,7 @@ const messages = defineMessages({ class GUI extends React.Component { componentDidMount () { + setIsScratchDesktop(this.props.isScratchDesktop); this.setReduxTitle(this.props.projectTitle); this.props.onStorageInit(storage); } @@ -78,6 +80,7 @@ class GUI extends React.Component { cloudHost, error, isError, + isScratchDesktop, isShowingProject, onStorageInit, onUpdateProjectId, @@ -113,6 +116,7 @@ GUI.propTypes = { intl: intlShape, isError: PropTypes.bool, isLoading: PropTypes.bool, + isScratchDesktop: PropTypes.bool, isShowingProject: PropTypes.bool, loadingStateVisible: PropTypes.bool, onSeeCommunity: PropTypes.func, @@ -128,6 +132,7 @@ GUI.propTypes = { }; GUI.defaultProps = { + isScratchDesktop: false, onStorageInit: storageInstance => storageInstance.addOfficialScratchWebStores(), onUpdateProjectId: () => {} }; diff --git a/src/containers/tips-library.jsx b/src/containers/tips-library.jsx index 5727e19d1..e5cb311aa 100644 --- a/src/containers/tips-library.jsx +++ b/src/containers/tips-library.jsx @@ -7,6 +7,8 @@ import decksLibraryContent from '../lib/libraries/decks/index.jsx'; import tutorialTags from '../lib/libraries/tutorial-tags'; import analytics from '../lib/analytics'; +import {notScratchDesktop} from '../lib/isScratchDesktop'; + import LibraryComponent from '../components/library/library.jsx'; import {connect} from 'react-redux'; @@ -56,16 +58,21 @@ class TipsLibrary extends React.PureComponent { }); } render () { - const decksLibraryThumbnailData = Object.keys(decksLibraryContent).map(id => ({ - rawURL: decksLibraryContent[id].img, - id: id, - name: decksLibraryContent[id].name, - featured: true, - tags: decksLibraryContent[id].tags, - urlId: decksLibraryContent[id].urlId, - requiredProjectId: decksLibraryContent[id].requiredProjectId, - hidden: decksLibraryContent[id].hidden || false - })); + const decksLibraryThumbnailData = Object.entries(decksLibraryContent) + .filter(([, item]) => + // Scratch Desktop doesn't want tutorials with `requiredProjectId` + notScratchDesktop() || !item.hasOwnProperty('requiredProjectId') + ) + .map(([id, item]) => ({ + rawURL: item.img, + id: id, + name: item.name, + featured: true, + tags: item.tags, + urlId: item.urlId, + requiredProjectId: item.requiredProjectId, + hidden: item.hidden || false + })); if (!this.props.visible) return null; return ( diff --git a/src/lib/isScratchDesktop.js b/src/lib/isScratchDesktop.js index 8d573e91e..3f58a8842 100644 --- a/src/lib/isScratchDesktop.js +++ b/src/lib/isScratchDesktop.js @@ -1,43 +1,37 @@ /** - * @returns {boolean} - true if is seems like the GUI is running under Scratch Desktop; false otherwise. - * This affects various things throughout the GUI. Ideally we should use some other way to check for this, but it - * should still be possible to override it externally for testing purposes. For example, maybe this should be a - * property on the GUI element or part of the global GUI state. Calls to this function should be taken as a - * placeholder for a future, better approach. + * Internal stored state. Not valid until after at least one call to `setIsScratchDesktop()`. + * @type {boolean} */ -const isScratchDesktop = function () { - if (isScratchDesktop.override !== null) { - return isScratchDesktop.override; - } - const userAgent = navigator.userAgent.toLowerCase(); - const isElectron = (userAgent.indexOf(' electron/') > -1); - return isElectron; +let _isScratchDesktop; // undefined = not ready yet + +/** + * Tell the `isScratchDesktop()` whether or not the GUI is running under Scratch Desktop. + * @param {boolean} value - the new value which `isScratchDesktop()` should return in the future. + */ +const setIsScratchDesktop = function (value) { + _isScratchDesktop = value; }; /** - * Set this value to non-null to force `isScratchDesktop` to return that value for testing & dev purposes. + * @returns {boolean} - true if it seems like the GUI is running under Scratch Desktop; false otherwise. */ -isScratchDesktop.override = null; +const isScratchDesktop = function () { + if (typeof _isScratchDesktop === 'undefined') { + throw new Error('isScratchDesktop is not ready yet'); + } + return _isScratchDesktop; +}; /** - * Parse an HREF string and extract an `isScratchDesktop` override value if present. - * The value is set in `isScratchDesktop.override`. - * Activate this by adding "?isScratchDesktop=1" or similar to the URL. - * @param {string} href - the `window.location.href` value (or equivalent) to parse + * @returns {boolean} - false if it seems like the GUI is running under Scratch Desktop; true otherwise. */ -const setIsScratchDesktopOverrideFromHref = function (href) { - const scratchDesktopMatches = href.match(/[?&]isScratchDesktop=([^&]+)/); - if (scratchDesktopMatches) { - try { - // parse 'true' into `true`, 'false' into `false`, etc. - isScratchDesktop.override = JSON.parse(scratchDesktopMatches[1]); - } catch { - // it's not JSON so just use the string - // note that a typo like "falsy" will be treated as true - isScratchDesktop.override = scratchDesktopMatches[1]; - } - } +const notScratchDesktop = function () { + return !isScratchDesktop(); }; export default isScratchDesktop; -export {setIsScratchDesktopOverrideFromHref}; +export { + isScratchDesktop, + notScratchDesktop, + setIsScratchDesktop +}; diff --git a/src/lib/libraries/decks/index.jsx b/src/lib/libraries/decks/index.jsx index b8aefa207..78aeb6f29 100644 --- a/src/lib/libraries/decks/index.jsx +++ b/src/lib/libraries/decks/index.jsx @@ -1,8 +1,6 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; -import isScratchDesktop from '../../../lib/isScratchDesktop'; - // Intro import libraryIntro from './intro/lib-getting-started.jpg'; import stepMove from './intro/intro1.gif'; @@ -118,7 +116,8 @@ import addEffectsThumb from './videos/add-effects.jpg'; import moveArrowKeysThumb from './videos/move-arrow-keys.jpg'; import spinThumb from './videos/spin.jpg'; -const decksRaw = { +export default { + 'intro-move-sayhello': { name: ( !deckInfo.hasOwnProperty('requiredProjectId')) - .reduce((newDecks, [key, deckInfo]) => { - newDecks[key] = deckInfo; - return newDecks; - }, {}) : - decksRaw; - -export default decks; diff --git a/src/playground/index.jsx b/src/playground/index.jsx index 91555003a..dcf6fa181 100644 --- a/src/playground/index.jsx +++ b/src/playground/index.jsx @@ -7,10 +7,6 @@ import 'intl'; // For Safari 9 import React from 'react'; import ReactDOM from 'react-dom'; -// this should be done before our other imports in case any use `isScratchDesktop()` right away -import {setIsScratchDesktopOverrideFromHref} from '../lib/isScratchDesktop'; -setIsScratchDesktopOverrideFromHref(window.location.href); - import analytics from '../lib/analytics'; import AppStateHOC from '../lib/app-state-hoc.jsx'; import BrowserModalComponent from '../components/browser-modal/browser-modal.jsx'; diff --git a/src/playground/render-gui.jsx b/src/playground/render-gui.jsx index ab4931f78..3ae35e4d3 100644 --- a/src/playground/render-gui.jsx +++ b/src/playground/render-gui.jsx @@ -2,8 +2,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import {compose} from 'redux'; -import isScratchDesktop from '../lib/isScratchDesktop'; - import AppStateHOC from '../lib/app-state-hoc.jsx'; import GUI from '../containers/gui.jsx'; import HashParserHOC from '../lib/hash-parser-hoc.jsx'; @@ -34,19 +32,38 @@ export default appTarget => { const backpackHostMatches = window.location.href.match(/[?&]backpack_host=([^&]*)&?/); const backpackHost = backpackHostMatches ? backpackHostMatches[1] : null; + const scratchDesktopMatches = window.location.href.match(/[?&]isScratchDesktop=([^&]+)/); + let simulateScratchDesktop; + if (scratchDesktopMatches) { + try { + // parse 'true' into `true`, 'false' into `false`, etc. + simulateScratchDesktop = JSON.parse(scratchDesktopMatches[1]); + } catch { + // it's not JSON so just use the string + // note that a typo like "falsy" will be treated as true + simulateScratchDesktop = scratchDesktopMatches[1]; + } + } + if (process.env.NODE_ENV === 'production' && typeof window === 'object') { // Warn before navigating away window.onbeforeunload = () => true; } ReactDOM.render( - , + // important: this is checking whether `simulateScratchDesktop` is truthy, not just defined! + simulateScratchDesktop ? + : + , appTarget); };