Unverified Commit 5903f575 authored by Paul Kaplan's avatar Paul Kaplan Committed by GitHub

Merge pull request #5468 from cwillisf/studios-stats-and-icons

Studios stats and icons
parents 70817bb1 f50d8dbf
......@@ -94,7 +94,11 @@ const selectStudioDescription = state => state.studio.description;
const selectStudioImage = state => state.studio.image;
const selectStudioOpenToAll = state => state.studio.openToAll;
const selectStudioCommentsAllowed = state => state.studio.commentsAllowed;
const selectStudioLastUpdated = state => state.studio.updated;
const selectStudioLoadFailed = state => state.studio.infoStatus === Status.ERROR;
const selectStudioCommentCount = state => state.studio.commentCount;
const selectStudioFollowerCount = state => state.studio.followers;
const selectStudioProjectCount = state => state.studio.projectCount;
const selectIsFetchingInfo = state => state.studio.infoStatus === Status.FETCHING;
const selectIsFollowing = state => state.studio.following;
const selectIsFetchingRoles = state => state.studio.rolesStatus === Status.FETCHING;
......@@ -115,7 +119,9 @@ const getInfo = () => ((dispatch, getState) => {
openToAll: body.open_to_all,
commentsAllowed: body.comments_allowed,
updated: new Date(body.history.modified),
commentCount: body.stats.comments,
followers: body.stats.followers,
projectCount: body.stats.projects,
owner: body.owner
}));
});
......@@ -170,7 +176,11 @@ module.exports = {
selectStudioImage,
selectStudioOpenToAll,
selectStudioCommentsAllowed,
selectStudioLastUpdated,
selectStudioLoadFailed,
selectStudioCommentCount,
selectStudioFollowerCount,
selectStudioProjectCount,
selectIsFetchingInfo,
selectIsFetchingRoles,
selectIsFollowing,
......
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.70008 9.60002C8.42098 7.95136 9.87058 6.64641 11.7481 6.17829C14.9634 5.37663 18.2197 7.33326 19.0214 10.5485C19.8231 13.7638 17.8664 17.0202 14.6512 17.8218C11.9437 18.4969 9.20407 17.2157 7.92938 14.8716C7.61277 14.2893 6.88413 14.074 6.3019 14.3906C5.71967 14.7072 5.50434 15.4359 5.82094 16.0181C7.60517 19.2993 11.4374 21.0966 15.2318 20.1506C19.7332 19.0282 22.4724 14.4693 21.3501 9.96792C20.2278 5.46653 15.6689 2.72726 11.1675 3.84958C8.91622 4.41089 7.10663 5.8319 5.99998 7.67313V6.00002C5.99998 5.33728 5.46272 4.80002 4.79998 4.80002C4.13723 4.80002 3.59998 5.33728 3.59998 6.00002V10.8C3.59998 11.4628 4.13723 12 4.79998 12H9.59998C10.2627 12 10.8 11.4628 10.8 10.8C10.8 10.1373 10.2627 9.60002 9.59998 9.60002H7.70008Z" fill="white"/>
<path d="M13.2 7.80002C13.5313 7.80002 13.8 8.06865 13.8 8.40002V11.5938L16.4228 12.6429C16.7305 12.766 16.8801 13.1152 16.7571 13.4229C16.634 13.7305 16.2848 13.8802 15.9771 13.7571L12.9771 12.5571C12.7493 12.466 12.6 12.2454 12.6 12V8.40002C12.6 8.06865 12.8686 7.80002 13.2 7.80002Z" fill="white"/>
</svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.4 11.6548C20.4 15.4381 16.6448 18.5003 12 18.5003C11.4006 18.5003 10.8107 18.4535 10.2488 18.3505L7.00594 20.1059C6.41785 20.4242 6.02835 20.1523 6.13619 19.4972L6.56854 16.8709C4.75182 15.6254 3.59998 13.7525 3.59998 11.6548C3.59998 7.87156 7.36452 4.79999 12 4.79999C16.6448 4.79999 20.4 7.87156 20.4 11.6548Z" fill="white"/>
</svg>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13 6C13 7.65685 11.6569 9 10 9C8.34315 9 7 7.65685 7 6C7 4.34315 8.34315 3 10 3C11.6569 3 13 4.34315 13 6ZM3.66 9C4.76457 9 5.66 8.10457 5.66 7C5.66 5.89543 4.76457 5 3.66 5C2.55543 5 1.66 5.89543 1.66 7C1.66 8.10457 2.55543 9 3.66 9ZM5.27807 14.6538C4.75697 14.8738 4.14912 15 3.5 15C1.567 15 0 13.8807 0 12.5C0 11.1193 1.567 10 3.5 10C4.69639 10 5.75257 10.4288 6.3838 11.0829C7.29425 10.4157 8.57778 10 10 10C11.4222 10 12.7058 10.4157 13.6162 11.0829C14.2474 10.4288 15.3036 10 16.5 10C18.433 10 20 11.1193 20 12.5C20 13.8807 18.433 15 16.5 15C15.8509 15 15.243 14.8738 14.7219 14.6538C14.0407 16.0199 12.1839 17 10 17C7.81612 17 5.95925 16.0199 5.27807 14.6538ZM16.66 9C17.7646 9 18.66 8.10457 18.66 7C18.66 5.89543 17.7646 5 16.66 5C15.5554 5 14.66 5.89543 14.66 7C14.66 8.10457 15.5554 9 16.66 9Z" fill="#575E75"/>
</svg>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 2C5 1.44772 5.44772 1 6 1C6.55228 1 7 1.44772 7 2V3C7 3.55228 6.55228 4 6 4C5.44772 4 5 3.55228 5 3V2ZM12 2H8V3C8 4.10457 7.10457 5 6 5C4.89543 5 4 4.10457 4 3V2C2.34315 2 1 3.34315 1 5V16C1 17.6569 2.34315 19 4 19H16C17.6569 19 19 17.6569 19 16V5C19 3.34315 17.6569 2 16 2V3C16 4.10457 15.1046 5 14 5C12.8954 5 12 4.10457 12 3V2ZM3 15.6V8H17V15.6C17 16.4284 16.3284 17.1 15.5 17.1H4.5C3.67157 17.1 3 16.4284 3 15.6ZM14 1C13.4477 1 13 1.44772 13 2V3C13 3.55228 13.4477 4 14 4C14.5523 4 15 3.55228 15 3V2C15 1.44772 14.5523 1 14 1Z" fill="#575E75"/>
</svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.8 10.812C20.1312 10.812 20.4 10.5432 20.4 10.212V6.6C20.4 6.2688 20.1312 6 19.8 6H11.0484C10.8888 6 10.7364 6.0636 10.6248 6.1752L9.77518 7.0248C9.66358 7.1364 9.51118 7.2 9.35158 7.2H7.44838C7.28878 7.2 7.13638 7.1364 7.02478 7.0248L6.17518 6.1752C6.06358 6.0636 5.91118 6 5.75158 6H4.19998C3.86878 6 3.59998 6.2688 3.59998 6.6V10.212C3.59998 10.5432 3.86878 10.812 4.19998 10.812H5.76358C5.92318 10.812 6.07438 10.8744 6.18718 10.9872L7.02478 11.8248C7.13638 11.9364 7.28878 12 7.44838 12H9.35158C9.51118 12 9.66358 11.9364 9.77518 11.8248L10.6128 10.9872C10.7244 10.8744 10.8768 10.812 11.0364 10.812H19.8ZM17.4 16.812C17.7312 16.812 18 16.5432 18 16.212V12.6C18 12.2688 17.7312 12 17.4 12H11.0484C10.8888 12 10.7364 12.0636 10.6248 12.1752L9.77518 13.0248C9.66358 13.1364 9.51118 13.2 9.35158 13.2H7.44838C7.28878 13.2 7.13638 13.1364 7.02478 13.0248L6.17518 12.1752C6.06358 12.0636 5.91118 12 5.75158 12H4.19998C3.86878 12 3.59998 12.2688 3.59998 12.6V16.212C3.59998 16.5432 3.86878 16.812 4.19998 16.812H5.76358C5.92318 16.812 6.07438 16.8744 6.18718 16.9872L7.02478 17.8248C7.13638 17.9364 7.28878 18 7.44838 18H9.35158C9.51118 18 9.66358 17.9364 9.77518 17.8248L10.6128 16.9872C10.7244 16.8744 10.8768 16.812 11.0364 16.812H17.4Z" fill="white"/>
</svg>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4057 11.5091C9.50472 11.5091 8.77527 12.2386 8.77527 13.1396C8.77527 14.0405 9.50472 14.77 10.4057 14.77C11.3067 14.77 12.0362 14.0405 12.0362 13.1396C12.0362 12.2386 11.3067 11.5091 10.4057 11.5091ZM11.3417 10.0236C10.927 10.9523 9.88444 10.9523 9.46975 10.0236L8.14574 7.07519C7.73106 6.15289 8.25066 5 9.08171 5H11.7297C12.5608 5 13.0804 6.15289 12.6657 7.07519L11.3417 10.0236Z" fill="white"/>
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="8" y="5" width="5" height="10">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4057 11.5091C9.50472 11.5091 8.77527 12.2386 8.77527 13.1396C8.77527 14.0405 9.50472 14.77 10.4057 14.77C11.3067 14.77 12.0362 14.0405 12.0362 13.1396C12.0362 12.2386 11.3067 11.5091 10.4057 11.5091ZM11.3417 10.0236C10.927 10.9523 9.88444 10.9523 9.46975 10.0236L8.14574 7.07519C7.73106 6.15289 8.25066 5 9.08171 5H11.7297C12.5608 5 13.0804 6.15289 12.6657 7.07519L11.3417 10.0236Z" fill="white"/>
</mask>
<g mask="url(#mask0)">
<rect width="20" height="20" fill="white"/>
</g>
</svg>
{
"studio.tabNavProjects": "Projects",
"studio.tabNavProjectsWithCount": "Projects {projectCount}",
"studio.tabNavCurators": "Curators",
"studio.tabNavComments": "Comments",
"studio.tabNavCommentsWithCount": "Comments {commentCount}",
"studio.tabNavActivity": "Activity",
"studio.title": "Title",
......@@ -73,6 +75,9 @@
"studio.activityRemoveCurator": "{removerProfileLink} removed the curator {removedProfileLink}",
"studio.activityBecomeOwner": "{promotedProfileLink} was promoted to manager by {promotorProfileLink}",
"studio.lastUpdated": "Updated {lastUpdatedDate, date, medium}",
"studio.followerCount": "{followerCount} followers",
"studio.reportThisStudio": "Report this studio",
"studio.reportPleaseExplain": "Please select which part of the studio you find to be disrespectful or inappropriate, or otherwise breaks the Scratch Community Guidelines.",
"studio.reportAreThereComments": "Are there inappropriate comments in the studio? Please report them by clicking the \"report\" button on the individual comments.",
......
import React, {useEffect} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import StudioDescription from './studio-description.jsx';
import StudioFollow from './studio-follow.jsx';
import StudioTitle from './studio-title.jsx';
import StudioImage from './studio-image.jsx';
import StudioReport from './studio-report.jsx';
import StudioStats from './studio-stats.jsx';
import StudioTitle from './studio-title.jsx';
import {selectIsLoggedIn} from '../../redux/session';
import {getInfo, getRoles} from '../../redux/studio';
import StudioReport from './studio-report.jsx';
const StudioInfo = ({
isLoggedIn, onLoadInfo, onLoadRoles
......@@ -27,7 +29,14 @@ const StudioInfo = ({
<StudioFollow />
<StudioImage />
<StudioDescription />
<StudioReport />
<div className="studio-info-footer">
<div className="studio-info-footer-stats">
<StudioStats />
</div>
<div className="studio-info-footer-report">
<StudioReport />
</div>
</div>
</React.Fragment>
);
};
......
......@@ -11,15 +11,20 @@ import {
selectors
} from '../../redux/studio-report';
import reportIcon from './icons/report-icon.svg';
const StudioReport = ({
canReport,
isOpen,
handleOpen
}) => (
<div>
{canReport && (
<button onClick={handleOpen}><FormattedMessage id="general.report" /></button>
)}
{canReport &&
<button onClick={handleOpen}>
<img src={reportIcon} />
<FormattedMessage id="general.report" />
</button>
}
{isOpen && (
<StudioReportModal />
)}
......
/* eslint-disable react/jsx-no-bind */
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {FormattedMessage} from 'react-intl';
import {selectIsFetchingInfo, selectStudioFollowerCount, selectStudioLastUpdated} from '../../redux/studio';
import lastUpdatedIcon from './icons/last-updated-icon.svg';
import followersIcon from './icons/followers-icon.svg';
const StudioStats = ({
isFetchingInfo,
followerCount,
lastUpdatedDate
}) => {
if (isFetchingInfo) return <React.Fragment />;
return (<React.Fragment>
<div><img
src={lastUpdatedIcon}
/><FormattedMessage
id="studio.lastUpdated"
values={{lastUpdatedDate}}
/></div>
<div><img
src={followersIcon}
/><FormattedMessage
id="studio.followerCount"
values={{followerCount}}
/></div>
</React.Fragment>);
};
StudioStats.propTypes = {
isFetchingInfo: PropTypes.bool,
followerCount: PropTypes.number,
lastUpdatedDate: PropTypes.instanceOf(Date)
};
export default connect(
state => ({
isFetchingInfo: selectIsFetchingInfo(state),
followerCount: selectStudioFollowerCount(state),
lastUpdatedDate: selectStudioLastUpdated(state)
}),
{
}
)(StudioStats);
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {useRouteMatch, NavLink} from 'react-router-dom';
import SubNavigation from '../../components/subnavigation/subnavigation.jsx';
import {FormattedMessage} from 'react-intl';
const StudioTabNav = () => {
import SubNavigation from '../../components/subnavigation/subnavigation.jsx';
import activityIcon from './icons/activity-icon.svg';
import commentsIcon from './icons/comments-icon.svg';
import curatorsIcon from './icons/curator-icon.svg';
import projectsIcon from './icons/projects-icon.svg';
import {selectIsFetchingInfo, selectStudioCommentCount, selectStudioProjectCount} from '../../redux/studio';
/**
* Format a number to a string. If the number is below the limit, format as-is. Otherwise, show a '+' to indicate that
* the actual number might be higher.
* @example
* limitCount(1, 100) == '1'
* limitCount(12.5, 100) == '12.5'
* limitCount(100, 100) == '100+'
* limitCount(999, 100) == '100+'
* @param {number} num - the number to format
* @param {number} limit - the number at which we start showing a '+'
* @returns {string} - a string representing a number, possibly with a '+' at the end
*/
const limitCount = (num, limit) => {
if (num < limit) {
return `${num}`;
}
return `${limit}+`;
};
// These must match the limits used by the API
const countLimits = {
comments: 100,
projects: 100
};
const StudioTabNav = ({isFetchingInfo, commentCount, projectCount}) => {
const {params: {studioPath, studioId}} = useRouteMatch();
const base = `/${studioPath}/${studioId}`;
return (
......@@ -16,28 +52,68 @@ const StudioTabNav = () => {
to={base}
exact
>
<li><FormattedMessage id="studio.tabNavProjects" /></li>
<li><img
src={projectsIcon}
/><FormattedMessage
id={isFetchingInfo ? 'studio.tabNavProjects' : 'studio.tabNavProjectsWithCount'}
values={{
projectCount: (
<span className="tab-count">
({limitCount(projectCount, countLimits.projects)})
</span>
)
}}
/></li>
</NavLink>
<NavLink
activeClassName="active"
to={`${base}/comments`}
>
<li><FormattedMessage id="studio.tabNavComments" /></li>
<li><img
src={commentsIcon}
/><FormattedMessage
id={isFetchingInfo ? 'studio.tabNavComments' : 'studio.tabNavCommentsWithCount'}
values={{
commentCount: (
<span className="tab-count">
({limitCount(commentCount, countLimits.comments)})
</span>
)
}}
/></li>
</NavLink>
<NavLink
activeClassName="active"
to={`${base}/curators`}
>
<li><FormattedMessage id="studio.tabNavCurators" /></li>
<li><img
src={curatorsIcon}
/><FormattedMessage id="studio.tabNavCurators" /></li>
</NavLink>
<NavLink
activeClassName="active"
to={`${base}/activity`}
>
<li><FormattedMessage id="studio.tabNavActivity" /></li>
<li><img
src={activityIcon}
/><FormattedMessage id="studio.tabNavActivity" /></li>
</NavLink>
</SubNavigation>
);
};
export default StudioTabNav;
StudioTabNav.propTypes = {
isFetchingInfo: PropTypes.bool,
commentCount: PropTypes.number,
projectCount: PropTypes.number
};
const mapStateToProps = state => ({
isFetchingInfo: selectIsFetchingInfo(state),
commentCount: selectStudioCommentCount(state),
projectCount: selectStudioProjectCount(state)
});
const mapDispatchToProps = () => ({});
export default connect(mapStateToProps, mapDispatchToProps)(StudioTabNav);
......@@ -61,6 +61,52 @@ $radius: 8px;
box-sizing: border-box;
}
}
.studio-info-footer {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.studio-info-footer-stats {
justify-content: flex-start;
div {
display: flex;
align-items: center;
margin: 0.25em;
img {
margin-right: 0.25em;
width: 1.5em;
}
}
}
.studio-info-footer-report {
justify-content: flex-end;
button {
font-size: smaller;
background-color: $ui-blue;
border: 1px solid transparent;
border-radius: 999em;
color: $ui-white;
display: flex;
align-items: center;
padding: 0.25em;
padding-right: 0.75em;
&:hover {
background-color: $ui-blue-dark;
}
img {
margin-right: 0.25em;
width: 1.5em;
}
}
}
.studio-title {
font-size: 28px;
font-weight: 700;
......@@ -109,7 +155,22 @@ $radius: 8px;
border-bottom: 1px solid $active-dark-gray;
padding-bottom: 8px;
font-size: 14px;
li { background: rgba(0, 0, 0, 0.15); }
li {
display: flex;
align-items: center;
background: rgba(0, 0, 0, 0.15);
padding: 0.5em 0.75em 0.5em 0.5em;
&:active {
padding: calc(0.5em + 1px) calc(0.75em + 1px) calc(0.5em + 1px) calc(0.5em + 1px);
}
img {
margin-right: 0.5em;
width: 1.5em;
}
.tab-count {
font-weight: normal;
}
}
.active > li { background: $ui-blue; }
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment