diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index 9935b3a7e25..025a739661e 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 5727e19d120..3c85ce99e39 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.keys(decksLibraryContent) + .filter(id => + // Scratch Desktop doesn't want tutorials with `requiredProjectId` + notScratchDesktop() || !decksLibraryContent[id].hasOwnProperty('requiredProjectId') + ) + .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 + })); if (!this.props.visible) return null; return ( diff --git a/src/lib/isScratchDesktop.js b/src/lib/isScratchDesktop.js new file mode 100644 index 00000000000..e159e385263 --- /dev/null +++ b/src/lib/isScratchDesktop.js @@ -0,0 +1,35 @@ +/** + * Internal stored state. Not valid until after at least one call to `setIsScratchDesktop()`. + * @type {boolean} + */ +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; +}; + +/** + * @returns {boolean} - true if it seems like the GUI is running under Scratch Desktop; false otherwise. + * If `setIsScratchDesktop()` has not yet been called, this can return `undefined`. + */ +const isScratchDesktop = function () { + return _isScratchDesktop; +}; + +/** + * @returns {boolean} - false if it seems like the GUI is running under Scratch Desktop; true otherwise. + */ +const notScratchDesktop = function () { + return !isScratchDesktop(); +}; + +export default isScratchDesktop; +export { + isScratchDesktop, + notScratchDesktop, + setIsScratchDesktop +}; diff --git a/src/playground/render-gui.jsx b/src/playground/render-gui.jsx index f3acac2eefd..3ae35e4d303 100644 --- a/src/playground/render-gui.jsx +++ b/src/playground/render-gui.jsx @@ -32,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); };