From 728d705b1ac6768c47c89a8297cf77c838f7f977 Mon Sep 17 00:00:00 2001
From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com>
Date: Tue, 26 Jan 2021 14:17:22 -0800
Subject: [PATCH] fix merge & rearrange components to satisfy lint
---
src/renderer/ScratchDesktopAppStateHOC.jsx | 53 +++++
src/renderer/ScratchDesktopGUIHOC.jsx | 175 +++++++++++++++++
src/renderer/app.jsx | 215 +--------------------
3 files changed, 230 insertions(+), 213 deletions(-)
create mode 100644 src/renderer/ScratchDesktopAppStateHOC.jsx
create mode 100644 src/renderer/ScratchDesktopGUIHOC.jsx
diff --git a/src/renderer/ScratchDesktopAppStateHOC.jsx b/src/renderer/ScratchDesktopAppStateHOC.jsx
new file mode 100644
index 0000000..13a7905
--- /dev/null
+++ b/src/renderer/ScratchDesktopAppStateHOC.jsx
@@ -0,0 +1,53 @@
+import {ipcRenderer} from 'electron';
+import bindAll from 'lodash.bindall';
+import React from 'react';
+
+/**
+ * Higher-order component to add desktop logic to AppStateHOC.
+ * @param {Component} WrappedComponent - an AppStateHOC-like component to wrap.
+ * @returns {Component} - a component similar to AppStateHOC with desktop-specific logic added.
+ */
+const ScratchDesktopAppStateHOC = function (WrappedComponent) {
+ class ScratchDesktopAppStateComponent extends React.Component {
+ constructor (props) {
+ super(props);
+ bindAll(this, [
+ 'handleTelemetryModalOptIn',
+ 'handleTelemetryModalOptOut'
+ ]);
+ this.state = {
+ // use `sendSync` because this should be set before first render
+ telemetryDidOptIn: ipcRenderer.sendSync('getTelemetryDidOptIn')
+ };
+ }
+ handleTelemetryModalOptIn () {
+ ipcRenderer.send('setTelemetryDidOptIn', true);
+ ipcRenderer.invoke('getTelemetryDidOptIn').then(telemetryDidOptIn => {
+ this.setState({telemetryDidOptIn});
+ });
+ }
+ handleTelemetryModalOptOut () {
+ ipcRenderer.send('setTelemetryDidOptIn', false);
+ ipcRenderer.invoke('getTelemetryDidOptIn').then(telemetryDidOptIn => {
+ this.setState({telemetryDidOptIn});
+ });
+ }
+ render () {
+ const shouldShowTelemetryModal = (typeof ipcRenderer.sendSync('getTelemetryDidOptIn') !== 'boolean');
+
+ return ();
+ }
+ }
+
+ return ScratchDesktopAppStateComponent;
+};
+
+export default ScratchDesktopAppStateHOC;
diff --git a/src/renderer/ScratchDesktopGUIHOC.jsx b/src/renderer/ScratchDesktopGUIHOC.jsx
new file mode 100644
index 0000000..97e5c9e
--- /dev/null
+++ b/src/renderer/ScratchDesktopGUIHOC.jsx
@@ -0,0 +1,175 @@
+import {ipcRenderer, remote} from 'electron';
+import bindAll from 'lodash.bindall';
+import omit from 'lodash.omit';
+import PropTypes from 'prop-types';
+import React from 'react';
+import {connect} from 'react-redux';
+import GUIComponent from 'scratch-gui/src/components/gui/gui.jsx';
+
+import {
+ LoadingStates,
+ onFetchedProjectData,
+ onLoadedProject,
+ defaultProjectId,
+ requestNewProject,
+ requestProjectUpload,
+ setProjectId
+} from 'scratch-gui/src/reducers/project-state';
+import {
+ openLoadingProject,
+ closeLoadingProject,
+ openTelemetryModal
+} from 'scratch-gui/src/reducers/modals';
+
+import ElectronStorageHelper from '../common/ElectronStorageHelper';
+
+import showPrivacyPolicy from './showPrivacyPolicy';
+
+/**
+ * Higher-order component to add desktop logic to the GUI.
+ * @param {Component} WrappedComponent - a GUI-like component to wrap.
+ * @returns {Component} - a component similar to GUI with desktop-specific logic added.
+ */
+const ScratchDesktopGUIHOC = function (WrappedComponent) {
+ class ScratchDesktopGUIComponent extends React.Component {
+ constructor (props) {
+ super(props);
+ bindAll(this, [
+ 'handleProjectTelemetryEvent',
+ 'handleSetTitleFromSave',
+ 'handleStorageInit',
+ 'handleUpdateProjectTitle'
+ ]);
+ this.props.onLoadingStarted();
+ ipcRenderer.invoke('get-initial-project-data').then(initialProjectData => {
+ const hasInitialProject = initialProjectData && (initialProjectData.length > 0);
+ this.props.onHasInitialProject(hasInitialProject, this.props.loadingState);
+ if (!hasInitialProject) {
+ this.props.onLoadingCompleted();
+ return;
+ }
+ this.props.vm.loadProject(initialProjectData).then(
+ () => {
+ this.props.onLoadingCompleted();
+ this.props.onLoadedProject(this.props.loadingState, true);
+ },
+ e => {
+ this.props.onLoadingCompleted();
+ this.props.onLoadedProject(this.props.loadingState, false);
+ remote.dialog.showMessageBox(remote.getCurrentWindow(), {
+ type: 'error',
+ title: 'Failed to load project',
+ message: 'Invalid or corrupt project file.',
+ detail: e.message
+ });
+
+ // this effectively sets the default project ID
+ // TODO: maybe setting the default project ID should be implicit in `requestNewProject`
+ this.props.onHasInitialProject(false, this.props.loadingState);
+
+ // restart as if we didn't have an initial project to load
+ this.props.onRequestNewProject();
+ }
+ );
+ });
+ }
+ componentDidMount () {
+ ipcRenderer.on('setTitleFromSave', this.handleSetTitleFromSave);
+ }
+ componentWillUnmount () {
+ ipcRenderer.removeListener('setTitleFromSave', this.handleSetTitleFromSave);
+ }
+ handleClickAbout () {
+ ipcRenderer.send('open-about-window');
+ }
+ handleProjectTelemetryEvent (event, metadata) {
+ ipcRenderer.send(event, metadata);
+ }
+ handleSetTitleFromSave (event, args) {
+ this.handleUpdateProjectTitle(args.title);
+ }
+ handleStorageInit (storageInstance) {
+ storageInstance.addHelper(new ElectronStorageHelper(storageInstance));
+ }
+ handleUpdateProjectTitle (newTitle) {
+ this.setState({projectTitle: newTitle});
+ }
+ render () {
+ const childProps = omit(this.props, Object.keys(ScratchDesktopGUIComponent.propTypes));
+
+ return ( this.handleClickAbout()
+ },
+ {
+ title: 'Privacy Policy',
+ onClick: () => showPrivacyPolicy()
+ },
+ {
+ title: 'Data Settings',
+ onClick: () => this.props.onTelemetrySettingsClicked()
+ }
+ ]}
+ onProjectTelemetryEvent={this.handleProjectTelemetryEvent}
+ onShowPrivacyPolicy={showPrivacyPolicy}
+ onStorageInit={this.handleStorageInit}
+ onUpdateProjectTitle={this.handleUpdateProjectTitle}
+
+ // allow passed-in props to override any of the above
+ {...childProps}
+ />);
+ }
+ }
+
+ ScratchDesktopGUIComponent.propTypes = {
+ loadingState: PropTypes.oneOf(LoadingStates),
+ onFetchedInitialProjectData: PropTypes.func,
+ onHasInitialProject: PropTypes.func,
+ onLoadedProject: PropTypes.func,
+ onLoadingCompleted: PropTypes.func,
+ onLoadingStarted: PropTypes.func,
+ onRequestNewProject: PropTypes.func,
+ onTelemetrySettingsClicked: PropTypes.func,
+ // using PropTypes.instanceOf(VM) here will cause prop type warnings due to VM mismatch
+ vm: GUIComponent.WrappedComponent.propTypes.vm
+ };
+ const mapStateToProps = state => {
+ const loadingState = state.scratchGui.projectState.loadingState;
+ return {
+ loadingState: loadingState,
+ vm: state.scratchGui.vm
+ };
+ };
+ const mapDispatchToProps = dispatch => ({
+ onLoadingStarted: () => dispatch(openLoadingProject()),
+ onLoadingCompleted: () => dispatch(closeLoadingProject()),
+ onHasInitialProject: (hasInitialProject, loadingState) => {
+ if (hasInitialProject) {
+ // emulate sb-file-uploader
+ return dispatch(requestProjectUpload(loadingState));
+ }
+
+ // `createProject()` might seem more appropriate but it's not a valid state transition here
+ // setting the default project ID is a valid transition from NOT_LOADED and acts like "create new"
+ return dispatch(setProjectId(defaultProjectId));
+ },
+ onFetchedInitialProjectData: (projectData, loadingState) =>
+ dispatch(onFetchedProjectData(projectData, loadingState)),
+ onLoadedProject: (loadingState, loadSuccess) => {
+ const canSaveToServer = false;
+ return dispatch(onLoadedProject(loadingState, canSaveToServer, loadSuccess));
+ },
+ onRequestNewProject: () => dispatch(requestNewProject(false)),
+ onTelemetrySettingsClicked: () => dispatch(openTelemetryModal())
+ });
+
+ return connect(mapStateToProps, mapDispatchToProps)(ScratchDesktopGUIComponent);
+};
+
+export default ScratchDesktopGUIHOC;
diff --git a/src/renderer/app.jsx b/src/renderer/app.jsx
index 7810fc5..d437381 100644
--- a/src/renderer/app.jsx
+++ b/src/renderer/app.jsx
@@ -1,32 +1,11 @@
-import {ipcRenderer, remote} from 'electron';
-import bindAll from 'lodash.bindall';
-import omit from 'lodash.omit';
-import PropTypes from 'prop-types';
import React from 'react';
-import {connect} from 'react-redux';
import {compose} from 'redux';
import GUI from 'scratch-gui/src/index';
-import GUIComponent from 'scratch-gui/src/components/gui/gui.jsx';
import AppStateHOC from 'scratch-gui/src/lib/app-state-hoc.jsx';
-import {
- LoadingStates,
- onFetchedProjectData,
- onLoadedProject,
- defaultProjectId,
- requestNewProject,
- requestProjectUpload,
- setProjectId
-} from 'scratch-gui/src/reducers/project-state';
-import {
- openLoadingProject,
- closeLoadingProject,
- openTelemetryModal
-} from 'scratch-gui/src/reducers/modals';
-import ElectronStorageHelper from '../common/ElectronStorageHelper';
-
-import showPrivacyPolicy from './showPrivacyPolicy';
+import ScratchDesktopAppStateHOC from './ScratchDesktopAppStateHOC.jsx';
+import ScratchDesktopGUIHOC from './ScratchDesktopGUIHOC.jsx';
import styles from './app.css';
const appTarget = document.getElementById('app');
@@ -34,196 +13,6 @@ appTarget.className = styles.app || 'app';
GUI.setAppElement(appTarget);
-/**
- * Higher-order component to add desktop logic to AppStateHOC.
- * @param {Component} WrappedComponent - an AppStateHOC-like component to wrap.
- * @returns {Component} - a component similar to AppStateHOC with desktop-specific logic added.
- */
-const ScratchDesktopAppStateHOC = function (WrappedComponent) {
- const ScratchDesktopAppStateComponent = function (props) {
- const shouldShowTelemetryModal = (typeof ipcRenderer.sendSync('getTelemetryDidOptIn') !== 'boolean');
-
- return ();
- };
-
- return ScratchDesktopAppStateComponent;
-};
-
-/**
- * Higher-order component to add desktop logic to the GUI.
- * @param {Component} WrappedComponent - a GUI-like component to wrap.
- * @returns {Component} - a component similar to GUI with desktop-specific logic added.
- */
-const ScratchDesktopGUIHOC = function (WrappedComponent) {
- class ScratchDesktopGUIComponent extends React.Component {
- constructor (props) {
- super(props);
- bindAll(this, [
- 'handleProjectTelemetryEvent',
- 'handleSetTitleFromSave',
- 'handleStorageInit',
- 'handleTelemetryModalOptIn',
- 'handleTelemetryModalOptOut',
- 'handleUpdateProjectTitle'
- ]);
- this.state = {
- // use `sendSync` because this should be set before first render
- telemetryDidOptIn: ipcRenderer.sendSync('getTelemetryDidOptIn')
- };
- this.props.onLoadingStarted();
- ipcRenderer.invoke('get-initial-project-data').then(initialProjectData => {
- const hasInitialProject = initialProjectData && (initialProjectData.length > 0);
- this.props.onHasInitialProject(hasInitialProject, this.props.loadingState);
- if (!hasInitialProject) {
- this.props.onLoadingCompleted();
- return;
- }
- this.props.vm.loadProject(initialProjectData).then(
- () => {
- this.props.onLoadingCompleted();
- this.props.onLoadedProject(this.props.loadingState, true);
- },
- e => {
- this.props.onLoadingCompleted();
- this.props.onLoadedProject(this.props.loadingState, false);
- remote.dialog.showMessageBox(remote.getCurrentWindow(), {
- type: 'error',
- title: 'Failed to load project',
- message: 'Invalid or corrupt project file.',
- detail: e.message
- });
-
- // this effectively sets the default project ID
- // TODO: maybe setting the default project ID should be implicit in `requestNewProject`
- this.props.onHasInitialProject(false, this.props.loadingState);
-
- // restart as if we didn't have an initial project to load
- this.props.onRequestNewProject();
- }
- );
- });
- }
- componentDidMount () {
- ipcRenderer.on('setTitleFromSave', this.handleSetTitleFromSave);
- }
- componentWillUnmount () {
- ipcRenderer.removeListener('setTitleFromSave', this.handleSetTitleFromSave);
- }
- handleClickAbout () {
- ipcRenderer.send('open-about-window');
- }
- handleProjectTelemetryEvent (event, metadata) {
- ipcRenderer.send(event, metadata);
- }
- handleSetTitleFromSave (event, args) {
- this.handleUpdateProjectTitle(args.title);
- }
- handleStorageInit (storageInstance) {
- storageInstance.addHelper(new ElectronStorageHelper(storageInstance));
- }
- handleTelemetryModalOptIn () {
- ipcRenderer.send('setTelemetryDidOptIn', true);
- ipcRenderer.invoke('getTelemetryDidOptIn').then(telemetryDidOptIn => {
- this.setState({telemetryDidOptIn});
- });
- }
- handleTelemetryModalOptOut () {
- ipcRenderer.send('setTelemetryDidOptIn', false);
- ipcRenderer.invoke('getTelemetryDidOptIn').then(telemetryDidOptIn => {
- this.setState({telemetryDidOptIn});
- });
- }
- handleUpdateProjectTitle (newTitle) {
- this.setState({projectTitle: newTitle});
- }
- render () {
- const shouldShowTelemetryModal = (typeof this.state.telemetryDidOptIn !== 'boolean');
-
- const childProps = omit(this.props, Object.keys(ScratchDesktopGUIComponent.propTypes));
-
- return ( this.handleClickAbout()
- },
- {
- title: 'Privacy Policy',
- onClick: () => showPrivacyPolicy()
- },
- {
- title: 'Data Settings',
- onClick: () => this.props.onTelemetrySettingsClicked()
- }
- ]}
- onProjectTelemetryEvent={this.handleProjectTelemetryEvent}
- onShowPrivacyPolicy={showPrivacyPolicy}
- onStorageInit={this.handleStorageInit}
- onTelemetryModalOptIn={this.handleTelemetryModalOptIn}
- onTelemetryModalOptOut={this.handleTelemetryModalOptOut}
- onUpdateProjectTitle={this.handleUpdateProjectTitle}
-
- // allow passed-in props to override any of the above
- {...childProps}
- />);
- }
- }
-
- ScratchDesktopGUIComponent.propTypes = {
- loadingState: PropTypes.oneOf(LoadingStates),
- onFetchedInitialProjectData: PropTypes.func,
- onHasInitialProject: PropTypes.func,
- onLoadedProject: PropTypes.func,
- onLoadingCompleted: PropTypes.func,
- onLoadingStarted: PropTypes.func,
- onRequestNewProject: PropTypes.func,
- onTelemetrySettingsClicked: PropTypes.func,
- // using PropTypes.instanceOf(VM) here will cause prop type warnings due to VM mismatch
- vm: GUIComponent.WrappedComponent.propTypes.vm
- };
- const mapStateToProps = state => {
- const loadingState = state.scratchGui.projectState.loadingState;
- return {
- loadingState: loadingState,
- vm: state.scratchGui.vm
- };
- };
- const mapDispatchToProps = dispatch => ({
- onLoadingStarted: () => dispatch(openLoadingProject()),
- onLoadingCompleted: () => dispatch(closeLoadingProject()),
- onHasInitialProject: (hasInitialProject, loadingState) => {
- if (hasInitialProject) {
- // emulate sb-file-uploader
- return dispatch(requestProjectUpload(loadingState));
- }
-
- // `createProject()` might seem more appropriate but it's not a valid state transition here
- // setting the default project ID is a valid transition from NOT_LOADED and acts like "create new"
- return dispatch(setProjectId(defaultProjectId));
- },
- onFetchedInitialProjectData: (projectData, loadingState) =>
- dispatch(onFetchedProjectData(projectData, loadingState)),
- onLoadedProject: (loadingState, loadSuccess) => {
- const canSaveToServer = false;
- return dispatch(onLoadedProject(loadingState, canSaveToServer, loadSuccess));
- },
- onRequestNewProject: () => dispatch(requestNewProject(false)),
- onTelemetrySettingsClicked: () => dispatch(openTelemetryModal())
- });
-
- return connect(mapStateToProps, mapDispatchToProps)(ScratchDesktopGUIComponent);
-};
// 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