import PropTypes from 'prop-types'; import React from 'react'; import {compose} from 'redux'; import {connect} from 'react-redux'; import ReactModal from 'react-modal'; import VM from 'scratch-vm'; import {injectIntl, intlShape} from 'react-intl'; import ErrorBoundaryHOC from '../lib/error-boundary-hoc.jsx'; import { getIsError, getIsShowingProject } from '../reducers/project-state'; import { activateTab, BLOCKS_TAB_INDEX, COSTUMES_TAB_INDEX, SOUNDS_TAB_INDEX, VARIABLES_TAB_INDEX, FILES_TAB_INDEX } from '../reducers/editor-tab'; import { closeCostumeLibrary, closeBackdropLibrary, closeTelemetryModal, openExtensionLibrary } from '../reducers/modals'; import FontLoaderHOC from '../lib/font-loader-hoc.jsx'; import LocalizationHOC from '../lib/localization-hoc.jsx'; import SBFileUploaderHOC from '../lib/sb-file-uploader-hoc.jsx'; import ProjectFetcherHOC from '../lib/project-fetcher-hoc.jsx'; import TitledHOC from '../lib/titled-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'; import cloudManagerHOC from '../lib/cloud-manager-hoc.jsx'; import TWFullScreenResizerHOC from '../lib/tw-fullscreen-resizer-hoc.jsx'; import GUIComponent from '../components/gui/gui.jsx'; import HomeCommunication from './home-communication.jsx'; import {setIsScratchDesktop} from '../lib/isScratchDesktop.js'; class GUI extends React.Component { componentDidMount () { setIsScratchDesktop(this.props.isScratchDesktop); this.props.onStorageInit(storage); this.props.onVmInit(this.props.vm); } componentDidUpdate (prevProps) { if (this.props.projectId !== prevProps.projectId && this.props.projectId !== null) { this.props.onUpdateProjectId(this.props.projectId); } if (this.props.isShowingProject && !prevProps.isShowingProject) { // this only notifies container when a project changes from not yet loaded to loaded // At this time the project view in www doesn't need to know when a project is unloaded this.props.onProjectLoaded(); } } render () { if (this.props.isError) { console.log('the below error was caught by the gui'); throw this.props.error; } const { /* eslint-disable no-unused-vars */ assetHost, cloudHost, error, isError, isScratchDesktop, isShowingProject, onProjectLoaded, onStorageInit, onUpdateProjectId, onVmInit, projectHost, projectId, /* eslint-enable no-unused-vars */ children, fetchingProject, isLoading, loadingStateVisible, isPlayground, ...componentProps } = this.props; return (<> {children} ); } } GUI.propTypes = { assetHost: PropTypes.string, children: PropTypes.node, cloudHost: PropTypes.string, error: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), fetchingProject: PropTypes.bool, intl: intlShape, isError: PropTypes.bool, isEmbedded: PropTypes.bool, isFullScreen: PropTypes.bool, isLoading: PropTypes.bool, isScratchDesktop: PropTypes.bool, isShowingProject: PropTypes.bool, isPlayground: PropTypes.bool, loadingStateVisible: PropTypes.bool, onProjectLoaded: PropTypes.func, onSeeCommunity: PropTypes.func, onStorageInit: PropTypes.func, onUpdateProjectId: PropTypes.func, onVmInit: PropTypes.func, projectHost: PropTypes.string, projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), telemetryModalVisible: PropTypes.bool, vm: PropTypes.instanceOf(VM).isRequired }; GUI.defaultProps = { isScratchDesktop: false, isPlayground: false, onStorageInit: storageInstance => storageInstance.addOfficialScratchWebStores(), onProjectLoaded: () => {}, onUpdateProjectId: () => {}, onVmInit: (/* vm */) => {} }; const mapStateToProps = state => { const loadingState = state.scratchGui.projectState.loadingState; return { activeTabIndex: state.scratchGui.editorTab.activeTabIndex, alertsVisible: state.scratchGui.alerts.visible, backdropLibraryVisible: state.scratchGui.modals.backdropLibrary, blocksTabVisible: state.scratchGui.editorTab.activeTabIndex === BLOCKS_TAB_INDEX, cardsVisible: state.scratchGui.cards.visible, connectionModalVisible: state.scratchGui.modals.connectionModal, costumeLibraryVisible: state.scratchGui.modals.costumeLibrary, costumesTabVisible: state.scratchGui.editorTab.activeTabIndex === COSTUMES_TAB_INDEX, error: state.scratchGui.projectState.error, isError: getIsError(loadingState), isEmbedded: state.scratchGui.mode.isEmbedded, isFullScreen: state.scratchGui.mode.isFullScreen || state.scratchGui.mode.isEmbedded, isPlayerOnly: state.scratchGui.mode.isPlayerOnly, isRtl: state.locales.isRtl, isShowingProject: getIsShowingProject(loadingState), loadingStateVisible: state.scratchGui.modals.loadingProject, projectId: state.scratchGui.projectState.projectId, soundsTabVisible: state.scratchGui.editorTab.activeTabIndex === SOUNDS_TAB_INDEX, variablesTabVisible: state.scratchGui.editorTab.activeTabIndex === VARIABLES_TAB_INDEX, filesTabVisible: state.scratchGui.editorTab.activeTabIndex === FILES_TAB_INDEX, targetIsStage: ( state.scratchGui.targets.stage && state.scratchGui.targets.stage.id === state.scratchGui.targets.editingTarget ), telemetryModalVisible: state.scratchGui.modals.telemetryModal, tipsLibraryVisible: state.scratchGui.modals.tipsLibrary, usernameModalVisible: state.scratchGui.modals.usernameModal, settingsModalVisible: state.scratchGui.modals.settingsModal, customExtensionModalVisible: state.scratchGui.modals.customExtensionModal, fontsModalVisible: state.scratchGui.modals.fontsModal, vm: state.scratchGui.vm }; }; const mapDispatchToProps = dispatch => ({ onExtensionButtonClick: () => dispatch(openExtensionLibrary()), onActivateTab: tab => dispatch(activateTab(tab)), onActivateCostumesTab: () => dispatch(activateTab(COSTUMES_TAB_INDEX)), onActivateSoundsTab: () => dispatch(activateTab(SOUNDS_TAB_INDEX)), onActivateVariablesTab: () => dispatch(activateTab(VARIABLES_TAB_INDEX)), onActivateFilesTab: () => dispatch(activateTab(FILES_TAB_INDEX)), onRequestCloseBackdropLibrary: () => dispatch(closeBackdropLibrary()), onRequestCloseCostumeLibrary: () => dispatch(closeCostumeLibrary()), onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal()) }); const ConnectedGUI = injectIntl(connect( mapStateToProps, mapDispatchToProps, )(GUI)); // note that redux's 'compose' function is just being used as a general utility to make // the hierarchy of HOC constructor calls clearer here; it has nothing to do with redux's // ability to compose reducers. const WrappedGui = compose( LocalizationHOC, ErrorBoundaryHOC('Top Level App'), FontLoaderHOC, // QueryParserHOC, // tw: HOC is unused ProjectFetcherHOC, TitledHOC, ProjectSaverHOC, vmListenerHOC, vmManagerHOC, SBFileUploaderHOC, cloudManagerHOC, TWFullScreenResizerHOC )(ConnectedGUI); WrappedGui.setAppElement = ReactModal.setAppElement; export default WrappedGui;