-
Karishma Chadha authoredKarishma Chadha authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
menu-bar.jsx 21.56 KiB
import classNames from 'classnames';
import {connect} from 'react-redux';
import {defineMessages, FormattedMessage, injectIntl, intlShape} from 'react-intl';
import PropTypes from 'prop-types';
import bindAll from 'lodash.bindall';
import React from 'react';
import Box from '../box/box.jsx';
import Button from '../button/button.jsx';
import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx';
import Divider from '../divider/divider.jsx';
import LanguageSelector from '../../containers/language-selector.jsx';
import ProjectLoader from '../../containers/project-loader.jsx';
import Menu from '../../containers/menu.jsx';
import {MenuItem, MenuSection} from '../menu/menu.jsx';
import ProjectSaver from '../../containers/project-saver.jsx';
import DeletionRestorer from '../../containers/deletion-restorer.jsx';
import TurboMode from '../../containers/turbo-mode.jsx';
import {openTipsLibrary} from '../../reducers/modals';
import {setPlayer} from '../../reducers/mode';
import {
openFileMenu,
closeFileMenu,
fileMenuOpen,
openEditMenu,
closeEditMenu,
editMenuOpen,
openLanguageMenu,
closeLanguageMenu,
languageMenuOpen
} from '../../reducers/menus';
import styles from './menu-bar.css';
import helpIcon from '../../lib/assets/icon--tutorials.svg';
import mystuffIcon from './icon--mystuff.png';
import feedbackIcon from './icon--feedback.svg';
import profileIcon from './icon--profile.png';
import communityIcon from './icon--see-community.svg';
import dropdownCaret from '../language-selector/dropdown-caret.svg';
import languageIcon from '../language-selector/language-icon.svg';
import scratchLogo from './scratch-logo.svg';
const ariaMessages = defineMessages({
language: {
id: 'gui.menuBar.LanguageSelector',
defaultMessage: 'language selector',
description: 'accessibility text for the language selection menu'
},
tutorials: {
id: 'gui.menuBar.tutorialsLibrary',
defaultMessage: 'Tutorials',
description: 'accessibility text for the tutorials button'
}
});
const MenuBarItemTooltip = ({
children,
className,
enable,
id,
place = 'bottom'
}) => {
if (enable) {
return (
<React.Fragment>
{children}
</React.Fragment>
);
}
return (
<ComingSoonTooltip
className={classNames(styles.comingSoon, className)}
place={place}
tooltipClassName={styles.comingSoonTooltip}
tooltipId={id}
>
{children}
</ComingSoonTooltip>
);
};
MenuBarItemTooltip.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
enable: PropTypes.bool,
id: PropTypes.string,
place: PropTypes.oneOf(['top', 'bottom', 'left', 'right'])
};
const MenuItemTooltip = ({id, isRtl, children, className}) => (
<ComingSoonTooltip
className={classNames(styles.comingSoon, className)}
isRtl={isRtl}
place={isRtl ? 'left' : 'right'}
tooltipClassName={styles.comingSoonTooltip}
tooltipId={id}
>
{children}
</ComingSoonTooltip>
);
MenuItemTooltip.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
id: PropTypes.string,
isRtl: PropTypes.bool
};
const MenuBarMenu = ({
children,
onRequestClose,
open,
place = 'right'
}) => (
<Menu
className={styles.menu}
open={open}
place={place}
onRequestClose={onRequestClose}
>
{children}
</Menu>
);
MenuBarMenu.propTypes = {
children: PropTypes.node,
onRequestClose: PropTypes.func,
open: PropTypes.bool,
place: PropTypes.oneOf(['left', 'right'])
};
class MenuBar extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleLanguageMouseUp',
'handleRestoreOption',
'restoreOptionMessage'
]);
}
handleLanguageMouseUp (e) {
if (!this.props.languageMenuOpen) {
this.props.onClickLanguage(e);
}
}
handleRestoreOption (restoreFun) {
return () => {
restoreFun();
this.props.onRequestCloseEdit();
};
}
restoreOptionMessage (deletedItem) {
switch (deletedItem) {
case 'Sprite':
return (<FormattedMessage
defaultMessage="Restore Sprite"
description="Menu bar item for restoring the last deleted sprite."
id="gui.menuBar.restoreSprite"
/>);
case 'Sound':
return (<FormattedMessage
defaultMessage="Restore Sound"
description="Menu bar item for restoring the last deleted sound."
id="gui.menuBar.restoreSound"
/>);
case 'Costume':
return (<FormattedMessage
defaultMessage="Restore Costume"
description="Menu bar item for restoring the last deleted costume."
id="gui.menuBar.restoreCostume"
/>);
default: {
return (<FormattedMessage
defaultMessage="Restore"
description="Menu bar item for restoring the last deleted item in its disabled state." /* eslint-disable-line max-len */
id="gui.menuBar.restore"
/>);
}
}
}
render () {
return (
<Box className={styles.menuBar}>
<div className={styles.mainMenu}>
<div className={styles.fileGroup}>
<div className={classNames(styles.menuBarItem)}>
<img
alt="Scratch"
className={styles.scratchLogo}
draggable={false}
src={scratchLogo}
/>
</div>
<div
className={classNames(styles.menuBarItem, styles.hoverable, styles.languageMenu)}
>
<div>
<img
className={styles.languageIcon}
src={languageIcon}
/>
<img
className={styles.languageCaret}
src={dropdownCaret}
/>
</div>
<LanguageSelector label={this.props.intl.formatMessage(ariaMessages.language)} />
</div>
<div
className={classNames(styles.menuBarItem, styles.hoverable, {
[styles.active]: this.props.fileMenuOpen
})}
onMouseUp={this.props.onClickFile}
>
<div className={classNames(styles.fileMenu)}>
<FormattedMessage
defaultMessage="File"
description="Text for file dropdown menu"
id="gui.menuBar.file"
/>
</div>
<MenuBarMenu
open={this.props.fileMenuOpen}
place={this.props.isRtl ? 'left' : 'right'}
onRequestClose={this.props.onRequestCloseFile}
>
<MenuItemTooltip
id="new"
isRtl={this.props.isRtl}
>
<MenuItem>
<FormattedMessage
defaultMessage="New"
description="Menu bar item for creating a new project"
id="gui.menuBar.new"
/>
</MenuItem>
</MenuItemTooltip>
<MenuSection>
<MenuItemTooltip
id="save"
isRtl={this.props.isRtl}
>
<MenuItem>
<FormattedMessage
defaultMessage="Save now"
description="Menu bar item for saving now"
id="gui.menuBar.saveNow"
/>
</MenuItem>
</MenuItemTooltip>
<MenuItemTooltip
id="copy"
isRtl={this.props.isRtl}
>
<MenuItem>
<FormattedMessage
defaultMessage="Save as a copy"
description="Menu bar item for saving as a copy"
id="gui.menuBar.saveAsCopy"
/></MenuItem>
</MenuItemTooltip>
</MenuSection>
<MenuSection>
<ProjectLoader>{(renderFileInput, loadProject, loadProps) => (
<MenuItem
onClick={loadProject}
{...loadProps}
>
<FormattedMessage
defaultMessage="Load from your computer"
description="Menu bar item for uploading a project from your computer"
id="gui.menuBar.uploadFromComputer"
/>
{renderFileInput()}
</MenuItem>
)}</ProjectLoader>
<ProjectSaver>{(saveProject, saveProps) => (
<MenuItem
onClick={saveProject}
{...saveProps}
>
<FormattedMessage
defaultMessage="Save to your computer"
description="Menu bar item for downloading a project to your computer"
id="gui.menuBar.downloadToComputer"
/>
</MenuItem>
)}</ProjectSaver>
</MenuSection>
</MenuBarMenu>
</div>
<div
className={classNames(styles.menuBarItem, styles.hoverable, {
[styles.active]: this.props.editMenuOpen
})}
onMouseUp={this.props.onClickEdit}
>
<div className={classNames(styles.editMenu)}>
<FormattedMessage
defaultMessage="Edit"
description="Text for edit dropdown menu"
id="gui.menuBar.edit"
/>
</div>
<MenuBarMenu
open={this.props.editMenuOpen}
place={this.props.isRtl ? 'left' : 'right'}
onRequestClose={this.props.onRequestCloseEdit}
>
<DeletionRestorer>{(handleRestore, {restorable, deletedItem}) => (
<MenuItem
className={classNames({[styles.disabled]: !restorable})}
onClick={this.handleRestoreOption(handleRestore)}
>
{this.restoreOptionMessage(deletedItem)}
</MenuItem>
)}</DeletionRestorer>
<MenuSection>
<TurboMode>{(toggleTurboMode, {turboMode}) => (
<MenuItem onClick={toggleTurboMode}>
{turboMode ? (
<FormattedMessage
defaultMessage="Turn off Turbo Mode"
description="Menu bar item for turning off turbo mode"
id="gui.menuBar.turboModeOff"
/>
) : (
<FormattedMessage
defaultMessage="Turn on Turbo Mode"
description="Menu bar item for turning on turbo mode"
id="gui.menuBar.turboModeOn"
/>
)}
</MenuItem>
)}</TurboMode>
</MenuSection>
</MenuBarMenu>
</div>
</div>
<Divider className={classNames(styles.divider)} />
<div
aria-label={this.props.intl.formatMessage(ariaMessages.tutorials)}
className={classNames(styles.menuBarItem, styles.hoverable)}
onClick={this.props.onOpenTipLibrary}
>
<img
className={styles.helpIcon}
src={helpIcon}
/>
<FormattedMessage {...ariaMessages.tutorials} />
</div>
<Divider className={classNames(styles.divider)} />
<div className={classNames(styles.menuBarItem)}>
<MenuBarItemTooltip id="title-field">
<input
disabled
className={classNames(styles.titleField)}
placeholder="Untitled-1"
/>
</MenuBarItemTooltip>
</div>
<div className={classNames(styles.menuBarItem)}>
<MenuBarItemTooltip id="share-button">
<Button className={classNames(styles.shareButton)}>
<FormattedMessage
defaultMessage="Share"
description="Label for project share button"
id="gui.menuBar.share"
/>
</Button>
</MenuBarItemTooltip>
</div>
<div className={classNames(styles.menuBarItem, styles.communityButtonWrapper)}>
{this.props.enableCommunity ?
<Button
className={classNames(styles.communityButton)}
iconClassName={styles.communityButtonIcon}
iconSrc={communityIcon}
onClick={this.props.onSeeCommunity}
>
<FormattedMessage
defaultMessage="See Community"
description="Label for see community button"
id="gui.menuBar.seeCommunity"
/>
</Button> :
<MenuBarItemTooltip id="community-button">
<Button
className={classNames(styles.communityButton)}
iconClassName={styles.communityButtonIcon}
iconSrc={communityIcon}
>
<FormattedMessage
defaultMessage="See Community"
description="Label for see community button"
id="gui.menuBar.seeCommunity"
/>
</Button>
</MenuBarItemTooltip>
}
</div>
</div>
<div className={classNames(styles.menuBarItem, styles.feedbackButtonWrapper)}>
<a
className={styles.feedbackLink}
href="https://scratch.mit.edu/discuss/topic/312261/"
rel="noopener noreferrer"
target="_blank"
>
<Button
className={styles.feedbackButton}
iconSrc={feedbackIcon}
>
<FormattedMessage
defaultMessage="Give Feedback"
description="Label for feedback form modal button"
id="gui.menuBar.giveFeedback"
/>
</Button>
</a>
</div>
<div className={styles.accountInfoWrapper}>
<MenuBarItemTooltip id="mystuff">
<div
className={classNames(
styles.menuBarItem,
styles.hoverable,
styles.mystuffButton
)}
>
<img
className={styles.mystuffIcon}
src={mystuffIcon}
/>
</div>
</MenuBarItemTooltip>
<MenuBarItemTooltip
id="account-nav"
place={this.props.isRtl ? 'right' : 'left'}
>
<div
className={classNames(
styles.menuBarItem,
styles.hoverable,
styles.accountNavMenu
)}
>
<img
className={styles.profileIcon}
src={profileIcon}
/>
<span>
{'scratch-cat' /* @todo username */}
</span>
<img
className={styles.dropdownCaretIcon}
src={dropdownCaret}
/>
</div>
</MenuBarItemTooltip>
</div>
</Box>
);
}
}
MenuBar.propTypes = {
editMenuOpen: PropTypes.bool,
enableCommunity: PropTypes.bool,
fileMenuOpen: PropTypes.bool,
intl: intlShape,
isRtl: PropTypes.bool,
languageMenuOpen: PropTypes.bool,
onClickEdit: PropTypes.func,
onClickFile: PropTypes.func,
onClickLanguage: PropTypes.func,
onOpenTipLibrary: PropTypes.func,
onRequestCloseEdit: PropTypes.func,
onRequestCloseFile: PropTypes.func,
onRequestCloseLanguage: PropTypes.func,
onSeeCommunity: PropTypes.func
};
const mapStateToProps = state => ({
fileMenuOpen: fileMenuOpen(state),
editMenuOpen: editMenuOpen(state),
isRtl: state.locales.isRtl,
languageMenuOpen: languageMenuOpen(state)
});
const mapDispatchToProps = dispatch => ({
onOpenTipLibrary: () => dispatch(openTipsLibrary()),
onClickFile: () => dispatch(openFileMenu()),
onRequestCloseFile: () => dispatch(closeFileMenu()),
onClickEdit: () => dispatch(openEditMenu()),
onRequestCloseEdit: () => dispatch(closeEditMenu()),
onClickLanguage: () => dispatch(openLanguageMenu()),
onRequestCloseLanguage: () => dispatch(closeLanguageMenu()),
onSeeCommunity: () => dispatch(setPlayer(true))
});
export default injectIntl(connect(
mapStateToProps,
mapDispatchToProps
)(MenuBar));