diff --git a/src/components/menu-bar/project-title-input.css b/src/components/menu-bar/project-title-input.css index 04d5021d73db18d6906ae1f8cc269419b1ad0c1b..84dcd0afcda19a459d091d4877ab52ea23bdf19c 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 da49372f8f106a4f0359758cf27be321651c6875..8b927417fddb717b4ff2fdfdd948e0bf7222e873 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 8486f29e3465a9be9fd102be86eb41da55c34028..04c6142dce25e073c4f0a0676cd2ab3e5c37590d 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 dae8ed62cd46ca6a5d808d336b59090ee3d80530..66081b7a906871cef4a5230d9264cc434c2d3dcd 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 37ffa62227a6f6ccb42a6170d4652db037f4f769..2222608922a9df36770fb8330fb663b2ea1e43c7 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 0000000000000000000000000000000000000000..2534d96bd61ba9f4cb4db89a51e33c031f1be6c4 --- /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 8eac6bd2247ccd677bab41cfa7e94a979d0874af..ba0ada4da78fbc561cbd7eca365e8a73aa2b62c8 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 9203104a23f98733e6febc81e8bb4fa8eefc731a..e236de667eb4b12919548908daad377dfa827fe9 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 adec922b8c206c9211888da34a8c426362b9a10b..562e57eac9c4c60ad4aea322adeec8b907067761 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 b8cd3ade5815fbca15750cab076e455eb555c818..09bf6c4eab417e626b8a719326ac685c25d46cdd 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;