diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 76ef997d89ba1d73a971cb10fffc4fa4e9c0242b..e1d5b9b2a8741e29df3ddd287020aed73f20b2a6 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -53,6 +53,7 @@ let isRendererSupported = null; const GUIComponent = props => { const { + accountNavOpen, activeTabIndex, alertsVisible, basePath, @@ -69,14 +70,20 @@ const GUIComponent = props => { isPlayerOnly, isRtl, loading, - onExtensionButtonClick, + renderLogin, + onClickAccountNav, + onCloseAccountNav, + onLogOut, + onOpenRegistration, + onToggleLoginOpen, + onUpdateProjectTitle, onActivateCostumesTab, onActivateSoundsTab, onActivateTab, + onExtensionButtonClick, onRequestCloseBackdropLibrary, onRequestCloseCostumeLibrary, onSeeCommunity, - onUpdateProjectTitle, previewInfoVisible, targetIsStage, soundsTabVisible, @@ -155,8 +162,15 @@ const GUIComponent = props => { /> ) : null} <MenuBar + accountNavOpen={accountNavOpen} enableCommunity={enableCommunity} + renderLogin={renderLogin} + onClickAccountNav={onClickAccountNav} + onCloseAccountNav={onCloseAccountNav} + onLogOut={onLogOut} + onOpenRegistration={onOpenRegistration} onSeeCommunity={onSeeCommunity} + onToggleLoginOpen={onToggleLoginOpen} onUpdateProjectTitle={onUpdateProjectTitle} /> <Box className={styles.bodyWrapper}> @@ -279,6 +293,7 @@ const GUIComponent = props => { }; GUIComponent.propTypes = { + accountNavOpen: PropTypes.bool, activeTabIndex: PropTypes.number, backdropLibraryVisible: PropTypes.bool, backpackOptions: PropTypes.shape({ @@ -300,13 +315,19 @@ GUIComponent.propTypes = { onActivateCostumesTab: PropTypes.func, onActivateSoundsTab: PropTypes.func, onActivateTab: PropTypes.func, + onClickAccountNav: PropTypes.func, + onCloseAccountNav: PropTypes.func, onExtensionButtonClick: PropTypes.func, + onLogOut: PropTypes.func, + onOpenRegistration: PropTypes.func, onRequestCloseBackdropLibrary: PropTypes.func, onRequestCloseCostumeLibrary: PropTypes.func, onSeeCommunity: PropTypes.func, onTabSelect: PropTypes.func, + onToggleLoginOpen: PropTypes.func, onUpdateProjectTitle: PropTypes.func, previewInfoVisible: PropTypes.bool, + renderLogin: PropTypes.func, soundsTabVisible: PropTypes.bool, stageSizeMode: PropTypes.oneOf(Object.keys(STAGE_SIZE_MODES)), targetIsStage: PropTypes.bool, diff --git a/src/components/menu-bar/account-nav.css b/src/components/menu-bar/account-nav.css new file mode 100644 index 0000000000000000000000000000000000000000..c04f0709ee8fc463535399a5149e3a443aecf42e --- /dev/null +++ b/src/components/menu-bar/account-nav.css @@ -0,0 +1,50 @@ +@import "../../css/colors.css"; +@import "../../css/units.css"; + +.user-info { + display: inline-flex; + flex-wrap: nowrap; + justify-content: center; + align-items: center; + align-content: center; + padding: 0 0.95rem; + max-width: 260px; + height: $menu-bar-height; + overflow: hidden; + text-decoration: none; + text-overflow: ellipsis; + white-space: nowrap; + color: $ui-white; + font-size: .75rem; + font-weight: normal; +} + +[dir="ltr"] .user-info .avatar { + margin-right: calc($space * .8125); +} + +[dir="rtl"] .user-info .avatar { + margin-left: calc($space * .8125); +} + +.user-info .avatar { + margin-right: calc($space * .8125); + border-radius: $form-radius; + width: 2rem; + height: 2rem; + vertical-align: middle; +} + +[dir="ltr"] .user-info .dropdown-caret-position { + margin-left: calc($space * .8125); +} + +[dir="rtl"] .user-info .dropdown-caret-position { + margin-right: calc($space * .8125); +} + +.user-info .dropdown-caret-position { + display: inline-block; + padding-bottom: .125rem; + vertical-align: middle; +} diff --git a/src/components/menu-bar/account-nav.jsx b/src/components/menu-bar/account-nav.jsx new file mode 100644 index 0000000000000000000000000000000000000000..351ae2136557e41da84df8f9d52ee1e3c9534144 --- /dev/null +++ b/src/components/menu-bar/account-nav.jsx @@ -0,0 +1,110 @@ +/* +NOTE: this file only temporarily resides in scratch-gui. +Nearly identical code appears in scratch-www, and the two should +eventually be consolidated. +*/ + +import classNames from 'classnames'; +import {FormattedMessage} from 'react-intl'; +import PropTypes from 'prop-types'; +import React from 'react'; + +import MenuBarMenu from './menu-bar-menu.jsx'; +import {MenuSection} from '../menu/menu.jsx'; +import MenuItemContainer from '../../containers/menu-item.jsx'; +import dropdownCaret from './dropdown-caret.svg'; + +import styles from './account-nav.css'; + +const AccountNavComponent = ({ + className, + classroomId, + isEducator, + isOpen, + isRtl, + isStudent, + menuBarMenuClassName, + onClick, + onClose, + onLogOut, + profileUrl, + thumbnailUrl, + username +}) => ( + <React.Fragment> + <div + className={classNames( + styles.userInfo, + className + )} + onMouseUp={onClick} + > + {thumbnailUrl ? ( + <img + className={styles.avatar} + src={thumbnailUrl} + /> + ) : null} + <span className={styles.profileName}> + {username} + </span> + <div className={styles.dropdownCaretPosition}> + <img + className={styles.dropdownCaretIcon} + src={dropdownCaret} + /> + </div> + </div> + <MenuBarMenu + className={menuBarMenuClassName} + open={isOpen} + // note: the Rtl styles are switched here, because this menu is justified + // opposite all the others + place={isRtl ? 'right' : 'left'} + onRequestClose={onClose} + > + <MenuItemContainer href={profileUrl}> + <FormattedMessage id="general.profile" /> + </MenuItemContainer> + <MenuItemContainer href="/mystuff/"> + <FormattedMessage id="general.myStuff" /> + </MenuItemContainer> + {isEducator ? ( + <MenuItemContainer href="/educators/classes/"> + <FormattedMessage id="general.myClasses" /> + </MenuItemContainer> + ) : null} + {isStudent ? ( + <MenuItemContainer href={`/classes/${classroomId}/`}> + <FormattedMessage id="general.myClass" /> + </MenuItemContainer> + ) : null} + <MenuItemContainer href="/accounts/settings/"> + <FormattedMessage id="general.accountSettings" /> + </MenuItemContainer> + <MenuSection> + <MenuItemContainer onClick={onLogOut}> + <FormattedMessage id="navigation.signOut" /> + </MenuItemContainer> + </MenuSection> + </MenuBarMenu> + </React.Fragment> +); + +AccountNavComponent.propTypes = { + className: PropTypes.string, + classroomId: PropTypes.string, + isEducator: PropTypes.bool, + isOpen: PropTypes.bool, + isRtl: PropTypes.bool, + isStudent: PropTypes.bool, + menuBarMenuClassName: PropTypes.string, + onClick: PropTypes.func, + onClose: PropTypes.func, + onLogOut: PropTypes.func, + profileUrl: PropTypes.string, + thumbnailUrl: PropTypes.string, + username: PropTypes.string +}; + +export default AccountNavComponent; diff --git a/src/components/language-selector/dropdown-caret.svg b/src/components/menu-bar/dropdown-caret.svg similarity index 100% rename from src/components/language-selector/dropdown-caret.svg rename to src/components/menu-bar/dropdown-caret.svg diff --git a/src/components/menu-bar/login-dropdown.css b/src/components/menu-bar/login-dropdown.css new file mode 100644 index 0000000000000000000000000000000000000000..8b16b7fdbfe18a0ad05de7438b72ab6cbff242d5 --- /dev/null +++ b/src/components/menu-bar/login-dropdown.css @@ -0,0 +1,4 @@ + +.login { + padding: .625rem; +} diff --git a/src/components/menu-bar/login-dropdown.jsx b/src/components/menu-bar/login-dropdown.jsx new file mode 100644 index 0000000000000000000000000000000000000000..11d23856c8f35edbec895c3b311073b8cc7ec743 --- /dev/null +++ b/src/components/menu-bar/login-dropdown.jsx @@ -0,0 +1,50 @@ +/* +NOTE: this file only temporarily resides in scratch-gui. +Nearly identical code appears in scratch-www, and the two should +eventually be consolidated. +*/ + +import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import React from 'react'; + +import MenuBarMenu from './menu-bar-menu.jsx'; + +import styles from './login-dropdown.css'; + +const LoginDropdown = ({ + className, + isOpen, + isRtl, + onClose, + renderLogin +}) => ( + <MenuBarMenu + className={className} + open={isOpen} + // note: the Rtl styles are switched here, because this menu is justified + // opposite all the others + place={isRtl ? 'right' : 'left'} + onRequestClose={onClose} + > + <div + className={classNames( + styles.login + )} + > + {renderLogin({ + onClose: onClose + })} + </div> + </MenuBarMenu> +); + +LoginDropdown.propTypes = { + className: PropTypes.string, + isOpen: PropTypes.bool, + isRtl: PropTypes.bool, + onClose: PropTypes.func, + renderLogin: PropTypes.func +}; + +export default LoginDropdown; diff --git a/src/components/menu-bar/menu-bar-menu.jsx b/src/components/menu-bar/menu-bar-menu.jsx new file mode 100644 index 0000000000000000000000000000000000000000..d71d9e04930c95ab79222881d5d87253597472c9 --- /dev/null +++ b/src/components/menu-bar/menu-bar-menu.jsx @@ -0,0 +1,31 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import Menu from '../../containers/menu.jsx'; + +const MenuBarMenu = ({ + children, + className, + onRequestClose, + open, + place = 'right' +}) => ( + <div className={className}> + <Menu + open={open} + place={place} + onRequestClose={onRequestClose} + > + {children} + </Menu> + </div> +); + +MenuBarMenu.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + onRequestClose: PropTypes.func, + open: PropTypes.bool, + place: PropTypes.oneOf(['left', 'right']) +}; + +export default MenuBarMenu; diff --git a/src/components/menu-bar/menu-bar.css b/src/components/menu-bar/menu-bar.css index 46fd7313c16c6a12c55799979d43a876f6e33eff..dcc230672499634335229e1e3295bd626319970a 100644 --- a/src/components/menu-bar/menu-bar.css +++ b/src/components/menu-bar/menu-bar.css @@ -58,11 +58,6 @@ width: $language-selector-width; } -.menu { - z-index: $z-index-menu-bar; - top: $menu-bar-height; -} - .menu-bar-item { display: flex; padding: 0 0.25rem; @@ -105,6 +100,11 @@ padding: 0 0.75rem; } +.menu-bar-menu { + margin-top: $menu-bar-height; + z-index: $z-index-menu-bar; +} + .feedback-link { color: $motion-primary; text-decoration: none; @@ -139,13 +139,16 @@ opacity: 0.5; } -.account-info-wrapper { +.account-info-group { display: flex; flex-direction: row; - padding: 0 .5rem; align-items: center; } +.account-info-group .menu-bar-item { + padding: 0 0.75rem; +} + .mystuff-icon { margin: 0 .25rem; height: 1rem; @@ -197,3 +200,22 @@ filter: hue-rotate(360deg); } } + +.mystuff > a { + background-repeat: no-repeat; + background-position: center center; + background-size: 45%; + padding-right: 10px; + padding-left: 10px; + width: 30px; + overflow: hidden; + text-indent: 50px; + white-space: nowrap; +} +.mystuff > a:hover { + background-size: 50%; +} + +.mystuff > a { + background-image: url("/images/mystuff.png"); +} diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index 897fedc3f3166da91678c56be805566d0200b35d..5b710fd351449b27b2311f606199a0e907f19aa4 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -11,9 +11,11 @@ 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 MenuBarMenu from './menu-bar-menu.jsx'; import {MenuItem, MenuSection} from '../menu/menu.jsx'; import ProjectTitleInput from './project-title-input.jsx'; +import AccountNav from '../../containers/account-nav.jsx'; +import LoginDropdown from './login-dropdown.jsx'; import ProjectSaver from '../../containers/project-saver.jsx'; import DeletionRestorer from '../../containers/deletion-restorer.jsx'; import TurboMode from '../../containers/turbo-mode.jsx'; @@ -21,6 +23,9 @@ import TurboMode from '../../containers/turbo-mode.jsx'; import {openTipsLibrary} from '../../reducers/modals'; import {setPlayer} from '../../reducers/mode'; import { + openAccountMenu, + closeAccountMenu, + accountMenuOpen, openFileMenu, closeFileMenu, fileMenuOpen, @@ -29,7 +34,10 @@ import { editMenuOpen, openLanguageMenu, closeLanguageMenu, - languageMenuOpen + languageMenuOpen, + openLoginMenu, + closeLoginMenu, + loginMenuOpen } from '../../reducers/menus'; import styles from './menu-bar.css'; @@ -39,7 +47,7 @@ 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 dropdownCaret from './dropdown-caret.svg'; import languageIcon from '../language-selector/language-icon.svg'; import scratchLogo from './scratch-logo.svg'; @@ -111,28 +119,6 @@ MenuItemTooltip.propTypes = { 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); @@ -253,14 +239,13 @@ class MenuBar extends React.Component { })} onMouseUp={this.props.onClickFile} > - <div className={classNames(styles.fileMenu)}> - <FormattedMessage - defaultMessage="File" - description="Text for file dropdown menu" - id="gui.menuBar.file" - /> - </div> + <FormattedMessage + defaultMessage="File" + description="Text for file dropdown menu" + id="gui.menuBar.file" + /> <MenuBarMenu + className={classNames(styles.menuBarMenu)} open={this.props.fileMenuOpen} place={this.props.isRtl ? 'left' : 'right'} onRequestClose={this.props.onRequestCloseFile} @@ -301,7 +286,8 @@ class MenuBar extends React.Component { defaultMessage="Save as a copy" description="Menu bar item for saving as a copy" id="gui.menuBar.saveAsCopy" - /></MenuItem> + /> + </MenuItem> </MenuItemTooltip> </MenuSection> <MenuSection> @@ -346,6 +332,7 @@ class MenuBar extends React.Component { /> </div> <MenuBarMenu + className={classNames(styles.menuBarMenu)} open={this.props.editMenuOpen} place={this.props.isRtl ? 'left' : 'right'} onRequestClose={this.props.onRequestCloseEdit} @@ -464,45 +451,126 @@ class MenuBar extends React.Component { </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> + + {/* show the proper UI in the account menu, given whether the user is + logged in, and whether a session is available to log in with */} + <div className={styles.accountInfoGroup}> + {this.props.sessionExists ? ( + this.props.username ? ( + // ************ user is logged in ************ + <React.Fragment> + <a href="/mystuff/"> + <div + className={classNames( + styles.menuBarItem, + styles.hoverable, + styles.mystuffButton + )} + > + <img + className={styles.mystuffIcon} + src={mystuffIcon} + /> + </div> + </a> + <AccountNav + className={classNames( + styles.menuBarItem, + styles.hoverable, + {[styles.active]: this.props.accountMenuOpen} + )} + isOpen={this.props.accountMenuOpen} + isRtl={this.props.isRtl} + menuBarMenuClassName={classNames(styles.menuBarMenu)} + onClick={this.props.onClickAccount} + onClose={this.props.onRequestCloseAccount} + onLogOut={this.props.onLogOut} + /> + </React.Fragment> + ) : ( + // ********* user not logged in, but a session exists + // ********* so they can choose to log in + <React.Fragment> + <div + className={classNames( + styles.menuBarItem, + styles.hoverable + )} + key="join" + onMouseUp={this.props.onOpenRegistration} + > + <FormattedMessage + defaultMessage="Join Scratch" + description="Link for creating a Scratch account" + id="gui.menuBar.joinScratch" + /> + </div> + <div + className={classNames( + styles.menuBarItem, + styles.hoverable + )} + key="login" + onMouseUp={this.props.onClickLogin} + > + <FormattedMessage + defaultMessage="Sign in" + description="Link for signing in to your Scratch account" + id="gui.menuBar.signIn" + /> + <LoginDropdown + className={classNames(styles.menuBarMenu)} + isOpen={this.props.loginMenuOpen} + isRtl={this.props.isRtl} + renderLogin={this.props.renderLogin} + onClose={this.props.onRequestCloseLogin} + /> + </div> + </React.Fragment> + ) + ) : ( + // ******** no login session is available, so don't show login stuff + <React.Fragment> + <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'} + </span> + <img + className={styles.dropdownCaretIcon} + src={dropdownCaret} + /> + </div> + </MenuBarItemTooltip> + </React.Fragment> + )} </div> </Box> ); @@ -510,6 +578,7 @@ class MenuBar extends React.Component { } MenuBar.propTypes = { + accountMenuOpen: PropTypes.bool, canUpdateProject: PropTypes.bool, editMenuOpen: PropTypes.bool, enableCommunity: PropTypes.bool, @@ -517,33 +586,53 @@ MenuBar.propTypes = { intl: intlShape, isRtl: PropTypes.bool, languageMenuOpen: PropTypes.bool, + loginMenuOpen: PropTypes.bool, + onClickAccount: PropTypes.func, onClickEdit: PropTypes.func, onClickFile: PropTypes.func, onClickLanguage: PropTypes.func, + onClickLogin: PropTypes.func, + onLogOut: PropTypes.func, + onOpenRegistration: PropTypes.func, onOpenTipLibrary: PropTypes.func, + onRequestCloseAccount: PropTypes.func, onRequestCloseEdit: PropTypes.func, onRequestCloseFile: PropTypes.func, onRequestCloseLanguage: PropTypes.func, + onRequestCloseLogin: PropTypes.func, onSeeCommunity: PropTypes.func, - onUpdateProjectTitle: PropTypes.func + onToggleLoginOpen: PropTypes.func, + onUpdateProjectTitle: PropTypes.func, + renderLogin: PropTypes.func, + sessionExists: PropTypes.bool, + username: PropTypes.string }; const mapStateToProps = state => ({ canUpdateProject: typeof (state.session && state.session.session && state.session.session.user) !== 'undefined', + accountMenuOpen: accountMenuOpen(state), fileMenuOpen: fileMenuOpen(state), editMenuOpen: editMenuOpen(state), isRtl: state.locales.isRtl, - languageMenuOpen: languageMenuOpen(state) + languageMenuOpen: languageMenuOpen(state), + loginMenuOpen: loginMenuOpen(state), + sessionExists: state.session && typeof state.session.session !== 'undefined', + username: state.session && state.session.session && state.session.session.user ? + state.session.session.user.username : null }); const mapDispatchToProps = dispatch => ({ onOpenTipLibrary: () => dispatch(openTipsLibrary()), + onClickAccount: () => dispatch(openAccountMenu()), + onRequestCloseAccount: () => dispatch(closeAccountMenu()), onClickFile: () => dispatch(openFileMenu()), onRequestCloseFile: () => dispatch(closeFileMenu()), onClickEdit: () => dispatch(openEditMenu()), onRequestCloseEdit: () => dispatch(closeEditMenu()), onClickLanguage: () => dispatch(openLanguageMenu()), onRequestCloseLanguage: () => dispatch(closeLanguageMenu()), + onClickLogin: () => dispatch(openLoginMenu()), + onRequestCloseLogin: () => dispatch(closeLoginMenu()), onSeeCommunity: () => dispatch(setPlayer(true)) }); diff --git a/src/components/menu/menu.jsx b/src/components/menu/menu.jsx index ce443db55cd0bc63a5a867b97ebd6c0760967cc8..4e6a6b229170d5d8f0a7844c3fa58ff3425972bc 100644 --- a/src/components/menu/menu.jsx +++ b/src/components/menu/menu.jsx @@ -32,6 +32,7 @@ MenuComponent.propTypes = { place: PropTypes.oneOf(['left', 'right']) }; + const MenuItem = ({ children, className, @@ -51,6 +52,7 @@ MenuItem.propTypes = { onClick: PropTypes.func }; + const addDividerClassToFirstChild = (child, id) => ( React.cloneElement(child, { className: classNames(child.className, { diff --git a/src/containers/account-nav.jsx b/src/containers/account-nav.jsx new file mode 100644 index 0000000000000000000000000000000000000000..d0ce3b0628b078612bb83b368a5bcd93f9d3eb1f --- /dev/null +++ b/src/containers/account-nav.jsx @@ -0,0 +1,53 @@ +/* +NOTE: this file only temporarily resides in scratch-gui. +Nearly identical code appears in scratch-www, and the two should +eventually be consolidated. +*/ + +import {injectIntl} from 'react-intl'; +import PropTypes from 'prop-types'; +import React from 'react'; +import {connect} from 'react-redux'; + +import AccountNavComponent from '../components/menu-bar/account-nav.jsx'; + +const AccountNav = function (props) { + const { + ...componentProps + } = props; + return ( + <AccountNavComponent + {...componentProps} + /> + ); +}; + +AccountNav.propTypes = { + classroomId: PropTypes.string, + isEducator: PropTypes.bool, + isRtl: PropTypes.bool, + isStudent: PropTypes.bool, + profileUrl: PropTypes.string, + thumbnailUrl: PropTypes.string, + username: PropTypes.string +}; + +const mapStateToProps = state => ({ + classroomId: state.session && state.session.session && state.session.session.user ? + state.session.session.user.classroomId : '', + isEducator: state.session && state.session.permissions && state.session.permissions.educator, + isStudent: state.session && state.session.permissions && state.session.permissions.student, + profileUrl: state.session && state.session.session && state.session.session.user ? + `/users/${state.session.session.user.username}` : '', + thumbnailUrl: state.session && state.session.session && state.session.session.user ? + state.session.session.user.thumbnailUrl : null, + username: state.session && state.session.session && state.session.session.user ? + state.session.session.user.username : '' +}); + +const mapDispatchToProps = () => ({}); + +export default injectIntl(connect( + mapStateToProps, + mapDispatchToProps +)(AccountNav)); diff --git a/src/containers/menu-item.jsx b/src/containers/menu-item.jsx new file mode 100644 index 0000000000000000000000000000000000000000..8755c19f7343db268ef10afd4976dfa4a14bffb9 --- /dev/null +++ b/src/containers/menu-item.jsx @@ -0,0 +1,43 @@ +import bindAll from 'lodash.bindall'; +import PropTypes from 'prop-types'; +import React from 'react'; + +import {MenuItem as MenuItemComponent} from '../components/menu/menu.jsx'; + +class MenuItem extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'navigateToHref' + ]); + } + navigateToHref () { + if (this.props.href) window.location.href = this.props.href; + } + render () { + const { + children, + className, + onClick + } = this.props; + const clickAction = onClick ? onClick : this.navigateToHref; + return ( + <MenuItemComponent + className={className} + onClick={clickAction} + > + {children} + </MenuItemComponent> + ); + } +} + +MenuItem.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + // can take an onClick prop, or take an href and build an onClick handler + href: PropTypes.string, + onClick: PropTypes.func +}; + +export default MenuItem; diff --git a/src/lib/vm-listener-hoc.jsx b/src/lib/vm-listener-hoc.jsx index cf2307726c49e3a53a246119ae623b07e458cef6..16bb5781f159e3c3f067c8d7f7ffb28d7d7f1af5 100644 --- a/src/lib/vm-listener-hoc.jsx +++ b/src/lib/vm-listener-hoc.jsx @@ -122,8 +122,8 @@ const vmListenerHOC = function (WrappedComponent) { }; const mapStateToProps = state => ({ vm: state.scratchGui.vm, - username: state.session && state.session.session ? - state.session.session.username : '' + username: state.session && state.session.session && state.session.session.user ? + state.session.session.user.username : '' }); const mapDispatchToProps = dispatch => ({ onTargetsUpdate: data => { diff --git a/src/reducers/menus.js b/src/reducers/menus.js index 6693160c177fc988232fbd505f5a6c1e5af8d134..4cc494c60b860d324e4fa49e96a748f17a7233ca 100644 --- a/src/reducers/menus.js +++ b/src/reducers/menus.js @@ -1,15 +1,19 @@ const OPEN_MENU = 'scratch-gui/menus/OPEN_MENU'; const CLOSE_MENU = 'scratch-gui/menus/CLOSE_MENU'; +const MENU_ACCOUNT = 'accountMenu'; const MENU_FILE = 'fileMenu'; const MENU_EDIT = 'editMenu'; const MENU_LANGUAGE = 'languageMenu'; +const MENU_LOGIN = 'loginMenu'; const initialState = { + [MENU_ACCOUNT]: false, [MENU_FILE]: false, [MENU_EDIT]: false, - [MENU_LANGUAGE]: false + [MENU_LANGUAGE]: false, + [MENU_LOGIN]: false }; const reducer = function (state, action) { @@ -35,6 +39,9 @@ const closeMenu = menu => ({ type: CLOSE_MENU, menu: menu }); +const openAccountMenu = () => openMenu(MENU_ACCOUNT); +const closeAccountMenu = () => closeMenu(MENU_ACCOUNT); +const accountMenuOpen = state => state.scratchGui.menus[MENU_ACCOUNT]; const openFileMenu = () => openMenu(MENU_FILE); const closeFileMenu = () => closeMenu(MENU_FILE); const fileMenuOpen = state => state.scratchGui.menus[MENU_FILE]; @@ -44,17 +51,26 @@ const editMenuOpen = state => state.scratchGui.menus[MENU_EDIT]; const openLanguageMenu = () => openMenu(MENU_LANGUAGE); const closeLanguageMenu = () => closeMenu(MENU_LANGUAGE); const languageMenuOpen = state => state.scratchGui.menus[MENU_LANGUAGE]; +const openLoginMenu = () => openMenu(MENU_LOGIN); +const closeLoginMenu = () => closeMenu(MENU_LOGIN); +const loginMenuOpen = state => state.scratchGui.menus[MENU_LOGIN]; export { reducer as default, initialState as menuInitialState, + openAccountMenu, + closeAccountMenu, + accountMenuOpen, openFileMenu, closeFileMenu, + fileMenuOpen, openEditMenu, closeEditMenu, + editMenuOpen, openLanguageMenu, closeLanguageMenu, - fileMenuOpen, - editMenuOpen, - languageMenuOpen + languageMenuOpen, + openLoginMenu, + closeLoginMenu, + loginMenuOpen };