Merge branch 'develop' into bugfix/studio-playground-may5

This commit is contained in:
Paul Kaplan 2021-05-10 21:11:05 -04:00
commit 9904586a38
31 changed files with 453 additions and 197 deletions

143
package-lock.json generated
View file

@ -101,9 +101,9 @@
"optional": true
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
"integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@ -503,9 +503,9 @@
}
},
"electron-to-chromium": {
"version": "1.3.726",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.726.tgz",
"integrity": "sha512-dw7WmrSu/JwtACiBzth8cuKf62NKL1xVJuNvyOg0jvruN/n4NLtGYoTzciQquCPNaS2eR+BT5GrxHbslfc/w1w==",
"version": "1.3.727",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.727.tgz",
"integrity": "sha512-Mfz4FIB4FSvEwBpDfdipRIrwd6uo8gUDoRDF4QEYb4h4tSuI3ov594OrjU6on042UlFHouIJpClDODGkPcBSbg==",
"dev": true
},
"semver": {
@ -1368,27 +1368,10 @@
}
}
},
"@formatjs/ecma402-abstract": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.7.0.tgz",
"integrity": "sha512-0IQF4oDZdO8ruyrNJZuRle3F/YiGgRwTNrZyMI1N1X8GERZusOrXU9Stw+j/lyyfDWaJK44b+Qnri/qfLPCtGQ==",
"dev": true,
"requires": {
"tslib": "^2.1.0"
},
"dependencies": {
"tslib": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
"dev": true
}
}
},
"@formatjs/intl-getcanonicallocales": {
"version": "1.5.9",
"resolved": "https://registry.npmjs.org/@formatjs/intl-getcanonicallocales/-/intl-getcanonicallocales-1.5.9.tgz",
"integrity": "sha512-bO0J3IaamFM3rU7noXo1bWSmPA8xuAL8NPk+k5Dy08ehiu/STT3sN+6DGLEvRCpb465CpjUWGCNknDFIcdu9hA==",
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/@formatjs/intl-getcanonicallocales/-/intl-getcanonicallocales-1.5.10.tgz",
"integrity": "sha512-tFqGxZ9HkAzphupybyCKdWHzL1ge/sY8TtzEK57Hs3RCxrv/y+VxIPrE+Izw2oCFowQBz76cyi0zT6PjHuWArA==",
"dev": true,
"requires": {
"cldr-core": "38",
@ -1404,21 +1387,21 @@
}
},
"@formatjs/intl-locale": {
"version": "2.4.23",
"resolved": "https://registry.npmjs.org/@formatjs/intl-locale/-/intl-locale-2.4.23.tgz",
"integrity": "sha512-5G0SjOsVxmX79dPaYk6KWxtdQ18UNK+E2JtAXvGxP8rSMqbJ/7cpDg95CU+YBXVKn6pRWefwqBsbjT5l+kK3yQ==",
"version": "2.4.24",
"resolved": "https://registry.npmjs.org/@formatjs/intl-locale/-/intl-locale-2.4.24.tgz",
"integrity": "sha512-+JOwvBRFS/GFuJlWiWbfAzBng0A+ANoGV1LRseXK+4uzp4Sn35GD8M/dfgU1lp2R2dTWpYie2yyoHe4k4aHF6w==",
"dev": true,
"requires": {
"@formatjs/ecma402-abstract": "1.7.0",
"@formatjs/intl-getcanonicallocales": "1.5.9",
"@formatjs/ecma402-abstract": "1.7.1",
"@formatjs/intl-getcanonicallocales": "1.5.10",
"cldr-core": "38",
"tslib": "^2.1.0"
},
"dependencies": {
"@formatjs/ecma402-abstract": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.7.0.tgz",
"integrity": "sha512-0IQF4oDZdO8ruyrNJZuRle3F/YiGgRwTNrZyMI1N1X8GERZusOrXU9Stw+j/lyyfDWaJK44b+Qnri/qfLPCtGQ==",
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.7.1.tgz",
"integrity": "sha512-FjewVLB2DVEVCvvC7IMffzXVhysvi442i6ed0H7qcrT6xtUpO4vr0oZgpOmsv6D9I4Io0GVebIuySwteS/k3gg==",
"dev": true,
"requires": {
"tslib": "^2.1.0"
@ -1433,15 +1416,24 @@
}
},
"@formatjs/intl-pluralrules": {
"version": "4.0.17",
"resolved": "https://registry.npmjs.org/@formatjs/intl-pluralrules/-/intl-pluralrules-4.0.17.tgz",
"integrity": "sha512-SOX7lRrM1DQXZSzqoCztT+Gc6lnnoqGAVrX/YivQ7J8miPfxN8vgZhZN0MiEGPSLV0HJdO+AA+Xu0hUNJPlpnQ==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@formatjs/intl-pluralrules/-/intl-pluralrules-4.0.18.tgz",
"integrity": "sha512-qRFITPsNoeXfsiGc97pp8mVgqcC7aQNuXsiJjY9LpXVTkYNfjUP4ZpbYXflM4xoWCXMJNz3ilsrQhZWXy9td5g==",
"dev": true,
"requires": {
"@formatjs/ecma402-abstract": "1.7.0",
"@formatjs/ecma402-abstract": "1.7.1",
"tslib": "^2.1.0"
},
"dependencies": {
"@formatjs/ecma402-abstract": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.7.1.tgz",
"integrity": "sha512-FjewVLB2DVEVCvvC7IMffzXVhysvi442i6ed0H7qcrT6xtUpO4vr0oZgpOmsv6D9I4Io0GVebIuySwteS/k3gg==",
"dev": true,
"requires": {
"tslib": "^2.1.0"
}
},
"tslib": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
@ -1451,15 +1443,24 @@
}
},
"@formatjs/intl-relativetimeformat": {
"version": "8.1.7",
"resolved": "https://registry.npmjs.org/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-8.1.7.tgz",
"integrity": "sha512-S8OoqOwiSqU/T0umkjB2lZcyWS4Dh8sWd4m8V+MsSnraeijNho/wSXQhJAuHx0b2Xvnlddt7a5RwyUnLADUepA==",
"version": "8.1.8",
"resolved": "https://registry.npmjs.org/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-8.1.8.tgz",
"integrity": "sha512-MIVrsgG7hvYrnes6TxJLflXhhTuxIaWCIdf6p5Iv6HguTtDJqqAFOCNRCqUnYQeYcNbgIQBgLb0Kh7djS0GU+w==",
"dev": true,
"requires": {
"@formatjs/ecma402-abstract": "1.7.0",
"@formatjs/ecma402-abstract": "1.7.1",
"tslib": "^2.1.0"
},
"dependencies": {
"@formatjs/ecma402-abstract": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.7.1.tgz",
"integrity": "sha512-FjewVLB2DVEVCvvC7IMffzXVhysvi442i6ed0H7qcrT6xtUpO4vr0oZgpOmsv6D9I4Io0GVebIuySwteS/k3gg==",
"dev": true,
"requires": {
"tslib": "^2.1.0"
}
},
"tslib": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
@ -10518,9 +10519,9 @@
}
},
"handlebars": {
"version": "4.7.6",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz",
"integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==",
"version": "4.7.7",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
"integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==",
"dev": true,
"requires": {
"minimist": "^1.2.5",
@ -10543,9 +10544,9 @@
"dev": true
},
"uglify-js": {
"version": "3.11.2",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.11.2.tgz",
"integrity": "sha512-G440NU6fewtnQftSgqRV1r2A5ChKbU1gqFCJ7I8S7MPpY/eZZfLGefaY6gUZYiWebMaO+txgiQ1ZyLDuNWJulg==",
"version": "3.13.5",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.5.tgz",
"integrity": "sha512-xtB8yEqIkn7zmOyS2zUNBsYCBRhDkvlNxMMY2smuJ/qA8NCHeQvKCF3i9Z4k8FJH4+PJvZRtMrPynfZ75+CSZw==",
"dev": true,
"optional": true
}
@ -10814,9 +10815,9 @@
}
},
"hosted-git-info": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
"integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true
},
"hsluv": {
@ -18918,9 +18919,9 @@
},
"dependencies": {
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
"integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@ -20855,9 +20856,9 @@
}
},
"scratch-blocks": {
"version": "0.1.0-prerelease.20210505033858",
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.20210505033858.tgz",
"integrity": "sha512-qsHsxVsXvB7cZnoLirkl+HRjj+X7VcJh9Rh7UX8JoMEJsfUfTRJfo3wSt7zv4iKx6EK/QIStQNs9AACSPVaA7Q==",
"version": "0.1.0-prerelease.20210509032512",
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.20210509032512.tgz",
"integrity": "sha512-rgQrbtSXxCzuuiMdyX/HGhiP8eOX8yDG9ncML9ykgnaDF8ZnfflLD201GwfyvOv3D51Nu6w2Ktg0PcF3ZqnH2Q==",
"dev": true,
"requires": {
"exports-loader": "0.6.3",
@ -20865,9 +20866,9 @@
}
},
"scratch-gui": {
"version": "0.1.0-prerelease.20210505040706",
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20210505040706.tgz",
"integrity": "sha512-KyHm41UAlIKomysMzXqKMkibs6WsibC2jCX/AbsYR47UNtFJLhjvUyJKv05EbGo9GAr/b1uRglTRtiXA8SWsvw==",
"version": "0.1.0-prerelease.20210509040505",
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20210509040505.tgz",
"integrity": "sha512-mXyQxytn3tnes2wiuNG9vnSFQ4anoDxkTHMLS2Xro91M5xP+KVgaR+60yWQegsBnz/8LAA+HTlKadHhr/t+PLQ==",
"dev": true,
"requires": {
"arraybuffer-loader": "^1.0.6",
@ -20918,8 +20919,8 @@
"redux": "3.7.2",
"redux-throttle": "0.1.1",
"scratch-audio": "0.1.0-prerelease.20200528195344",
"scratch-blocks": "0.1.0-prerelease.20210505033858",
"scratch-l10n": "3.11.20210504031549",
"scratch-blocks": "0.1.0-prerelease.20210509032512",
"scratch-l10n": "3.11.20210509031637",
"scratch-paint": "0.2.0-prerelease.20210407203313",
"scratch-render": "0.1.0-prerelease.20210325231800",
"scratch-render-fonts": "1.0.0-prerelease.20210401210003",
@ -21311,9 +21312,9 @@
"dev": true
},
"scratch-l10n": {
"version": "3.11.20210504031549",
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.11.20210504031549.tgz",
"integrity": "sha512-R6CfX0p8LegrsqKm5s+8cSlvzg8Ijma4zGOet22yVtJbtSEcmFmi3LbwV5cKKPjoDWobZMYUhrazmlBLswKPQw==",
"version": "3.11.20210509031637",
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.11.20210509031637.tgz",
"integrity": "sha512-MtO6InGeR6WCtJRlPSnnphWQGwv1bLZ8m7tWczF3kQeZopESq7iy36TkHdPioQ9NnptCtQw1xgeyVakuKbNGxg==",
"dev": true,
"requires": {
"@babel/cli": "^7.1.2",
@ -21400,9 +21401,9 @@
}
},
"scratch-l10n": {
"version": "3.11.20210505031459",
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.11.20210505031459.tgz",
"integrity": "sha512-1geGseNJ95UIyKaOWYIkdE0V2mSUxcrGR/wUE86727FqwYdo19nJyaXvzcv2mWWGdvDu3eV7ei3raD1ZXAniSA==",
"version": "3.11.20210510031549",
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.11.20210510031549.tgz",
"integrity": "sha512-WeoSa4zxtOcAGeaASqVcJcZu/QWoWtHohsP71ICbNcBpcAqnpPV1AhdGNrOQyiwuj0Vy/6CBuH9QYpS0IF+AkA==",
"dev": true,
"requires": {
"@babel/cli": "^7.1.2",
@ -25851,9 +25852,9 @@
"dev": true
},
"ua-parser-js": {
"version": "0.7.20",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.20.tgz",
"integrity": "sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw==",
"version": "0.7.28",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz",
"integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==",
"dev": true
},
"uglify-js": {

View file

@ -126,7 +126,7 @@
"redux-mock-store": "^1.2.3",
"redux-thunk": "2.0.1",
"sass-loader": "6.0.6",
"scratch-gui": "0.1.0-prerelease.20210505040706",
"scratch-gui": "0.1.0-prerelease.20210509040505",
"scratch-l10n": "latest",
"selenium-webdriver": "3.6.0",
"slick-carousel": "1.6.0",

View file

@ -26,6 +26,7 @@ $background-color: hsla(0, 0, 99, 1); //#FDFDFD
/* 3.0 colors */
/* Using www naming convention for now, should be consistent with gui */
$ui-aqua: hsla(163, 85, 40, 1); // #0FBD8C Extension Primary
$ui-aqua-dark: hsla(163, 85, 30, 1); // #0B8E69 Extension Aqua 3
$ui-purple: hsla(260, 100, 70, 1); // #9966FF Looks Primary
$ui-purple-dark: hsla(260, 60, 60, 1); // #774DCB Looks Secondary
$ui-magenta: hsla(300, 53%, 60%, 1); /* #CF63CF Sounds Primary */

View file

@ -6,21 +6,38 @@ const React = require('react');
require('./button.scss');
const Button = props => {
const classes = classNames('button', props.className);
const classes = classNames('button', props.className, {'close-button': props.isCloseType});
return (
<button
className={classes}
{...omit(props, ['className', 'children'])}
{...omit(props, ['className', 'children', 'isCloseType'])}
>
{props.children}
{
props.isCloseType ? (
<img
alt="close-icon"
className="modal-content-close-img"
draggable="false"
src="/svgs/modal/close-x.svg"
/>
) : [
props.children
]
}
</button>
);
};
Button.propTypes = {
children: PropTypes.node,
className: PropTypes.string
className: PropTypes.string,
isCloseType: PropTypes.bool
};
Button.defaultProps = {
className: '',
isCloseType: false
};
module.exports = Button;

View file

@ -53,3 +53,22 @@ $pass-bg: $ui-aqua;
margin-right: -.25rem;
}
}
.close-button {
padding: 0;
position: absolute;
top: 0.5rem;
right: 0.5rem;
border-radius: 1rem;
background-color: $active-gray;
cursor: pointer;
width: 2rem;
height: 2rem;
text-align: center;
line-height: 2rem;
}
.close-button img {
padding-top: 0.5rem;
}

View file

@ -248,6 +248,7 @@
"thumbnail.by": "by",
"report.error": "Something went wrong when trying to send your message. Please try again.",
"report.project": "Report Project",
"report.studio": "Report Studio",
"report.projectInstructions": "When you send a report, it lets the Scratch Team know about projects that break the {CommunityGuidelinesLink}. Does something in this project break the {CommunityGuidelinesLink}? If you think it does, please tell us more.",
"report.CommunityGuidelinesLinkText": "Scratch Community Guidelines",
"report.reasonPlaceHolder": "Select a reason",

View file

@ -12,7 +12,7 @@ const Types = keyMirror({
SET_STATUS: null
});
const banWhitelistPaths = [
const banGoodListPaths = [
'/accounts/banned-response',
'/community_guidelines',
'/privacy_policy',
@ -68,7 +68,7 @@ const handleSessionResponse = (dispatch, body) => {
if (
body.user &&
body.user.banned &&
banWhitelistPaths.indexOf(window.location.pathname) === -1
banGoodListPaths.every(goodPath => window.location.pathname.indexOf(goodPath) === -1)
) {
window.location = '/accounts/banned-response/';
return;

View file

@ -91,6 +91,7 @@ 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 selectStudioLoadFailed = state => state.studio.infoStatus === Status.ERROR;
const selectIsFetchingInfo = state => state.studio.infoStatus === Status.FETCHING;
const selectIsFollowing = state => state.studio.following;
const selectIsFetchingRoles = state => state.studio.rolesStatus === Status.FETCHING;
@ -157,6 +158,7 @@ module.exports = {
selectStudioImage,
selectStudioOpenToAll,
selectStudioCommentsAllowed,
selectStudioLoadFailed,
selectIsFetchingInfo,
selectIsFetchingRoles,
selectIsFollowing

View file

@ -363,9 +363,9 @@ const Developers = () => (
githubLink: (
<a href="https://github.com/LLK/">GitHub</a>
),
emailLink: (
<a href="mailto:help@scratch.mit.edu">
help@scratch.mit.edu
contactUsLink: (
<a href="https://scratch.mit.edu/contact-us/">
<FormattedMessage id="general.contactUs" />
</a>
)
}}

View file

@ -56,5 +56,5 @@
"developers.faqDifferencesTitle": "Whats the difference between Blockly and Scratch Blocks?",
"developers.faqDifferencesBody": "Scratch Blocks builds upon the Blockly code base, and is specifically designed with our principles in mind to support creative learning experiences.",
"developers.faqCollabTitle": "Id like to collaborate. How do I get in touch?",
"developers.faqCollabBody": "You can reach us over on {githubLink} or you can send an email to {emailLink}. We look forward to hearing from you!"
"developers.faqCollabBody": "You can reach us over on {githubLink} or on our {contactUsLink} page. We look forward to hearing from you!"
}

View file

@ -2,8 +2,6 @@ const React = require('react');
const render = require('../../lib/render.jsx');
const Scratch3Registration = require('../../components/registration/scratch3-registration.jsx');
const ErrorBoundary = require('../../components/errorboundary/errorboundary.jsx');
// Require this even though we don't use it because, without it, webpack runs out of memory...
const Page = require('../../components/page/www/page.jsx'); // eslint-disable-line no-unused-vars
const initSentry = require('../../lib/sentry.js');
initSentry();

View file

@ -0,0 +1,56 @@
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const PropTypes = require('prop-types');
const React = require('react');
const TitleBanner = require('../../../components/title-banner/title-banner.jsx');
const Button = require('../../../components/forms/button.jsx');
require('./donate-banner.scss');
const navigateToDonatePage = () => {
window.location = 'https://secure.donationpay.org/scratchfoundation';
};
const DonateTopBanner = ({
onRequestClose
}) => (
<TitleBanner className="donate-banner">
<div className="donate-container">
<img
className="donate-icon"
src="/images/ideas/try-it-icon.svg"
/>
<div className="donate-central-items">
<p className="donate-text">
<FormattedMessage id="donatebanner.askSupport" />
</p>
<Button
className="donate-button"
key="add-to-studio-button"
onClick={navigateToDonatePage}
>
<FormattedMessage id="general.donate" />
</Button>
</div>
</div>
<Button
isCloseType
className="donate-close-button"
key="closeButton"
name="closeButton"
type="button"
onClick={onRequestClose}
>
<div className="action-button-text">
<FormattedMessage id="general.close" />
</div>
</Button>
</TitleBanner>
);
DonateTopBanner.propTypes = {
onRequestClose: PropTypes.func
};
module.exports = injectIntl(DonateTopBanner);

View file

@ -0,0 +1,77 @@
@import "../../../colors";
@import "../../../frameless";
$tile-height: 244px;
.donate-banner {
display: flex;
position: fixed;
z-index: 8;
background-color: $ui-aqua-dark;
padding: 0;
overflow: hidden;
align-items: center;
justify-content: center;
.donate-container {
display: flex;
margin: 0.375rem auto;
align-items: center;
.donate-icon {
margin: 0.6875rem;
width: 1.75rem;
height: 1.75rem;
}
.donate-central-items {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
}
.donate-text {
text-align: left;
color: $ui-white;
font-size: 1rem;
font-weight: bold;
margin-right: 1rem;
max-width: 70vw;
}
.donate-button {
margin: 0 7rem 0 .5rem;
border-radius: 1.25rem;
background-color: $ui-white;
color: $ui-aqua-dark;
padding: 0 1.75rem;
height: 2.25rem;
text-decoration: none;
line-height: .875rem;
font-size: 1rem;
font-weight: bold;
}
}
.donate-close-button {
right: 1rem;
top: auto;
}
}
@media only screen and (max-width: $mobileIntermediate) {
.donate-banner .donate-container .donate-central-items {
flex-wrap: wrap;
}
.donate-banner .donate-container .donate-icon {
padding-bottom: 2rem;
}
.donate-banner .donate-container .donate-button {
margin-left: 0;
margin-bottom: 1rem
}
.donate-banner .donate-close-button {
top: 1rem;
}
}

View file

@ -29,6 +29,8 @@
"intro.watchVideo": "Watch Video",
"news.scratchNews": "Scratch News",
"donatebanner.askSupport": "Scratch is the world's largest free coding community for kids. Your support makes a difference.",
"teacherbanner.greeting": "Hi",
"teacherbanner.subgreeting": "Teacher Account",
"teacherbanner.classesButton": "My Classes",

View file

@ -32,12 +32,10 @@ const LoveProjectMessage = require('./activity-rows/love-project.jsx');
const RemixProjectMessage = require('./activity-rows/remix-project.jsx');
const ShareProjectMessage = require('./activity-rows/share-project.jsx');
// Hour of Code Banner Components
const TopBanner = require('./hoc/top-banner.jsx');
const MiddleBanner = require('./hoc/middle-banner.jsx');
const HOC_START_TIME = 1605484800000; // 2020-11-16 00:00:00
const HOC_END_TIME = 1608681600000; // 2020-12-23 00:00:00
// Banner Components
const DonateBanner = require('./donate/donate-banner.jsx');
const HOCTopBanner = require('./hoc/top-banner.jsx');
const HOCMiddleBanner = require('./hoc/middle-banner.jsx');
require('./splash.scss');
@ -352,6 +350,7 @@ class SplashPresentation extends React.Component { // eslint-disable-line react/
const formatHTMLMessage = this.props.intl.formatHTMLMessage;
const formatMessage = this.props.intl.formatMessage;
const messages = {
'general.viewAll': formatMessage({id: 'general.viewAll'}),
'news.scratchNews': formatMessage({id: 'news.scratchNews'}),
@ -412,22 +411,29 @@ class SplashPresentation extends React.Component { // eslint-disable-line react/
/>
] : []}
{
this.props.sessionStatus === sessionActions.Status.FETCHED &&
Object.keys(this.props.user).length === 0 && (// Only show top banner if user is not logged in
(Date.now() >= HOC_START_TIME && Date.now() < HOC_END_TIME) ? (
this.props.shouldShowHOCTopBanner && (
<MediaQuery
key="frameless-tablet"
minWidth={frameless.tabletPortrait}
>
<TopBanner />
<HOCTopBanner />
</MediaQuery>
) : (
)
}
{
this.props.shouldShowDonateBanner && (
<DonateBanner
onRequestClose={this.props.onCloseDonateBanner}
/>
)
}
{
this.props.shouldShowIntro && (
<Intro
key="intro"
messages={messages}
/>
)
)
}
<div
className="inner mod-splash"
@ -464,17 +470,14 @@ class SplashPresentation extends React.Component { // eslint-disable-line react/
{featured.shift()}
</div>
{
this.props.sessionStatus === sessionActions.Status.FETCHED &&
Object.keys(this.props.user).length !== 0 && // Only show if user is logged in
Date.now() >= HOC_START_TIME && // Show middle banner on and after Dec 3
Date.now() < HOC_END_TIME && // Hide middle banner after Dec 14
false && // we did not use this middle banner in last HoC
this.props.shouldShowHOCMiddleBanner && (
<MediaQuery
key="frameless-desktop"
minWidth={frameless.tabletPortrait}
>
<MiddleBanner />
<HOCMiddleBanner />
</MediaQuery>
)
}
<div
@ -573,6 +576,7 @@ SplashPresentation.propTypes = {
lovedByFollowing: PropTypes.arrayOf(PropTypes.object),
news: PropTypes.arrayOf(PropTypes.object),
onCloseAdminPanel: PropTypes.func.isRequired,
onCloseDonateBanner: PropTypes.func.isRequired,
onDismiss: PropTypes.func.isRequired,
onHideEmailConfirmationModal: PropTypes.func.isRequired,
onOpenAdminPanel: PropTypes.func.isRequired,
@ -581,7 +585,11 @@ SplashPresentation.propTypes = {
refreshCacheStatus: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
sessionStatus: PropTypes.string.isRequired,
sharedByFollowing: PropTypes.arrayOf(PropTypes.object),
shouldShowDonateBanner: PropTypes.bool.isRequired,
shouldShowEmailConfirmation: PropTypes.bool.isRequired,
shouldShowHOCTopBanner: PropTypes.bool.isRequired,
shouldShowIntro: PropTypes.bool.isRequired,
shouldShowHOCMiddleBanner: PropTypes.bool.isRequired,
shouldShowWelcome: PropTypes.bool.isRequired,
user: PropTypes.object.isRequired // eslint-disable-line react/forbid-prop-types
};

View file

@ -12,6 +12,11 @@ const splashActions = require('../../redux/splash.js');
const Page = require('../../components/page/www/page.jsx');
const SplashPresentation = require('./presentation.jsx');
const SCRATCH_WEEK_START_TIME = 1621224000000; // 2021-05-17 00:00:00
const SCRATCH_WEEK_END_TIME = 1621828800000; // 2021-05-24 00:00:00
const HOC_START_TIME = 1605484800000; // 2020-11-16 00:00:00
const HOC_END_TIME = 1608681600000; // 2020-12-23 00:00:00
class Splash extends React.Component {
constructor (props) {
super(props);
@ -22,6 +27,7 @@ class Splash extends React.Component {
'handleShowEmailConfirmationModal',
'handleHideEmailConfirmationModal',
'handleCloseAdminPanel',
'handleCloseDonateBanner',
'handleOpenAdminPanel',
'handleDismiss',
'shouldShowWelcome',
@ -29,8 +35,9 @@ class Splash extends React.Component {
]);
this.state = {
adminPanelOpen: false,
dismissedDonateBanner: false,
news: [], // gets news posts from the scratch Tumblr
emailConfirmationModalOpen: false, // flag that determines whether to show banner to request email conf.
emailConfirmationModalOpen: false,
refreshCacheStatus: 'notrequested'
};
}
@ -114,6 +121,9 @@ class Splash extends React.Component {
handleOpenAdminPanel () {
this.setState({adminPanelOpen: true});
}
handleCloseDonateBanner () {
this.setState({dismissedDonateBanner: true});
}
handleShowEmailConfirmationModal () {
this.setState({emailConfirmationModalOpen: true});
}
@ -144,8 +154,33 @@ class Splash extends React.Component {
this.props.flags.confirm_email_banner
);
}
shouldShowHOCTopBanner () {
return (
this.props.sessionStatus === sessionActions.Status.FETCHED && // done fetching session
Object.keys(this.props.user).length === 0 && // no user session found
Date.now() >= HOC_START_TIME &&
Date.now() < HOC_END_TIME
);
}
shouldShowHOCMiddleBanner () {
return false; // we did not use this middle banner in last HoC
}
shouldShowDonateBanner () {
return (
this.state.dismissedDonateBanner === false &&
this.props.sessionStatus === sessionActions.Status.FETCHED && // done fetching session
Object.keys(this.props.user).length === 0 && // no user session found
Date.now() >= SCRATCH_WEEK_START_TIME &&
Date.now() < SCRATCH_WEEK_END_TIME &&
this.shouldShowHOCTopBanner() !== true
);
}
render () {
const showEmailConfirmation = this.shouldShowEmailConfirmation() || false;
const showDonateBanner = this.shouldShowDonateBanner() || false;
const showHOCTopBanner = this.shouldShowHOCTopBanner() || false;
const showHOCMiddleBanner = this.shouldShowHOCMiddleBanner() || false;
const showIntro = showHOCTopBanner !== true;
const showWelcome = this.shouldShowWelcome();
const homepageRefreshStatus = this.getHomepageRefreshStatus();
@ -163,9 +198,14 @@ class Splash extends React.Component {
refreshCacheStatus={homepageRefreshStatus}
sessionStatus={this.props.sessionStatus}
sharedByFollowing={this.props.shared}
shouldShowDonateBanner={showDonateBanner}
shouldShowEmailConfirmation={showEmailConfirmation}
shouldShowHOCTopBanner={showHOCTopBanner}
shouldShowIntro={showIntro}
shouldShowHOCMiddleBanner={showHOCMiddleBanner}
shouldShowWelcome={showWelcome}
user={this.props.user}
onCloseDonateBanner={this.handleCloseDonateBanner}
onCloseAdminPanel={this.handleCloseAdminPanel}
onDismiss={this.handleDismiss}
onHideEmailConfirmationModal={this.handleHideEmailConfirmationModal}

View file

@ -0,0 +1,28 @@
{
"studio.tabNavProjects": "Projects",
"studio.tabNavCurators": "Curators",
"studio.tabNavComments": "Comments",
"studio.tabNavActivity": "Activity",
"studio.title": "Title",
"studio.description": "Description",
"studio.thumbnail": "Thumbnail",
"studio.projectsHeader": "Projects",
"studio.addProjectsHeader": "Add Projects",
"studio.addProject": "Add",
"studio.creatorRole": "Studio Creator",
"studio.managersHeader": "Managers",
"studio.unfollowStudio": "Unfollow Studio",
"studio.followStudio": "Follow Studio",
"studio.curatorsHeader": "Curators",
"studio.inviteCuratorsHeader": "Invite Curators",
"studio.inviteCurator": "Invite",
"studio.curatorAcceptInvite": "Accept Invite",
"studio.commentsHeader": "Comments"
}

View file

@ -56,7 +56,7 @@ const StudioComments = ({
return (
<div>
<h2>Comments</h2>
<h2><FormattedMessage id="studio.commentsHeader" /></h2>
{canEditCommentsAllowed && <StudioCommentsAllowed />}
<div>
{shouldShowCommentComposer && commentsAllowed &&

View file

@ -3,6 +3,7 @@ import React, {useState} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import classNames from 'classnames';
import {FormattedMessage} from 'react-intl';
import {acceptInvitation} from './lib/studio-member-actions';
@ -26,7 +27,7 @@ const StudioCuratorInvite = ({onSubmit}) => {
setSubmitting(false);
});
}}
>Accept invite</button>
><FormattedMessage id="studio.curatorAcceptInvite" /></button>
{error && <div>{error}</div>}
</div>
);

View file

@ -3,6 +3,7 @@ import React, {useState} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import classNames from 'classnames';
import {FormattedMessage} from 'react-intl';
import {inviteCurator} from './lib/studio-member-actions';
@ -20,7 +21,7 @@ const StudioCuratorInviter = ({onSubmit}) => {
};
return (
<div className="studio-adder-section">
<h3> Invite Curators</h3>
<h3><FormattedMessage id="studio.inviteCuratorsHeader" /></h3>
<input
disabled={submitting}
type="text"
@ -35,7 +36,7 @@ const StudioCuratorInviter = ({onSubmit}) => {
})}
disabled={submitting}
onClick={submit}
>Invite</button>
><FormattedMessage id="studio.inviteCurator" /></button>
{error && <div>{error}</div>}
</div>
);

View file

@ -1,6 +1,7 @@
import React, {useEffect} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {FormattedMessage} from 'react-intl';
import {curators} from './lib/redux-modules';
import Debug from './debug.jsx';
@ -18,7 +19,7 @@ const StudioCurators = ({
}, []);
return (<div className="studio-members">
<h2>Curators</h2>
<h2><FormattedMessage id="studio.curatorsHeader" /></h2>
{canInviteCurators && <CuratorInviter />}
{showCuratorInvite && <CuratorInvite />}
{error && <Debug
@ -37,7 +38,7 @@ const StudioCurators = ({
{loading ? <small>Loading...</small> : (
moreToLoad ?
<button onClick={onLoadMore}>
Load more
<FormattedMessage id="general.loadMore" />
</button> :
<small>No more to load</small>
)}

View file

@ -2,6 +2,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {FormattedMessage} from 'react-intl';
import {selectIsFollowing} from '../../redux/studio';
import {selectCanFollowStudio} from '../../redux/studio-permissions';
import {
@ -28,7 +30,9 @@ const StudioFollow = ({
onClick={() => handleFollow(!isFollowing)}
>
{isMutating ? '...' : (
isFollowing ? 'Unfollow Studio' : 'Follow Studio'
isFollowing ?
<FormattedMessage id="studio.unfollowStudio" /> :
<FormattedMessage id="studio.followStudio" />
)}
</button>
{followingError && <div>Error mutating following: {followingError}</div>}

View file

@ -1,6 +1,7 @@
import React, {useEffect} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {FormattedMessage} from 'react-intl';
import {managers} from './lib/redux-modules';
import {loadManagers} from './lib/studio-member-actions';
@ -15,7 +16,7 @@ const StudioManagers = ({items, error, loading, moreToLoad, onLoadMore}) => {
return (
<div className="studio-members">
<h2>Managers</h2>
<h2><FormattedMessage id="studio.managersHeader" /></h2>
{error && <Debug
label="Error"
data={error}
@ -33,7 +34,7 @@ const StudioManagers = ({items, error, loading, moreToLoad, onLoadMore}) => {
{loading ? <small>Loading...</small> : (
moreToLoad ?
<button onClick={onLoadMore}>
Load more
<FormattedMessage id="general.loadMore" />
</button> :
<small>No more to load</small>
)}

View file

@ -3,6 +3,7 @@ import React, {useState} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import classNames from 'classnames';
import {FormattedMessage} from 'react-intl';
import {
selectCanRemoveCurators, selectCanRemoveManager, selectCanPromoteCurators
@ -33,7 +34,7 @@ const StudioMemberTile = ({
href={userUrl}
className="studio-member-name"
>{username}</a>
{isCreator && <div className="studio-member-role">Studio Creator</div>}
{isCreator && <div className="studio-member-role"><FormattedMessage id="studio.creatorRole" /></div>}
</div>
{canRemove &&
<button

View file

@ -3,6 +3,7 @@ import React, {useState} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import classNames from 'classnames';
import {FormattedMessage} from 'react-intl';
import {addProject} from './lib/studio-project-actions';
@ -20,7 +21,7 @@ const StudioProjectAdder = ({onSubmit}) => {
};
return (
<div className="studio-adder-section">
<h3> Add Projects</h3>
<h3><FormattedMessage id="studio.addProjectsHeader" /></h3>
<input
disabled={submitting}
type="text"
@ -35,7 +36,7 @@ const StudioProjectAdder = ({onSubmit}) => {
})}
disabled={submitting}
onClick={submit}
>Add</button>
><FormattedMessage id="studio.addProject" /></button>
{error && <div>{error}</div>}
</div>
);

View file

@ -2,6 +2,7 @@ import React, {useEffect} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import StudioOpenToAll from './studio-open-to-all.jsx';
import {FormattedMessage} from 'react-intl';
import {projects} from './lib/redux-modules';
import {selectCanAddProjects, selectCanEditOpenToAll} from '../../redux/studio-permissions';
@ -19,7 +20,7 @@ const StudioProjects = ({
return (
<div className="studio-projects">
<h2>Projects</h2>
<h2><FormattedMessage id="studio.projectsHeader" /></h2>
{canEditOpenToAll && <StudioOpenToAll />}
{canAddProjects && <StudioProjectAdder />}
{error && <Debug
@ -43,7 +44,7 @@ const StudioProjects = ({
{loading ? <small>Loading...</small> : (
moreToLoad ?
<button onClick={onLoadMore}>
Load more
<FormattedMessage id="general.loadMore" />
</button> :
<small>No more to load</small>
)}

View file

@ -2,6 +2,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {FormattedMessage} from 'react-intl';
import {
Fields,
@ -24,15 +25,15 @@ const StudioReport = ({
<div>
<h3>Reporting</h3>
{canReport && (
<button onClick={handleOpen}>Report</button>
<button onClick={handleOpen}><FormattedMessage id="general.report" /></button>
)}
{isOpen && (
<div style={{padding: '1rem', margin: '1rem', border: '1px solid green'}}>
<div>Report Studio Modal</div>
<div><FormattedMessage id="report.studio" /></div>
{previouslyReported ? (
<React.Fragment>
<div>Submitted the report!</div>
<button onClick={handleClose}>Close</button>
<button onClick={handleClose}><FormattedMessage id="general.close" /></button>
</React.Fragment>
) : (
<React.Fragment>
@ -40,9 +41,9 @@ const StudioReport = ({
value={field}
onChange={e => handleSetField(e.target.value)}
>
<option value={Fields.TITLE}>Title</option>
<option value={Fields.DESCRIPTION}>Description</option>
<option value={Fields.THUMBNAIL}>Thumbnail</option>
<option value={Fields.TITLE}><FormattedMessage id="studio.title" /></option>
<option value={Fields.DESCRIPTION}><FormattedMessage id="studio.description" /></option>
<option value={Fields.THUMBNAIL}><FormattedMessage id="studio.thumbnail" /></option>
</select>
{error && (
<div>
@ -54,9 +55,9 @@ const StudioReport = ({
disabled={isSubmitting}
onClick={handleSubmit}
>
Submit
<FormattedMessage id="report.send" />
</button>
<button onClick={handleClose}>Cancel</button>
<button onClick={handleClose}><FormattedMessage id="general.cancel" /></button>
</React.Fragment>
)}
</div>

View file

@ -1,6 +1,7 @@
import React from 'react';
import {useRouteMatch, NavLink} from 'react-router-dom';
import SubNavigation from '../../components/subnavigation/subnavigation.jsx';
import {FormattedMessage} from 'react-intl';
const StudioTabNav = () => {
const {params: {studioPath, studioId}} = useRouteMatch();
@ -15,25 +16,25 @@ const StudioTabNav = () => {
to={base}
exact
>
<li>Projects</li>
<li><FormattedMessage id="studio.tabNavProjects" /></li>
</NavLink>
<NavLink
activeClassName="active"
to={`${base}/comments`}
>
<li>Comments</li>
<li><FormattedMessage id="studio.tabNavComments" /></li>
</NavLink>
<NavLink
activeClassName="active"
to={`${base}/curators`}
>
<li>Curators</li>
<li><FormattedMessage id="studio.tabNavCurators" /></li>
</NavLink>
<NavLink
activeClassName="active"
to={`${base}/activity`}
>
<li>Activity</li>
<li><FormattedMessage id="studio.tabNavActivity" /></li>
</NavLink>
</SubNavigation>
);

View file

@ -6,9 +6,14 @@ import {
Redirect,
useRouteMatch
} from 'react-router-dom';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import Page from '../../components/page/www/page.jsx';
import render from '../../lib/render.jsx';
import NotAvailable from '../../components/not-available/not-available.jsx';
import StudioTabNav from './studio-tab-nav.jsx';
import StudioProjects from './studio-projects.jsx';
@ -25,17 +30,19 @@ import {
activity
} from './lib/redux-modules';
const {getInitialState, studioReducer} = require('../../redux/studio');
const {getInitialState, studioReducer, selectStudioLoadFailed} = require('../../redux/studio');
const {studioReportReducer} = require('../../redux/studio-report');
const {commentsReducer} = require('../../redux/comments');
const {studioMutationsReducer} = require('../../redux/studio-mutations');
import './studio.scss';
const StudioShell = () => {
const StudioShell = ({studioLoadFailed}) => {
const match = useRouteMatch();
return (
studioLoadFailed ?
<NotAvailable /> :
<div className="studio-shell">
<div className="studio-info">
<StudioInfo />
@ -68,13 +75,23 @@ const StudioShell = () => {
);
};
StudioShell.propTypes = {
studioLoadFailed: PropTypes.bool
};
const ConnectedStudioShell = connect(
state => ({
studioLoadFailed: selectStudioLoadFailed(state)
}),
)(StudioShell);
render(
<Page className="studio-page">
<Router>
<Switch>
{/* Use variable studioPath to support /studio-playground/ or future route */}
<Route path="/:studioPath/:studioId">
<StudioShell />
<ConnectedStudioShell />
</Route>
</Switch>
</Router>

View file

@ -50,25 +50,6 @@ const TeacherFaq = props => (
<dd><FormattedHTMLMessage id="teacherfaq.teacherSignUpBody" /></dd>
<dt><FormattedMessage id="teacherfaq.classMultipleTeachersTitle" /></dt>
<dd><FormattedMessage id="teacherfaq.classMultipleTeachersBody" /></dd>
<dt><FormattedMessage id="teacherfaq.convertToTeacherTitle" /></dt>
<dd>
<FormattedMessage
id="teacherfaq.convertToTeacherList"
values={{
helpEmail: (
<a href="mailto:help@scratch.mit.edu">
help@scratch.mit.edu
</a>
)
}}
/>
</dd>
<ul>
<li><FormattedMessage id="teacherfaq.convertToTeacherUsername" /></li>
<li><FormattedMessage id="teacherfaq.convertToTeacherEmail" /></li>
<li><FormattedMessage id="teacherfaq.convertToTeacherBirth" /></li>
</ul>
<dt><FormattedMessage id="teacherfaq.teacherPersonalTitle" /></dt>
<dd><FormattedMessage id="teacherfaq.teacherPersonalBody" /></dd>
<dt><FormattedMessage id="teacherfaq.teacherGoogleTitle" /></dt>

View file

@ -10,11 +10,6 @@
"teacherfaq.teacherSignUpBody": "To request a Teacher Account, please go to the teacher account <a href=\"/educators/register\">request form</a>.",
"teacherfaq.classMultipleTeachersTitle": "Can a class have multiple teachers?",
"teacherfaq.classMultipleTeachersBody": "A class can only have one teacher account associated with it.",
"teacherfaq.convertToTeacherTitle": "I already have a Scratch account, can you make it a Teacher account?",
"teacherfaq.convertToTeacherList": "Please send an email to {helpEmail} and be sure to include the following information:",
"teacherfaq.convertToTeacherUsername": "Your Scratch username (Please double check that you know your username by logging into it before contacting us!)",
"teacherfaq.convertToTeacherEmail": "The email address associated with your Scratch account",
"teacherfaq.convertToTeacherBirth": "The birth month and birth year associated with your Scratch account",
"teacherfaq.teacherPersonalTitle": "Why do you need to know my personal information during registration?",
"teacherfaq.teacherPersonalBody": "We use this information to verify the account creator is an educator. We will not share this information with anyone else, and it will not be shared publicly on the site.",
"teacherfaq.teacherGoogleTitle": "Does Scratch connect with Google Classroom, Clever or any other classroom management service?",