diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 308ee35944340efb75b6723c5fb363923b5b42c8..9aa5fccbe1a49e1f826b9e76835fe9b4bc505628 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -58,6 +58,8 @@ const GUIComponent = props => { accountNavOpen, activeTabIndex, alertsVisible, + authorId, + authorUsername, basePath, backdropLibraryVisible, backpackOptions, @@ -179,6 +181,8 @@ const GUIComponent = props => { ) : null} <MenuBar accountNavOpen={accountNavOpen} + authorId={authorId} + authorUsername={authorUsername} canCreateCopy={canCreateCopy} canCreateNew={canCreateNew} canRemix={canRemix} @@ -323,6 +327,8 @@ const GUIComponent = props => { GUIComponent.propTypes = { accountNavOpen: PropTypes.bool, activeTabIndex: PropTypes.number, + authorId: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), + authorUsername: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), backdropLibraryVisible: PropTypes.bool, backpackOptions: PropTypes.shape({ host: PropTypes.string, diff --git a/src/components/menu-bar/account-nav.css b/src/components/menu-bar/account-nav.css index c04f0709ee8fc463535399a5149e3a443aecf42e..e868d034478b93809a75856a73c42e6c836a21b3 100644 --- a/src/components/menu-bar/account-nav.css +++ b/src/components/menu-bar/account-nav.css @@ -28,11 +28,8 @@ } .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 { @@ -45,6 +42,12 @@ .user-info .dropdown-caret-position { display: inline-block; - padding-bottom: .125rem; + padding-bottom: .0625rem; vertical-align: middle; } + +.user-info .profile-name { + font-size: .75rem; + line-height: .9375rem; + font-weight: bold; +} diff --git a/src/components/menu-bar/account-nav.jsx b/src/components/menu-bar/account-nav.jsx index 9d4addf7604978315f08cafa16c4eaf9cc0cbb29..d7339f8c2d8dc84f73c66c514d75a65c21040b44 100644 --- a/src/components/menu-bar/account-nav.jsx +++ b/src/components/menu-bar/account-nav.jsx @@ -12,6 +12,7 @@ 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 UserAvatar from './user-avatar.jsx'; import dropdownCaret from './dropdown-caret.svg'; import styles from './account-nav.css'; @@ -40,7 +41,7 @@ const AccountNavComponent = ({ onMouseUp={onClick} > {thumbnailUrl ? ( - <img + <UserAvatar className={styles.avatar} src={thumbnailUrl} /> diff --git a/src/components/menu-bar/author-info.css b/src/components/menu-bar/author-info.css new file mode 100644 index 0000000000000000000000000000000000000000..54e5a123f3e27a47b5cdbb9dd64e0f07f9db9b18 --- /dev/null +++ b/src/components/menu-bar/author-info.css @@ -0,0 +1,51 @@ +@import "../../css/colors.css"; +@import "../../css/units.css"; + + + +.author-info { + color: $ui-white; + font-family: "Helvetica Neue"; + display: flex; + justify-content: start; + align-items: center; +} + +.author-info .avatar { + margin-right: .5625rem; +} + +.author-info .title-author { + /* display: flex; + align-items: center; + justify-content: end; */ +} + +.author-info .project-title { + max-width: 12rem; + display: block; + overflow: hidden; + font-size: .875rem; + line-height: 1.0625rem; + font-weight: bold; + text-overflow: ellipsis; + white-space: nowrap; +} + +.author-info .username-line { + max-width: 12rem; + font-size: .75rem; + line-height: .9375rem; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.author-info .username { + font-weight: bold; +} + +.author-info .unimportant-text { + font-weight: normal; +} diff --git a/src/components/menu-bar/author-info.jsx b/src/components/menu-bar/author-info.jsx new file mode 100644 index 0000000000000000000000000000000000000000..1fd5b1d79c7df6749a42edd7eee5be63dbeb8513 --- /dev/null +++ b/src/components/menu-bar/author-info.jsx @@ -0,0 +1,60 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import classNames from 'classnames'; +import {FormattedMessage, injectIntl} from 'react-intl'; +import UserAvatar from './user-avatar.jsx'; + +import styles from './author-info.css'; + +const AuthorInfo = ({ + className, + projectTitle, + userId, + username +}) => ( + <div + className={classNames( + className + )} + > + <div + className={classNames( + styles.authorInfo + )} + > + <UserAvatar + className={styles.avatar} + userId={userId} + /> + <div className={styles.titleAuthor}> + <span className={styles.projectTitle}> + {projectTitle} + </span> + <div> + <span className={styles.usernameLine}> + <span className={styles.unimportantText}> + <FormattedMessage + defaultMessage="by" + description="Shows that a project was created by this user" + id="gui.authorInfo.by" + /> + + </span> + <span className={styles.username}> + {username} + </span> + </span> + </div> + </div> + </div> + </div> +); + +AuthorInfo.propTypes = { + className: PropTypes.string, + projectTitle: PropTypes.string, + userId: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), + username: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]) +}; + +export default injectIntl(AuthorInfo); diff --git a/src/components/menu-bar/menu-bar.css b/src/components/menu-bar/menu-bar.css index 29902eabb188273efbb721b849dd74620fa6fe9a..97a73395e4cf220234817198c9817cdd8c4e7c8d 100644 --- a/src/components/menu-bar/menu-bar.css +++ b/src/components/menu-bar/menu-bar.css @@ -126,6 +126,11 @@ height: 34px; } +.author-info { + margin-left: .25rem; + margin-right: .6875rem; +} + .menu-bar-button { height: 2rem; } diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index bc138db9164a02ca1bd8f605c529383201c0b6b9..0ad97c9f3d01228e3d680796a8b65e9ce4d83e59 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -14,6 +14,7 @@ import SBFileUploader from '../../containers/sb-file-uploader.jsx'; import MenuBarMenu from './menu-bar-menu.jsx'; import {MenuItem, MenuSection} from '../menu/menu.jsx'; import ProjectTitleInput from './project-title-input.jsx'; +import AuthorInfo from './author-info.jsx'; import AccountNav from '../../containers/account-nav.jsx'; import LoginDropdown from './login-dropdown.jsx'; import SB3Downloader from '../../containers/sb3-downloader.jsx'; @@ -468,17 +469,26 @@ class MenuBar extends React.Component { <FormattedMessage {...ariaMessages.tutorials} /> </div> <Divider className={classNames(styles.divider)} /> - <div className={classNames(styles.menuBarItem, styles.growable)}> - <MenuBarItemTooltip - enable - id="title-field" - > - <ProjectTitleInput - className={classNames(styles.titleFieldGrowable)} - onUpdateProjectTitle={this.props.onUpdateProjectTitle} - /> - </MenuBarItemTooltip> - </div> + {(this.props.authorUsername && this.props.authorUsername !== this.props.username) ? ( + <AuthorInfo + className={styles.authorInfo} + projectTitle={this.props.projectTitle} + userId={this.props.authorId} + username={this.props.authorUsername} + /> + ) : ( + <div className={classNames(styles.menuBarItem, styles.growable)}> + <MenuBarItemTooltip + enable + id="title-field" + > + <ProjectTitleInput + className={classNames(styles.titleFieldGrowable)} + onUpdateProjectTitle={this.props.onUpdateProjectTitle} + /> + </MenuBarItemTooltip> + </div> + )} <div className={classNames(styles.menuBarItem)}> {this.props.canShare ? shareButton : ( this.props.showComingSoon ? ( @@ -677,6 +687,8 @@ class MenuBar extends React.Component { MenuBar.propTypes = { accountMenuOpen: PropTypes.bool, + authorId: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), + authorUsername: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), canCreateCopy: PropTypes.bool, canCreateNew: PropTypes.bool, canRemix: PropTypes.bool, @@ -714,6 +726,7 @@ MenuBar.propTypes = { onShare: PropTypes.func, onToggleLoginOpen: PropTypes.func, onUpdateProjectTitle: PropTypes.func, + projectTitle: PropTypes.string, renderLogin: PropTypes.func, sessionExists: PropTypes.bool, showComingSoon: PropTypes.bool, @@ -736,6 +749,7 @@ const mapStateToProps = state => { isShowingProject: getIsShowingProject(loadingState), languageMenuOpen: languageMenuOpen(state), loginMenuOpen: loginMenuOpen(state), + projectTitle: state.scratchGui.projectTitle, sessionExists: state.session && typeof state.session.session !== 'undefined', username: user ? user.username : null }; diff --git a/src/components/menu-bar/user-avatar.css b/src/components/menu-bar/user-avatar.css new file mode 100644 index 0000000000000000000000000000000000000000..58c7616967f93eac32534eec503f257ec8fbffa9 --- /dev/null +++ b/src/components/menu-bar/user-avatar.css @@ -0,0 +1,9 @@ +@import "../../css/colors.css"; +@import "../../css/units.css"; + +.user-thumbnail { + border-radius: $form-radius; + vertical-align: middle; + border-radius: .25rem; + box-shadow: 0 0 0 1px $ui-black-transparent; +} diff --git a/src/components/menu-bar/user-avatar.jsx b/src/components/menu-bar/user-avatar.jsx new file mode 100644 index 0000000000000000000000000000000000000000..3268dab6b658f0d5ac074afd50ac2420571c9eb0 --- /dev/null +++ b/src/components/menu-bar/user-avatar.jsx @@ -0,0 +1,32 @@ +import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import React from 'react'; +import {thumbnailUrl} from '../../lib/user-thumbnail'; + +import styles from './user-avatar.css'; + +const UserAvatar = ({ + className, + size, + src, + userId +}) => ( + <React.Fragment> + <img + className={classNames( + className, + styles.userThumbnail + )} + src={src ? src : thumbnailUrl(userId, size)} + /> + </React.Fragment> +); + +UserAvatar.propTypes = { + className: PropTypes.string, + size: PropTypes.number, + src: PropTypes.string, + userId: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]) +}; + +export default UserAvatar; diff --git a/src/lib/user-thumbnail.js b/src/lib/user-thumbnail.js new file mode 100644 index 0000000000000000000000000000000000000000..100b6d5da67e5d1f97a85a8949ea86a5c68617c5 --- /dev/null +++ b/src/lib/user-thumbnail.js @@ -0,0 +1,19 @@ +/** + * @user-thumbnail + * Utility functions to return thumnail-related strings + */ + +/** + * Generate a new empty sprite. The caller should provide localized versions of the default names. + * @param {string} userId userId for the user whose thumbnail we want + * @param {number} width desired thumbnail width; defaults to 32 + * @param {number} height desired thumbnail height; defaults to width. + * @returns {string} thumbnail url string + */ +const thumbnailUrl = (userId, width, height) => ( + `/get_image/user/${userId}_${width ? width : 32}x${height ? height : (width ? width : 32)}.png` +); + +export { + thumbnailUrl +};