diff --git a/package.json b/package.json index 778cc2eeab42401a0bfface1d0226cd681b73c9f..50369a1f7079e65da1c8441695c93ba1c60e2b32 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "react-style-proptype": "3.1.0", "react-tabs": "2.1.1", "react-test-renderer": "16.2.0", + "react-tooltip": "3.4.0", "redux": "3.7.0", "redux-mock-store": "^1.2.3", "redux-throttle": "0.1.1", diff --git a/src/components/button/button.jsx b/src/components/button/button.jsx index ae322d10fbfcdf655a641e7e61fd40bf750af330..59b39668fe9952a6425850fbe222266fc94639bb 100644 --- a/src/components/button/button.jsx +++ b/src/components/button/button.jsx @@ -9,23 +9,31 @@ const ButtonComponent = ({ onClick, children, ...props -}) => ( - <span - className={classNames( - styles.button, - className - )} - role="button" - onClick={onClick} - {...props} - > - {children} - </span> -); +}) => { + if (props.disabled === true) { + onClick = function () {}; + } + + return ( + <span + className={classNames( + styles.button, + className + )} + role="button" + onClick={onClick} + {...props} + > + {children} + </span> + ); +}; ButtonComponent.propTypes = { children: PropTypes.node, className: PropTypes.string, + disabled: PropTypes.bool, onClick: PropTypes.func.isRequired }; + export default ButtonComponent; diff --git a/src/components/coming-soon/aww-cat.png b/src/components/coming-soon/aww-cat.png new file mode 100644 index 0000000000000000000000000000000000000000..bddfb8de9ee95ccef600bcb6a67ce5ed3c5ed4d5 Binary files /dev/null and b/src/components/coming-soon/aww-cat.png differ diff --git a/src/components/coming-soon/coming-soon.css b/src/components/coming-soon/coming-soon.css new file mode 100644 index 0000000000000000000000000000000000000000..7c3bdeb1496711ae37db84077a9779b4f361be5b --- /dev/null +++ b/src/components/coming-soon/coming-soon.css @@ -0,0 +1,68 @@ +/* + * NOTE: the copious use of `important` is needed to overwrite + * the default tooltip styling, and is required by the 3rd party + * library being used, `react-tooltip` + */ + +@import "../../css/colors.css"; + +.coming-soon { + background-color: $data-primary !important; + border: 1px solid hsla(0, 0%, 0%, .1) !important; + border-radius: .25rem !important; + box-shadow: 0 0 .5rem hsla(0, 0%, 0%, .25) !important; + padding: .75rem 1rem !important; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important; + font-size: 1rem !important; + line-height: 1.25rem !important; + z-index: 100 !important; +} + +.coming-soon:after { + content: ""; + border-top: 1px solid hsla(0, 0%, 0%, .1) !important; + border-left: 0 !important; + border-bottom: 0 !important; + border-right: 1px solid hsla(0, 0%, 0%, .1) !important; + border-radius: .25rem; + background-color: $data-primary !important; + height: 1rem !important; + width: 1rem !important; +} + +.show, +.show:before, +.show:after { + opacity: 1 !important; +} + +.left:after { + margin-top: -.5rem !important; + right: -.5rem !important; + transform: rotate(45deg) !important; +} + +.right:after { + margin-top: -.5rem !important; + left: -.5rem !important; + transform: rotate(-135deg) !important; +} + +.top:after { + margin-right: -.5rem !important; + bottom: -.5rem !important; + transform: rotate(135deg) !important; +} + +.bottom:after { + margin-left: -.5rem !important; + top: -.5rem !important; + transform: rotate(-45deg) !important; +} + +.coming-soon-image { + margin-left: .125rem; + width: 1.25rem; + height: 1.25rem; + vertical-align: middle; +} diff --git a/src/components/coming-soon/coming-soon.jsx b/src/components/coming-soon/coming-soon.jsx new file mode 100644 index 0000000000000000000000000000000000000000..e16ae80bea138376f354dc7c8611ac994e3384ad --- /dev/null +++ b/src/components/coming-soon/coming-soon.jsx @@ -0,0 +1,143 @@ +import bindAll from 'lodash.bindall'; +import classNames from 'classnames'; +import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl'; +import PropTypes from 'prop-types'; +import React from 'react'; +import ReactTooltip from 'react-tooltip'; + +import styles from './coming-soon.css'; + +import awwCatIcon from './aww-cat.png'; +import coolCatIcon from './cool-cat.png'; + +const messages = defineMessages({ + message1: { + defaultMessage: 'Don\'t worry, we\'re on it {emoji}', + description: 'One of the "coming soon" random messages for yet-to-be-done features', + id: 'gui.comingSoon.message1' + }, + message2: { + defaultMessage: 'Coming Soon...', + description: 'One of the "coming soon" random messages for yet-to-be-done features', + id: 'gui.comingSoon.message2' + }, + message3: { + defaultMessage: 'We\'re working on it {emoji}', + description: 'One of the "coming soon" random messages for yet-to-be-done features', + id: 'gui.comingSoon.message3' + } +}); + +class ComingSoonContent extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'setHide', + 'setShow', + 'getRandomMessage' + ]); + this.state = { + isShowing: false + }; + } + setShow () { + // needed to set the opacity to 1, since the default is .9 on show + this.setState({isShowing: true}); + } + setHide () { + this.setState({isShowing: false}); + } + getRandomMessage () { + // randomly chooses a messages from `messages` to display in the tooltip. + const images = [awwCatIcon, coolCatIcon]; + const messageNumber = Math.floor(Math.random() * Object.keys(messages).length) + 1; + const imageNumber = Math.floor(Math.random() * Object.keys(images).length); + return ( + <FormattedMessage + {...messages[`message${messageNumber}`]} + values={{ + emoji: ( + <img + className={styles.comingSoonImage} + src={images[imageNumber]} + /> + ) + }} + /> + ); + } + render () { + return ( + <ReactTooltip + afterHide={this.setHide} + afterShow={this.setShow} + className={classNames( + styles.comingSoon, + this.props.className, + { + [styles.show]: (this.state.isShowing), + [styles.left]: (this.props.place === 'left'), + [styles.right]: (this.props.place === 'right'), + [styles.top]: (this.props.place === 'top'), + [styles.bottom]: (this.props.place === 'bottom') + } + )} + getContent={this.getRandomMessage} + id={this.props.tooltipId} + /> + ); + } +} + +ComingSoonContent.propTypes = { + className: PropTypes.string, + intl: intlShape, + place: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), + tooltipId: PropTypes.string.isRequired +}; + +ComingSoonContent.defaultProps = { + place: 'bottom' +}; + +const ComingSoon = injectIntl(ComingSoonContent); + +const ComingSoonTooltip = props => ( + <div className={props.className}> + <div + data-delay-hide={props.delayHide} + data-delay-show={props.delayShow} + data-effect="solid" + data-for={props.tooltipId} + data-place={props.place} + data-tip="tooltip" + > + {props.children} + </div> + <ComingSoon + className={props.tooltipClassName} + place={props.place} + tooltipId={props.tooltipId} + /> + </div> +); + +ComingSoonTooltip.propTypes = { + children: PropTypes.node.isRequired, + className: PropTypes.string, + delayHide: PropTypes.number, + delayShow: PropTypes.number, + place: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), + tooltipClassName: PropTypes.string, + tooltipId: PropTypes.string.isRequired +}; + +ComingSoonTooltip.defaultProps = { + delayHide: 0, + delayShow: 0 +}; + +export { + ComingSoon as ComingSoonComponent, + ComingSoonTooltip +}; diff --git a/src/components/coming-soon/cool-cat.png b/src/components/coming-soon/cool-cat.png new file mode 100644 index 0000000000000000000000000000000000000000..15b2151e35bd84cfa154e6af8fd20607ca4d5e9e Binary files /dev/null and b/src/components/coming-soon/cool-cat.png differ diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css index 3d08cadf626c339dcdc33b3b1aa029c487078955..2c0533e984167f5e8b5712bb07e1ce338aa4a9f4 100644 --- a/src/components/gui/gui.css +++ b/src/components/gui/gui.css @@ -143,14 +143,6 @@ overflow: hidden; } -.stage-menu-wrapper { - display: flex; - flex-shrink: 0; - align-items: center; - height: $stage-menu-height; - padding: $space; -} - .extension-button-container { width: 3.25rem; height: 3.25rem; diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 349e20d5e36b533c2a712f3d55e1b594e512e77e..639c8cbd30c12432512ba5886bc92f1931b02cba 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -112,13 +112,7 @@ const GUIComponent = props => { <Box className={styles.stageAndTargetWrapper}> <Box className={styles.stageMenuWrapper}> - <MediaQuery minWidth={layout.fullSizeMinWidth}>{isFullSize => ( - <StageHeader - height={isFullSize ? layout.fullStageHeight : layout.smallerStageHeight} - vm={vm} - width={isFullSize ? layout.fullStageWidth : layout.smallerStageWidth} - /> - )}</MediaQuery> + <StageHeader vm={vm} /> </Box> <Box className={styles.stageWrapper}> <MediaQuery minWidth={layout.fullSizeMinWidth}>{isFullSize => ( diff --git a/src/components/language-selector/language-icon.svg b/src/components/language-selector/language-icon.svg index 3644ab6fde2247b0cd949231071e98bc95638a10..42bd409ab18e8ed0e737e6547c7ecc89ea57df0d 100644 Binary files a/src/components/language-selector/language-icon.svg and b/src/components/language-selector/language-icon.svg differ diff --git a/src/components/language-selector/language-selector.css b/src/components/language-selector/language-selector.css index a060131a8fc2d2c8aa6afa58629622e3a141320f..0ae9e0a1028ec95ccd9e9896b57721e439e57e06 100644 --- a/src/components/language-selector/language-selector.css +++ b/src/components/language-selector/language-selector.css @@ -1,3 +1,4 @@ +@import "../../css/colors.css"; @import "../../css/units.css"; .group { @@ -9,7 +10,7 @@ .language-icon { height: 2rem; - display:none; + display: none; } .language-select { @@ -19,7 +20,7 @@ user-select: none; outline: none; background: rgba(255, 255, 255, 0.5); - color: #3373cc; + color: $motion-tertiary; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } diff --git a/src/components/language-selector/language-selector.jsx b/src/components/language-selector/language-selector.jsx index d3d3bb48daf7251528d48c30d014ebf8bb5e1a04..db846fef5953fa54c3f4847808b21735b60f2807 100644 --- a/src/components/language-selector/language-selector.jsx +++ b/src/components/language-selector/language-selector.jsx @@ -2,6 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import Box from '../box/box.jsx'; +import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx'; import locales from 'scratch-l10n'; import languageIcon from './language-icon.svg'; import styles from './language-selector.css'; @@ -12,27 +13,33 @@ const LanguageSelector = ({ ...props }) => ( <Box {...props}> - <div className={styles.group}> - <img - className={styles.languageIcon} - src={languageIcon} - /> - <select - aria-label="language selector" - className={styles.languageSelect} - value={currentLocale} - onChange={onChange} - > - {Object.keys(locales).map(locale => ( - <option - key={locale} - value={locale} - > - {locales[locale].name} - </option> - ))} - </select> - </div> + <ComingSoonTooltip + place="bottom" + tooltipId="language-selector" + > + <div className={styles.group}> + <img + className={styles.languageIcon} + src={languageIcon} + /> + <select + disabled + aria-label="language selector" + className={styles.languageSelect} + value={currentLocale} + onChange={onChange} + > + {Object.keys(locales).map(locale => ( + <option + key={locale} + value={locale} + > + {locales[locale].name} + </option> + ))} + </select> + </div> + </ComingSoonTooltip> </Box> ); diff --git a/src/components/load-button/load-button.jsx b/src/components/load-button/load-button.jsx index dd94a2a11358ba0726888c7ed77550b6511acaa5..0f580278cdf7659ba819c1e345d7b220ceed013b 100644 --- a/src/components/load-button/load-button.jsx +++ b/src/components/load-button/load-button.jsx @@ -2,6 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import ButtonComponent from '../button/button.jsx'; +import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx'; import styles from './load-button.css'; @@ -13,13 +14,24 @@ const LoadButtonComponent = ({ ...props }) => ( <span {...props}> - <ButtonComponent onClick={onClick}>{title}</ButtonComponent> - <input - className={styles.fileInput} - ref={inputRef} - type="file" - onChange={onChange} - /> + <ComingSoonTooltip + place="bottom" + tooltipId="load-button" + > + <ButtonComponent + disabled + onClick={onClick} + > + {title} + </ButtonComponent> + <input + disabled + className={styles.fileInput} + ref={inputRef} + type="file" + onChange={onChange} + /> + </ComingSoonTooltip> </span> ); diff --git a/src/components/menu-bar/menu-bar.css b/src/components/menu-bar/menu-bar.css index a9d3b37fa7db7847cb0eba2ebfde769b8c013ff6..a85926d39a914a662f81bd263fefbe265d5b6363 100644 --- a/src/components/menu-bar/menu-bar.css +++ b/src/components/menu-bar/menu-bar.css @@ -1,3 +1,4 @@ +@import "../../css/colors.css"; @import "../../css/units.css"; .menu-bar { @@ -23,7 +24,7 @@ width: 100%; */ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - background-color: #4c97ff; + background-color: $motion-primary; color: white; } @@ -56,11 +57,6 @@ line-height: $menu-bar-height; cursor: pointer; text-decoration: none; - color: white; + color: $motion-tertiary; user-select: none; } - -.menu-item:hover { - opacity: 0.8; - background: rgba(255, 255, 255, 0.2) -} diff --git a/src/components/prompt/icon--dropdown-caret.svg b/src/components/prompt/icon--dropdown-caret.svg new file mode 100644 index 0000000000000000000000000000000000000000..8e08eda35e5cb30d80ec7d97a21352dbe6098f7a Binary files /dev/null and b/src/components/prompt/icon--dropdown-caret.svg differ diff --git a/src/components/prompt/prompt.css b/src/components/prompt/prompt.css index 94b907c04500fdbf5f4e2b067b7579afebb62b05..5279012337b3f363715f017d8608346c3712e8fd 100644 --- a/src/components/prompt/prompt.css +++ b/src/components/prompt/prompt.css @@ -49,3 +49,29 @@ .button-row button + button { margin-left: 0.5rem; } + +.more-options { + border-top: 1px dashed hsla(0, 0%, 0%, .25); + overflow: visible; + padding: 1rem; + text-align: center; + margin: 0 0 1rem; +} + +.more-options-accordion { + width: 60%; + margin: 0 auto; +} + +.more-options-text { + opacity: .5; +} + +.more-options-icon { + width: .75rem; + height: .75rem; + margin-left: .5rem; + vertical-align: middle; + padding-bottom: .2rem; + opacity: .5; +} diff --git a/src/components/prompt/prompt.jsx b/src/components/prompt/prompt.jsx index 4fca4cb4a6c476350bad74c8ad43c60603bd48fd..b99580b797206045b8e23949d3fe4e630fd7332f 100644 --- a/src/components/prompt/prompt.jsx +++ b/src/components/prompt/prompt.jsx @@ -1,10 +1,23 @@ +import {defineMessages, FormattedMessage} from 'react-intl'; import PropTypes from 'prop-types'; import React from 'react'; -import Modal from '../modal/modal.jsx'; + import Box from '../box/box.jsx'; +import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx'; +import Modal from '../modal/modal.jsx'; import styles from './prompt.css'; +import dropdownIcon from './icon--dropdown-caret.svg'; + +const messages = defineMessages({ + moreOptionsMessage: { + defaultMessage: 'More Options', + description: 'Dropdown message for variable/list options', + id: 'gui.gui.variablePrompt' + } +}); + const PromptComponent = props => ( <Modal className={styles.modalContent} @@ -24,6 +37,23 @@ const PromptComponent = props => ( onKeyPress={props.onKeyPress} /> </Box> + <Box className={styles.moreOptions}> + <ComingSoonTooltip + className={styles.moreOptionsAccordion} + place="right" + tooltipId="variable-options-accordion" + > + <div className={styles.moreOptionsText}> + <FormattedMessage + {...messages.moreOptionsMessage} + /> + <img + className={styles.moreOptionsIcon} + src={dropdownIcon} + /> + </div> + </ComingSoonTooltip> + </Box> <Box className={styles.buttonRow}> <button className={styles.cancelButton} diff --git a/src/components/stage-header/icon--fullscreen.svg b/src/components/stage-header/icon--fullscreen.svg new file mode 100644 index 0000000000000000000000000000000000000000..9d26e43918902d57eaa8c1719ee4c133877d85f9 Binary files /dev/null and b/src/components/stage-header/icon--fullscreen.svg differ diff --git a/src/components/stage-header/icon--large-stage.svg b/src/components/stage-header/icon--large-stage.svg new file mode 100644 index 0000000000000000000000000000000000000000..ed4361ca3ac88804e87a1531ea79c7fc53201721 Binary files /dev/null and b/src/components/stage-header/icon--large-stage.svg differ diff --git a/src/components/stage-header/icon--small-stage.svg b/src/components/stage-header/icon--small-stage.svg new file mode 100644 index 0000000000000000000000000000000000000000..f73f5b242583675de267ad84dde99a7173a7e032 Binary files /dev/null and b/src/components/stage-header/icon--small-stage.svg differ diff --git a/src/components/stage-header/icon--unfullscreen.svg b/src/components/stage-header/icon--unfullscreen.svg new file mode 100644 index 0000000000000000000000000000000000000000..bf4a3e450de8aa70b034203d69824860d568f1c0 Binary files /dev/null and b/src/components/stage-header/icon--unfullscreen.svg differ diff --git a/src/components/stage-header/icon--unzoom.svg b/src/components/stage-header/icon--unzoom.svg deleted file mode 100644 index c929966dc735c3c1aeaadc4c10fdfa82306596f6..0000000000000000000000000000000000000000 Binary files a/src/components/stage-header/icon--unzoom.svg and /dev/null differ diff --git a/src/components/stage-header/icon--zoom.svg b/src/components/stage-header/icon--zoom.svg deleted file mode 100644 index 62e008983d41b35d568610d17287c2854e2b7ca7..0000000000000000000000000000000000000000 Binary files a/src/components/stage-header/icon--zoom.svg and /dev/null differ diff --git a/src/components/stage-header/stage-header.css b/src/components/stage-header/stage-header.css index c491b82e984982be1d118a92ac8399f1628763ac..2c2c30bede76add659c963f1878ab79e4ce51155 100644 --- a/src/components/stage-header/stage-header.css +++ b/src/components/stage-header/stage-header.css @@ -23,21 +23,48 @@ padding: $space; } -.stage-zoom-icon { - text-align: right; +.stage-size-row { + display: flex; +} + +.stage-size-toggle-group { + display: flex; + margin-right: .2rem; +} + +.stage-button { + display: block; + border: 1px solid $ui-pane-border; + border-radius: .25rem; box-sizing: content-box; width: 1.25rem; height: 1.25rem; padding: 0.375rem; - border-radius: 0.25rem; user-select: none; cursor: pointer; - transition: 0.2s ease-out; } -.stage-zoom-icon:hover { - /* Scale icon image by 1.2, but keep background static */ - width: 1.5rem; - height: 1.5rem; - padding: 0.25rem; +.stage-button-icon { + width: 100%; + height: 100%; +} + +.stage-button-right { + border-left: none; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.stage-button-left { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.stage-button-active { + background: #FFF; +} + +.stage-button-disabled { + opacity: .5; + cursor: auto; } diff --git a/src/components/stage-header/stage-header.jsx b/src/components/stage-header/stage-header.jsx index 635d161ba1ff092f49efb091446f90cdceaeec47..19f46e220c4f99bbec44ac044c9a61805240d870 100644 --- a/src/components/stage-header/stage-header.jsx +++ b/src/components/stage-header/stage-header.jsx @@ -1,81 +1,157 @@ import classNames from 'classnames'; +import {defineMessages, injectIntl, intlShape} from 'react-intl'; import PropTypes from 'prop-types'; import React from 'react'; import VM from 'scratch-vm'; + import Box from '../box/box.jsx'; +import Button from '../button/button.jsx'; +import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx'; import Controls from '../../containers/controls.jsx'; -import zoomIcon from './icon--zoom.svg'; -import unzoomIcon from './icon--unzoom.svg'; +import {STAGE_SIZES} from '../../reducers/stage-size'; + +import fullScreenIcon from './icon--fullscreen.svg'; +import largeStageIcon from './icon--large-stage.svg'; +import smallStageIcon from './icon--small-stage.svg'; +import unFullScreenIcon from './icon--unfullscreen.svg'; + import styles from './stage-header.css'; +const messages = defineMessages({ + largeStageSizeMessage: { + defaultMessage: 'Stage Size Toggle - Large', + description: 'Button to change stage size to large', + id: 'gui.gui.stageSizeLarge' + }, + smallStageSizeMessage: { + defaultMessage: 'Stage Size Toggle - Small', + description: 'Button to change stage size to small', + id: 'gui.gui.stageSizeSmall' + }, + fullStageSizeMessage: { + defaultMessage: 'Stage Size Toggle - Full Screen', + description: 'Button to change stage size to full screen', + id: 'gui.gui.stageSizeFull' + }, + unFullStageSizeMessage: { + defaultMessage: 'Stage Size Toggle - Un-full screen', + description: 'Button to get out of full screen mode', + id: 'gui.gui.stageSizeUnFull' + } +}); + const StageHeaderComponent = function (props) { const { - className, - height, - isZoomed, - onUnzoom, - onZoom, - titleZoomIcon, - vm, - width, - ...componentProps + stageSize, + isFullScreen, + onSetStageLarge, + onSetStageFull, + onSetStageUnFull, + vm } = props; - return isZoomed === false ? ( - <Box className={styles.stageHeaderWrapper}> - <Box - className={styles.stageMenuWrapper} - height={height} - width={width} - > - <Controls vm={vm} /> - <img - className={classNames( - className, - styles.stageZoomIcon - )} - src={zoomIcon} - title={titleZoomIcon} - onClick={onZoom} - {...componentProps} - /> + + let header = null; + + if (isFullScreen) { + header = ( + <Box className={styles.stageHeaderWrapperOverlay}> + <Box className={styles.stageMenuWrapper}> + <Controls vm={vm} /> + <Button + className={classNames( + styles.stageButton, + styles.stageButtonActive + )} + onClick={onSetStageUnFull} + > + <img + alt={props.intl.formatMessage(messages.unFullStageSizeMessage)} + className={styles.stageButtonIcon} + src={unFullScreenIcon} + title="Full Screen Control" + /> + </Button> + </Box> </Box> - </Box> - ) : ( - <Box className={styles.stageHeaderWrapperOverlay}> - <Box - className={styles.stageMenuWrapper} - height={'100%'} - width={'100%'} - > - <Controls vm={vm} /> - <img - className={classNames( - className, - styles.stageZoomIcon - )} - src={unzoomIcon} - title={titleZoomIcon} - onClick={onUnzoom} - {...componentProps} - /> + ); + } else { + header = ( + <Box className={styles.stageHeaderWrapper}> + <Box className={styles.stageMenuWrapper}> + <Controls vm={vm} /> + <div className={styles.stageSizeRow}> + <div className={styles.stageSizeToggleGroup}> + <ComingSoonTooltip + place="left" + tooltipId="small-stage-button" + > + <div + disabled + className={classNames( + styles.stageButton, + styles.stageButtonLeft, + styles.stageButtonDisabled + )} + role="button" + > + <img + disabled + alt={props.intl.formatMessage(messages.smallStageSizeMessage)} + className={styles.stageButtonIcon} + src={smallStageIcon} + /> + </div> + </ComingSoonTooltip> + <div> + <Button + className={classNames( + styles.stageButton, + styles.stageButtonRight, + { + [styles.stageButtonActive]: stageSize === STAGE_SIZES.large + } + )} + onClick={onSetStageLarge} + > + <img + alt={props.intl.formatMessage(messages.largeStageSizeMessage)} + className={styles.stageButtonIcon} + src={largeStageIcon} + /> + </Button> + </div> + </div> + <div> + <Button + className={styles.stageButton} + onClick={onSetStageFull} + > + <img + alt={props.intl.formatMessage(messages.fullStageSizeMessage)} + className={styles.stageButtonIcon} + src={fullScreenIcon} + title="Full Screen Control" + /> + </Button> + </div> + </div> + </Box> </Box> - </Box> - ); + ); + } + + return header; }; + StageHeaderComponent.propTypes = { - className: PropTypes.string, - height: PropTypes.number, - isZoomed: PropTypes.bool.isRequired, - onUnzoom: PropTypes.func.isRequired, - onZoom: PropTypes.func.isRequired, - titleZoomIcon: PropTypes.string, - vm: PropTypes.instanceOf(VM).isRequired, - width: PropTypes.number + intl: intlShape, + isFullScreen: PropTypes.bool.isRequired, + onSetStageFull: PropTypes.func.isRequired, + onSetStageLarge: PropTypes.func.isRequired, + onSetStageUnFull: PropTypes.func.isRequired, + stageSize: PropTypes.oneOf(Object.keys(STAGE_SIZES)).isRequired, + vm: PropTypes.instanceOf(VM).isRequired }; -StageHeaderComponent.defaultProps = { - width: 480, - height: 360, - titleZoomIcon: 'Zoom Control' -}; -export default StageHeaderComponent; + +export default injectIntl(StageHeaderComponent); diff --git a/src/components/stage/stage.jsx b/src/components/stage/stage.jsx index 47981e04ab0f44cbd416212420be64e64aeac819..c8777a263d72f8458c818a3b798f23a864d4fc32 100644 --- a/src/components/stage/stage.jsx +++ b/src/components/stage/stage.jsx @@ -13,7 +13,7 @@ const StageComponent = props => { canvasRef, height, isColorPicking, - isZoomed, + isFullScreen, width, colorInfo, onDeactivateColorPicker, @@ -26,7 +26,7 @@ const StageComponent = props => { let widthCorrectedAspect = width; const spacingBorderAdjustment = 9; const stageMenuHeightAdjustment = 40; - if (isZoomed) { + if (isFullScreen) { heightCorrectedAspect = window.innerHeight - stageMenuHeightAdjustment - spacingBorderAdjustment; widthCorrectedAspect = heightCorrectedAspect + (heightCorrectedAspect / 3); if (widthCorrectedAspect > window.innerWidth) { @@ -38,15 +38,15 @@ const StageComponent = props => { <div> <Box className={classNames({ - [styles.stageWrapper]: !isZoomed, - [styles.stageWrapperOverlay]: isZoomed, - [styles.withColorPicker]: !isZoomed && isColorPicking + [styles.stageWrapper]: !isFullScreen, + [styles.stageWrapperOverlay]: isFullScreen, + [styles.withColorPicker]: !isFullScreen && isColorPicking })} > <Box className={classNames( styles.stage, - {[styles.stageOverlayContent]: isZoomed} + {[styles.stageOverlayContent]: isFullScreen} )} componentRef={canvasRef} element="canvas" @@ -95,7 +95,7 @@ StageComponent.propTypes = { colorInfo: Loupe.propTypes.colorInfo, height: PropTypes.number, isColorPicking: PropTypes.bool, - isZoomed: PropTypes.bool.isRequired, + isFullScreen: PropTypes.bool.isRequired, onDeactivateColorPicker: PropTypes.func, onQuestionAnswered: PropTypes.func, question: PropTypes.string, diff --git a/src/containers/save-button.jsx b/src/containers/save-button.jsx index 12585ab70a2b3a3d4c63ad7017a61ca3d7d213d9..f2ae497def0837fcf6eb0b888f3b84933d95039c 100644 --- a/src/containers/save-button.jsx +++ b/src/containers/save-button.jsx @@ -4,6 +4,8 @@ import React from 'react'; import {connect} from 'react-redux'; import ButtonComponent from '../components/button/button.jsx'; +import {ComingSoonTooltip} from '../components/coming-soon/coming-soon.jsx'; + class SaveButton extends React.Component { constructor (props) { @@ -38,12 +40,18 @@ class SaveButton extends React.Component { ...props } = this.props; return ( - <ButtonComponent - onClick={this.handleClick} - {...props} + <ComingSoonTooltip + place="bottom" + tooltipId="save-button" > - Save - </ButtonComponent> + <ButtonComponent + disabled + onClick={this.handleClick} + {...props} + > + Save + </ButtonComponent> + </ComingSoonTooltip> ); } } diff --git a/src/containers/stage-header.jsx b/src/containers/stage-header.jsx index d3acba94ee1501098034a2eda728f49866e9d00b..593851a4104f95f65793129d983a9a3bc920fbaa 100644 --- a/src/containers/stage-header.jsx +++ b/src/containers/stage-header.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import VM from 'scratch-vm'; -import {setZoomed} from '../reducers/zoom'; +import {setStageSize, setFullScreen, STAGE_SIZES} from '../reducers/stage-size'; import {connect} from 'react-redux'; @@ -22,19 +22,20 @@ class StageHeader extends React.Component { } StageHeader.propTypes = { - height: PropTypes.number, - isZoomed: PropTypes.bool, - vm: PropTypes.instanceOf(VM).isRequired, - width: PropTypes.number + stageSize: PropTypes.oneOf(Object.keys(STAGE_SIZES)), + vm: PropTypes.instanceOf(VM).isRequired }; const mapStateToProps = state => ({ - isZoomed: state.isZoomed + stageSize: state.stageSize.stageSize, + isFullScreen: state.stageSize.isFullScreen }); const mapDispatchToProps = dispatch => ({ - onZoom: () => dispatch(setZoomed(true)), - onUnzoom: () => dispatch(setZoomed(false)) + onSetStageLarge: () => dispatch(setStageSize(STAGE_SIZES.large)), + onSetStageSmall: () => dispatch(setStageSize(STAGE_SIZES.small)), + onSetStageFull: () => dispatch(setFullScreen(true)), + onSetStageUnFull: () => dispatch(setFullScreen(false)) }); export default connect( diff --git a/src/containers/stage.jsx b/src/containers/stage.jsx index 9f76aa7ab10c5193240f6b7a5b9bac1e5bd1769d..cb2a38963ec090b32ade16e0ec18e1e30855ac56 100644 --- a/src/containers/stage.jsx +++ b/src/containers/stage.jsx @@ -57,7 +57,7 @@ class Stage extends React.Component { this.props.height !== nextProps.height || this.props.isColorPicking !== nextProps.isColorPicking || this.state.colorInfo !== nextState.colorInfo || - this.props.isZoomed !== nextProps.isZoomed || + this.props.isFullScreen !== nextProps.isFullScreen || this.state.question !== nextState.question; } componentDidUpdate (prevProps) { @@ -279,7 +279,7 @@ class Stage extends React.Component { Stage.propTypes = { height: PropTypes.number, isColorPicking: PropTypes.bool, - isZoomed: PropTypes.bool, + isFullScreen: PropTypes.bool.isRequired, onActivateColorPicker: PropTypes.func, onDeactivateColorPicker: PropTypes.func, vm: PropTypes.instanceOf(VM).isRequired, @@ -288,7 +288,7 @@ Stage.propTypes = { const mapStateToProps = state => ({ isColorPicking: state.colorPicker.active, - isZoomed: state.isZoomed + isFullScreen: state.stageSize.isFullScreen }); const mapDispatchToProps = dispatch => ({ diff --git a/src/css/colors.css b/src/css/colors.css index d524795cd5ece9550b0aee9767111e60d43c2f91..e270217f9a595bf09c26b66f1cafd5ca25dba308 100644 --- a/src/css/colors.css +++ b/src/css/colors.css @@ -16,4 +16,6 @@ $sound-tertiary: #A63FA6; $control-primary: #FFAB19; +$data-primary: #FF8C1A; + $form-border: #E9EEF2; diff --git a/src/reducers/gui.js b/src/reducers/gui.js index 83ede0203093375c2515d7b613b93d8056240a26..d49144b4f059e66bb16a4b374783c2724ce73d34 100644 --- a/src/reducers/gui.js +++ b/src/reducers/gui.js @@ -8,14 +8,14 @@ import monitorLayoutReducer from './monitor-layout'; import targetReducer from './targets'; import toolboxReducer from './toolbox'; import vmReducer from './vm'; -import zoomReducer from './zoom'; +import stageSizeReducer from './stage-size'; import {ScratchPaintReducer} from 'scratch-paint'; export default combineReducers({ colorPicker: colorPickerReducer, customProcedures: customProceduresReducer, intl: intlReducer, - isZoomed: zoomReducer, + stageSize: stageSizeReducer, modals: modalReducer, monitors: monitorReducer, monitorLayout: monitorLayoutReducer, diff --git a/src/reducers/stage-size.js b/src/reducers/stage-size.js new file mode 100644 index 0000000000000000000000000000000000000000..ea2750e3409917e35cad5cdc4dd1562ca98a0c98 --- /dev/null +++ b/src/reducers/stage-size.js @@ -0,0 +1,55 @@ +const SET_STAGE_SIZE = 'scratch-gui/StageSize/SET_STAGE_SIZE'; +const SET_FULL_SCREEN = 'scratch-gui/StageSize/SET_FULL_SCREEN'; + +const initialState = { + isFullScreen: false, + stageSize: 'large' +}; + +// stage size constants +const STAGE_SIZES = { + small: 'small', + large: 'large' +}; + +const reducer = function (state, action) { + if (typeof state === 'undefined') state = initialState; + switch (action.type) { + case SET_STAGE_SIZE: + return { + isFullScreen: state.isFullScreen, + stageSize: action.stageSize + }; + case SET_FULL_SCREEN: + return { + isFullScreen: action.isFullScreen, + stageSize: state.stageSize + }; + default: + return state; + } +}; + +const setStageSize = function (stageSize) { + return { + type: SET_STAGE_SIZE, + stageSize: stageSize + }; +}; + +// `isFullScreen` is a separate value because "stage size" does not +// actually apply to full screen mode, so they are treated as separate +// values to be assessed. +const setFullScreen = function (isFullScreen) { + return { + type: SET_FULL_SCREEN, + isFullScreen: isFullScreen + }; +}; + +export { + reducer as default, + setStageSize, + setFullScreen, + STAGE_SIZES +}; diff --git a/src/reducers/zoom.js b/src/reducers/zoom.js deleted file mode 100644 index 3cd0f5f7fa091fb4ead412bf024f0aa2fcf9c445..0000000000000000000000000000000000000000 --- a/src/reducers/zoom.js +++ /dev/null @@ -1,23 +0,0 @@ -const SET_ZOOMED = 'scratch-gui/Zoomed/SET_ZOOMED'; -const defaultZoomed = false; -const initialState = defaultZoomed; - -const reducer = function (state, action) { - if (typeof state === 'undefined') state = initialState; - switch (action.type) { - case SET_ZOOMED: - return action.isZoomed; - default: - return state; - } -}; -const setZoomed = function (isZoomed) { - return { - type: SET_ZOOMED, - isZoomed: isZoomed - }; -}; -export { - reducer as default, - setZoomed -}; diff --git a/test/integration/test.js b/test/integration/test.js index 58b488ecda88325b1991647c3c3ce3dafbae5973..8668fce2a5ae4e4b28b66ee636aa5d84b4d52e95 100644 --- a/test/integration/test.js +++ b/test/integration/test.js @@ -143,7 +143,7 @@ describe('costumes, sounds and variables', () => { const projectId = '96708228'; await loadUri(`${uri}#${projectId}`); await new Promise(resolve => setTimeout(resolve, 2000)); - await clickXpath('//img[@title="Zoom Control"]'); + await clickXpath('//img[@title="Full Screen Control"]'); await clickXpath('//img[@title="Go"]'); await new Promise(resolve => setTimeout(resolve, 2000)); await clickXpath('//img[@title="Stop"]'); @@ -235,7 +235,9 @@ describe('costumes, sounds and variables', () => { await expect(logs).toEqual([]); }); - test('Localization', async () => { + // Skipped temporarily while the language selector is marked as + // "Coming Soon" + test.skip('Localization', async () => { await loadUri(uri); await clickText('Blocks'); await clickText('Extensions');