From f0a10fdfa0e022dc2d05c30d73381e4c2c633598 Mon Sep 17 00:00:00 2001
From: Ben Wheeler <wheeler.benjamin@gmail.com>
Date: Thu, 6 Sep 2018 19:11:34 -0400
Subject: [PATCH] localized initial project name, fixed misuse of TitledHOC,
 truncate local filenames

---
 .../menu-bar/project-title-input.css          | 12 +++-
 .../menu-bar/project-title-input.jsx          | 18 ++++--
 src/containers/gui.jsx                        |  7 ++-
 src/containers/project-loader.jsx             |  2 +-
 src/css/colors.css                            |  1 +
 src/lib/titled-hoc.jsx                        | 54 ++++++++++++++++++
 src/playground/index.jsx                      | 52 ------------------
 src/playground/player.jsx                     | 55 ++++++-------------
 src/playground/render-gui.jsx                 |  3 +-
 src/reducers/project-title.js                 |  4 +-
 10 files changed, 104 insertions(+), 104 deletions(-)
 create mode 100644 src/lib/titled-hoc.jsx

diff --git a/src/components/menu-bar/project-title-input.css b/src/components/menu-bar/project-title-input.css
index 04d5021d7..84dcd0afc 100644
--- a/src/components/menu-bar/project-title-input.css
+++ b/src/components/menu-bar/project-title-input.css
@@ -17,20 +17,26 @@ $title-width: 12rem;
     padding: .5rem;
 }
 
-.title-field,
-.title-field::placeholder {
+.title-field {
     color: $ui-white;
     font-weight: bold;
     font-size: .8rem;
 }
 
+.title-field::placeholder {
+    color: $ui-white;
+    font-weight: normal;
+    font-size: .8rem;
+    font-style: italic;
+}
+
 .title-field:hover {
     background-color: hsla(0, 100%, 100%, 0.5);
 }
 
 .title-field:focus {
     outline:none;
-    border: none;
+    border: 1px solid $ui-transparent;
     -webkit-box-shadow: 0 0 0 calc($space * .5) $ui-white-transparent;
     box-shadow: 0 0 0 calc($space * .5) $ui-white-transparent;
     background-color: $ui-white;
diff --git a/src/components/menu-bar/project-title-input.jsx b/src/components/menu-bar/project-title-input.jsx
index da49372f8..8b927417f 100644
--- a/src/components/menu-bar/project-title-input.jsx
+++ b/src/components/menu-bar/project-title-input.jsx
@@ -3,6 +3,7 @@ import {connect} from 'react-redux';
 import PropTypes from 'prop-types';
 import bindAll from 'lodash.bindall';
 import React from 'react';
+import {defineMessages, intlShape, injectIntl} from 'react-intl';
 
 import BufferedInputHOC from '../forms/buffered-input-hoc.jsx';
 import Input from '../forms/input.jsx';
@@ -10,6 +11,14 @@ const BufferedInput = BufferedInputHOC(Input);
 
 import styles from './project-title-input.css';
 
+const messages = defineMessages({
+    projectTitlePlaceholder: {
+        id: 'gui.gui.projectTitlePlaceholder',
+        description: 'Placeholder for project title when blank',
+        defaultMessage: 'Project title here'
+    }
+});
+
 class ProjectTitleInput extends React.Component {
     constructor (props) {
         super(props);
@@ -28,8 +37,8 @@ class ProjectTitleInput extends React.Component {
         return (
             <BufferedInput
                 className={classNames(styles.titleField)}
-                maxlength="100"
-                placeholder=""
+                maxLength="100"
+                placeholder={this.props.intl.formatMessage(messages.projectTitlePlaceholder)}
                 tabIndex="0"
                 type="text"
                 value={this.props.projectTitle}
@@ -40,6 +49,7 @@ class ProjectTitleInput extends React.Component {
 }
 
 ProjectTitleInput.propTypes = {
+    intl: intlShape.isRequired,
     onUpdateProjectTitle: PropTypes.func,
     projectTitle: PropTypes.string
 };
@@ -50,7 +60,7 @@ const mapStateToProps = state => ({
 
 const mapDispatchToProps = () => ({});
 
-export default connect(
+export default injectIntl(connect(
     mapStateToProps,
     mapDispatchToProps
-)(ProjectTitleInput);
+)(ProjectTitleInput));
diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx
index 8486f29e3..04c6142dc 100644
--- a/src/containers/gui.jsx
+++ b/src/containers/gui.jsx
@@ -35,6 +35,10 @@ class GUI extends React.Component {
         };
     }
     componentDidMount () {
+        if (this.props.projectTitle) {
+            this.props.onUpdateReduxProjectTitle(this.props.projectTitle);
+        }
+
         if (this.props.vm.initialized) return;
         this.audioEngine = new AudioEngine();
         this.props.vm.attachAudioEngine(this.audioEngine);
@@ -51,9 +55,6 @@ class GUI extends React.Component {
                 this.setState({loadingError: true, errorMessage: e});
             });
         this.props.vm.initialized = true;
-        if (this.props.projectTitle) {
-            this.props.onUpdateReduxProjectTitle(this.props.projectTitle);
-        }
     }
     componentWillReceiveProps (nextProps) {
         if (this.props.projectData !== nextProps.projectData) {
diff --git a/src/containers/project-loader.jsx b/src/containers/project-loader.jsx
index dae8ed62c..66081b7a9 100644
--- a/src/containers/project-loader.jsx
+++ b/src/containers/project-loader.jsx
@@ -79,7 +79,7 @@ class ProjectLoader extends React.Component {
             if (thisFileInput.files[0].name) {
                 const matches = thisFileInput.files[0].name.match(/^(.*)\.sb3$/);
                 if (matches) {
-                    this.props.onSetProjectTitle(matches[1]);
+                    this.props.onSetProjectTitle(matches[1].substring(0, 100));
                 }
             }
         }
diff --git a/src/css/colors.css b/src/css/colors.css
index 37ffa6222..222260892 100644
--- a/src/css/colors.css
+++ b/src/css/colors.css
@@ -6,6 +6,7 @@ $ui-modal-overlay: hsla(215, 100%, 65%, 0.9); /* 90% transparent version of moti
 
 $ui-white: hsla(0, 100%, 100%, 1); /* #FFFFFF */
 $ui-white-transparent: hsla(0, 100%, 100%, 0.25); /* 25% transparent version of ui-white */
+$ui-transparent: hsla(0, 100%, 100%, 0); /* 25% transparent version of ui-white */
 
 $ui-black-transparent: hsla(0, 0%, 0%, 0.15); /* 15% transparent version of black */
 
diff --git a/src/lib/titled-hoc.jsx b/src/lib/titled-hoc.jsx
new file mode 100644
index 000000000..2534d96bd
--- /dev/null
+++ b/src/lib/titled-hoc.jsx
@@ -0,0 +1,54 @@
+import React from 'react';
+import bindAll from 'lodash.bindall';
+import {defineMessages, intlShape, injectIntl} from 'react-intl';
+
+const messages = defineMessages({
+    defaultProjectTitle: {
+        id: 'gui.gui.defaultProjectTitle',
+        description: 'Default title for project',
+        defaultMessage: 'Scratch Project'
+    }
+});
+
+/* Higher Order Component to get and set the project title
+ * @param {React.Component} WrappedComponent component to receive project title related props
+ * @returns {React.Component} component with project loading behavior
+ */
+const TitledHOC = function (WrappedComponent) {
+    class TitledComponent extends React.Component {
+        constructor (props) {
+            super(props);
+            bindAll(this, [
+                'handleUpdateProjectTitle'
+            ]);
+            this.state = {
+                projectTitle: this.props.intl.formatMessage(messages.defaultProjectTitle)
+            };
+        }
+        handleUpdateProjectTitle (newTitle) {
+            this.setState({projectTitle: newTitle});
+        }
+        render () {
+            return (
+                <WrappedComponent
+                    projectTitle={this.state.projectTitle}
+                    onUpdateProjectTitle={this.handleUpdateProjectTitle}
+                    {...this.props}
+                />
+            );
+        }
+    }
+
+    TitledComponent.propTypes = {
+        intl: intlShape.isRequired
+    };
+
+    // return TitledComponent;
+    const IntlTitledComponent = injectIntl(TitledComponent);
+    return IntlTitledComponent;
+
+};
+
+export {
+    TitledHOC as default
+};
diff --git a/src/playground/index.jsx b/src/playground/index.jsx
index 8eac6bd22..ba0ada4da 100644
--- a/src/playground/index.jsx
+++ b/src/playground/index.jsx
@@ -5,7 +5,6 @@ import 'intl'; // For Safari 9
 
 import React from 'react';
 import ReactDOM from 'react-dom';
-import bindAll from 'lodash.bindall';
 
 import analytics from '../lib/analytics';
 import AppStateHOC from '../lib/app-state-hoc.jsx';
@@ -33,54 +32,3 @@ if (supportedBrowser()) {
     // eslint-disable-next-line react/jsx-no-bind
     ReactDOM.render(<WrappedBrowserModalComponent onBack={handleBack} />, appTarget);
 }
-
-// GUI.setAppElement(appTarget);
-//
-// // simple example of how you might manage project title externally.
-// // Changing project title within GUI interface will update it here.
-// class TitledGUI extends React.Component {
-//     constructor (props) {
-//         super(props);
-//         bindAll(this, [
-//             'handleUpdateProjectTitle'
-//         ]);
-//         this.state = {
-//             projectTitle: 'Untitled-1'
-//         };
-//     }
-//     handleUpdateProjectTitle (newTitle) {
-//         this.setState({projectTitle: newTitle});
-//     }
-//     render () {
-//         const {
-//             projectTitle, // eslint-disable-line no-unused-vars
-//             onUpdateProjectTitle, // eslint-disable-line no-unused-vars
-//             ...componentProps
-//         } = this.props;
-//         return (
-//             <GUI
-//                 {...componentProps}
-//                 projectTitle={this.state.projectTitle}
-//                 onUpdateProjectTitle={this.handleUpdateProjectTitle}
-//             />
-//         );
-//     }
-// }
-//
-// const WrappedGui = HashParserHOC(AppStateHOC(TitledGUI));
-//
-// // TODO a hack for testing the backpack, allow backpack host to be set by url param
-// const backpackHostMatches = window.location.href.match(/[?&]backpack_host=([^&]*)&?/);
-// const backpackHost = backpackHostMatches ? backpackHostMatches[1] : null;
-//
-// const backpackOptions = {
-//     visible: true,
-//     host: backpackHost
-// };
-//
-// ReactDOM.render(
-//     <WrappedGui
-//         backpackOptions={backpackOptions}
-//     />,
-//     appTarget
-// );
diff --git a/src/playground/player.jsx b/src/playground/player.jsx
index 9203104a2..e236de667 100644
--- a/src/playground/player.jsx
+++ b/src/playground/player.jsx
@@ -3,12 +3,12 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import ReactDOM from 'react-dom';
 import {connect} from 'react-redux';
-import bindAll from 'lodash.bindall';
 
 import Box from '../components/box/box.jsx';
 import GUI from '../containers/gui.jsx';
 import HashParserHOC from '../lib/hash-parser-hoc.jsx';
 import AppStateHOC from '../lib/app-state-hoc.jsx';
+import TitledHOC from '../lib/titled-hoc.jsx';
 
 import {setPlayer} from '../reducers/mode';
 
@@ -19,43 +19,20 @@ if (process.env.NODE_ENV === 'production' && typeof window === 'object') {
 
 import styles from './player.css';
 
-class Player extends React.Component {
-    constructor (props) {
-        super(props);
-        bindAll(this, [
-            'handleUpdateProjectTitle'
-        ]);
-        this.state = {
-            projectTitle: 'Untitled-1'
-        };
-    }
-    handleUpdateProjectTitle (newTitle) {
-        this.setState({projectTitle: newTitle});
-    }
-    render () {
-        const {
-            isPlayerOnly,
-            onSeeInside,
-            projectId
-        } = this.props;
-        return (
-            <Box
-                className={classNames({
-                    [styles.stageOnly]: isPlayerOnly
-                })}
-            >
-                {isPlayerOnly && <button onClick={onSeeInside}>{'See inside'}</button>}
-                <GUI
-                    enableCommunity
-                    isPlayerOnly={isPlayerOnly}
-                    projectId={projectId}
-                    projectTitle={this.state.projectTitle}
-                    onUpdateProjectTitle={this.handleUpdateProjectTitle}
-                />
-            </Box>
-        );
-    }
-}
+const Player = ({isPlayerOnly, onSeeInside, projectId}) => (
+    <Box
+        className={classNames({
+            [styles.stageOnly]: isPlayerOnly
+        })}
+    >
+        {isPlayerOnly && <button onClick={onSeeInside}>{'See inside'}</button>}
+        <GUI
+            enableCommunity
+            isPlayerOnly={isPlayerOnly}
+            projectId={projectId}
+        />
+    </Box>
+);
 
 Player.propTypes = {
     isPlayerOnly: PropTypes.bool,
@@ -72,7 +49,7 @@ const mapDispatchToProps = dispatch => ({
 });
 
 const ConnectedPlayer = connect(mapStateToProps, mapDispatchToProps)(Player);
-const WrappedPlayer = HashParserHOC(AppStateHOC(ConnectedPlayer));
+const WrappedPlayer = HashParserHOC(AppStateHOC(TitledHOC(ConnectedPlayer)));
 
 const appTarget = document.createElement('div');
 document.body.appendChild(appTarget);
diff --git a/src/playground/render-gui.jsx b/src/playground/render-gui.jsx
index adec922b8..562e57eac 100644
--- a/src/playground/render-gui.jsx
+++ b/src/playground/render-gui.jsx
@@ -4,6 +4,7 @@ import ReactDOM from 'react-dom';
 import AppStateHOC from '../lib/app-state-hoc.jsx';
 import GUI from '../containers/gui.jsx';
 import HashParserHOC from '../lib/hash-parser-hoc.jsx';
+import TitledHOC from '../lib/titled-hoc.jsx';
 
 /*
  * Render the GUI playground. This is a separate function because importing anything
@@ -12,7 +13,7 @@ import HashParserHOC from '../lib/hash-parser-hoc.jsx';
  */
 export default appTarget => {
     GUI.setAppElement(appTarget);
-    const WrappedGui = HashParserHOC(AppStateHOC(GUI));
+    const WrappedGui = HashParserHOC(AppStateHOC(TitledHOC(GUI)));
 
     // TODO a hack for testing the backpack, allow backpack host to be set by url param
     const backpackHostMatches = window.location.href.match(/[?&]backpack_host=([^&]*)&?/);
diff --git a/src/reducers/project-title.js b/src/reducers/project-title.js
index b8cd3ade5..09bf6c4ea 100644
--- a/src/reducers/project-title.js
+++ b/src/reducers/project-title.js
@@ -1,6 +1,8 @@
 const SET_PROJECT_TITLE = 'projectTitle/SET_PROJECT_TITLE';
 
-const initialState = 'Untitled-1';
+// we are initializing to a blank string instead of an actual title,
+// because it would be hard to localize here
+const initialState = '';
 
 const reducer = function (state, action) {
     if (typeof state === 'undefined') state = initialState;
-- 
GitLab