diff --git a/.tx/config b/.tx/config
index 7e6f49722..1549f1b69 100644
--- a/.tx/config
+++ b/.tx/config
@@ -115,3 +115,9 @@ file_filter = localizations/download/.json
source_file = src/views/download/l10n.json
source_lang = en
type = KEYVALUEJSON
+
+[scratch-website.camp-l10njson]
+file_filter = localizations/camp/.json
+source_file = src/views/camp/l10n.json
+source_lang = en
+type = KEYVALUEJSON
diff --git a/src/components/carousel/carousel.jsx b/src/components/carousel/carousel.jsx
index 50df3a61b..46e01fa1a 100644
--- a/src/components/carousel/carousel.jsx
+++ b/src/components/carousel/carousel.jsx
@@ -23,7 +23,8 @@ var Carousel = React.createClass({
return {
items: require('./carousel.json'),
showRemixes: false,
- showLoves: false
+ showLoves: false,
+ type: 'project'
};
},
render: function () {
@@ -62,7 +63,7 @@ var Carousel = React.createClass({
{this.props.items.map(function (item) {
var href = '';
- switch (item.type) {
+ switch (this.props.type) {
case 'gallery':
href = '/studios/' + item.id + '/';
break;
@@ -77,13 +78,13 @@ var Carousel = React.createClass({
+ src={item.image}
+ creator={item.author.username}
+ remixes={item.stats.remixes}
+ loves={item.stats.loves} />
);
}.bind(this))}
diff --git a/src/components/carousel/legacy-carousel.jsx b/src/components/carousel/legacy-carousel.jsx
new file mode 100644
index 000000000..36efb5cbf
--- /dev/null
+++ b/src/components/carousel/legacy-carousel.jsx
@@ -0,0 +1,97 @@
+// This component handles json returned via proxy from a django server,
+// or directly from a django server, and the model structure that system
+// has.
+var classNames = require('classnames');
+var defaults = require('lodash.defaults');
+var React = require('react');
+var Slider = require('react-slick');
+
+var Thumbnail = require('../thumbnail/thumbnail.jsx');
+
+var frameless = require('../../lib/frameless.js');
+
+require('slick-carousel/slick/slick.scss');
+require('slick-carousel/slick/slick-theme.scss');
+require('./carousel.scss');
+
+/**
+ * Displays content in horizontal scrolling box. Example usage: splash page rows.
+ */
+var LegacyCarousel = React.createClass({
+ type: 'LegacyCarousel',
+ propTypes: {
+ items: React.PropTypes.array
+ },
+ getDefaultProps: function () {
+ return {
+ items: require('./carousel.json'),
+ showRemixes: false,
+ showLoves: false
+ };
+ },
+ render: function () {
+ var settings = this.props.settings || {};
+ defaults(settings, {
+ centerMode: false,
+ dots: false,
+ infinite: false,
+ lazyLoad: true,
+ slidesToShow: 5,
+ slidesToScroll: 5,
+ variableWidth: true,
+ responsive: [
+ {breakpoint: frameless.mobile, settings: {
+ arrows: true,
+ slidesToScroll: 1,
+ slidesToShow: 1,
+ centerMode: true
+ }},
+ {breakpoint: frameless.tablet, settings: {
+ slidesToScroll: 2,
+ slidesToShow: 2
+ }},
+ {breakpoint: frameless.desktop, settings: {
+ slidesToScroll: 4,
+ slidesToShow: 4
+ }}
+ ]
+ });
+ var arrows = this.props.items.length > settings.slidesToShow;
+ var classes = classNames(
+ 'carousel',
+ this.props.className
+ );
+ return (
+
+ {this.props.items.map(function (item) {
+ var href = '';
+ switch (item.type) {
+ case 'gallery':
+ href = '/studios/' + item.id + '/';
+ break;
+ case 'project':
+ href = '/projects/' + item.id + '/';
+ break;
+ default:
+ href = '/' + item.type + '/' + item.id + '/';
+ }
+
+ return (
+
+ );
+ }.bind(this))}
+
+ );
+ }
+});
+
+module.exports = LegacyCarousel;
diff --git a/src/components/microworld/microworld.jsx b/src/components/microworld/microworld.jsx
index 87d626e5e..ec21d5a87 100644
--- a/src/components/microworld/microworld.jsx
+++ b/src/components/microworld/microworld.jsx
@@ -3,7 +3,7 @@ var React = require('react');
require('./microworld.scss');
var Box = require('../box/box.jsx');
-var Carousel = require('../carousel/carousel.jsx');
+var LegacyCarousel = require('../carousel/legacy-carousel.jsx');
var IframeModal = require('../modal/iframe/modal.jsx');
var NestedCarousel = require('../nestedcarousel/nestedcarousel.jsx');
@@ -109,7 +109,7 @@ var Microworld = React.createClass({
-
+
);
@@ -129,7 +129,7 @@ var Microworld = React.createClass({
-
+
);
}
@@ -138,7 +138,7 @@ var Microworld = React.createClass({
-
+
);
}
@@ -187,9 +187,9 @@ var Microworld = React.createClass({
moreHref={studioHref ? studioHref : null}>
{/* The two carousels are used to show two rows of projects, one above the
other. This should be probably be changed, to allow better scrolling. */}
-
-
@@ -204,7 +204,7 @@ var Microworld = React.createClass({
key="scratch_design_studio"
moreTitle={studioHref ? 'Visit the studio' : null}
moreHref={studioHref ? studioHref : null}>
-
diff --git a/src/l10n.json b/src/l10n.json
index 078c4646a..eaf9f44ec 100644
--- a/src/l10n.json
+++ b/src/l10n.json
@@ -13,7 +13,6 @@
"general.country": "Country",
"general.create": "Create",
"general.credits": "Credits",
- "general.discuss": "Discuss",
"general.dmca": "DMCA",
"general.emailAddress": "Email Address",
"general.error": "Oops! Something went wrong",
@@ -26,7 +25,6 @@
"general.getStarted": "Get Started",
"general.gender": "Gender",
"general.guidelines": "Community Guidelines",
- "general.help": "Help",
"general.jobs": "Jobs",
"general.joinScratch": "Join Scratch",
"general.legal": "Legal",
@@ -90,9 +88,7 @@
"general.teacherAccounts": "Teacher Accounts",
-
"footer.discuss": "Discussion Forums",
- "footer.help": "Help Page",
"footer.scratchFamily": "Scratch Family",
"form.validationRequired": "This field is required",
diff --git a/src/views/credits/credits.jsx b/src/views/credits/credits.jsx
index 6f9fbfae6..1e4254229 100644
--- a/src/views/credits/credits.jsx
+++ b/src/views/credits/credits.jsx
@@ -228,6 +228,7 @@ var Credits = React.createClass({
Screenhero ,
Sentry ,
Tower ,
+ Transifex ,
and Travis-CI .
@@ -247,7 +248,6 @@ var Credits = React.createClass({
Nagios ,
Nginx ,
Node.js ,
- Pootle ,
PostgreSQL ,
Python ,
Redis ,
diff --git a/src/views/download/download.scss b/src/views/download/download.scss
index 7a82a04ae..fdae55f48 100644
--- a/src/views/download/download.scss
+++ b/src/views/download/download.scss
@@ -53,7 +53,11 @@ $developer-spot: $splash-blue;
.sub-nav-item {
margin: .5rem;
}
-
+
+ .callout {
+ text-align: center;
+ }
+
.download-content {
padding-bottom: 2rem;
}
diff --git a/src/views/download/l10n.json b/src/views/download/l10n.json
index 0a68a4f15..f52aa6bda 100644
--- a/src/views/download/l10n.json
+++ b/src/views/download/l10n.json
@@ -24,7 +24,7 @@
"download.otherVersionsOlder": "If you have an older computer, or cannot install the Scratch 2.0 offline editor, you can try installing Scratch 1.4 .",
"download.otherVersionsAdmin": "If you are a network administrator: a Scratch 2.0 MSI has been created and maintained by a member of the community and hosted for public download here .",
"download.knownIssuesTitle": "Known issues",
- "download.knownIssuesOne": "If your offline editor is crashing directly after Scratch is opened, install the Scratch 2 offline editor again (see step 2 above.) This issue is due to a bug introduced in Adobe Air version 14. (released April 2014).",
+ "download.knownIssuesOne": "If your offline editor is crashing directly after Scratch is opened, install the Scratch 2 offline editor again (see step 2 above). This issue is due to a bug introduced in Adobe Air version 14 (released April 2014).",
"download.knownIssuesTwo": "Graphic effects blocks (in \"Looks\") may slow down projects due to a known Flash bug.",
"download.knownIssuesThree": "The backpack is not yet available.",
"download.reportBugs": "Report Bugs and Glitches",
diff --git a/src/views/jobs/jobs.jsx b/src/views/jobs/jobs.jsx
index 14fcb6571..33f273e8b 100644
--- a/src/views/jobs/jobs.jsx
+++ b/src/views/jobs/jobs.jsx
@@ -38,6 +38,14 @@ var Jobs = React.createClass({
MIT Media Lab, Cambridge, MA (or Remote)
+
+
+ Junior Designer
+
+
+ MIT Media Lab, Cambridge, MA
+
+
diff --git a/src/views/splash/l10n.json b/src/views/splash/l10n.json
index f63a65bb8..856d1c624 100644
--- a/src/views/splash/l10n.json
+++ b/src/views/splash/l10n.json
@@ -19,7 +19,7 @@
"intro.tagLine": "Create stories, games, and animations Share with others around the world",
"intro.tryItOut": "TRY IT OUT",
"intro.description": "A creative learning community with {value} projects shared",
- "intro.defaultDescription": "A creative learning community with over 14 million projects shared",
+ "intro.defaultDescription": "A creative learning community with over 20 million projects shared",
"news.scratchNews": "Scratch News",
diff --git a/src/views/splash/presentation.jsx b/src/views/splash/presentation.jsx
new file mode 100644
index 000000000..9db4df7e1
--- /dev/null
+++ b/src/views/splash/presentation.jsx
@@ -0,0 +1,316 @@
+var injectIntl = require('react-intl').injectIntl;
+var React = require('react');
+
+var sessionActions = require('../../redux/session.js');
+var shuffle = require('../../lib/shuffle.js').shuffle;
+
+var Activity = require('../../components/activity/activity.jsx');
+var AdminPanel = require('../../components/adminpanel/adminpanel.jsx');
+var DropdownBanner = require('../../components/dropdown-banner/banner.jsx');
+var Box = require('../../components/box/box.jsx');
+var Button = require('../../components/forms/button.jsx');
+var Carousel = require('../../components/carousel/carousel.jsx');
+var LegacyCarousel = require('../../components/carousel/legacy-carousel.jsx');
+var Intro = require('../../components/intro/intro.jsx');
+var IframeModal = require('../../components/modal/iframe/modal.jsx');
+var News = require('../../components/news/news.jsx');
+var TeacherBanner = require('../../components/teacher-banner/teacher-banner.jsx');
+var Welcome = require('../../components/welcome/welcome.jsx');
+
+var MediaQuery = require('react-responsive');
+var frameless = require('../../lib/frameless');
+
+require('./splash.scss');
+
+var SplashPresentation = injectIntl(React.createClass({
+ type: 'Splash',
+ propTypes: {
+ sessionStatus: React.PropTypes.string.isRequired,
+ user: React.PropTypes.object.isRequired,
+ isEducator: React.PropTypes.bool.isRequired,
+ isAdmin: React.PropTypes.bool.isRequired,
+ handleDismiss: React.PropTypes.func.isRequired,
+ refreshHomepageCache: React.PropTypes.func.isRequired,
+ shouldShowEmailConfirmation: React.PropTypes.bool.isRequired,
+ emailConfirmationModalOpen: React.PropTypes.bool.isRequired,
+ showEmailConfirmationModal: React.PropTypes.func.isRequired,
+ hideEmailConfirmationModal: React.PropTypes.func.isRequired,
+ shouldShowWelcome: React.PropTypes.bool.isRequired,
+ refreshCacheStatus: React.PropTypes.object.isRequired
+ },
+ getDefaultProps: function () {
+ return {
+ projectCount: 20000000, // gets the shared project count
+ activity: [], // recent social actions taken by users someone is following
+ news: [], // gets news posts from the scratch Tumblr
+ sharedByFollowing: [], // "Projects by Scratchers I'm Following"
+ lovedByFollowing: [], // "Projects Loved by Scratchers I'm Following"
+ inStudiosFollowing: [], // "Projects in Studios I'm Following"
+ featuredGlobal: {} // global homepage rows, such as "Featured Projects"
+ };
+ },
+ componentDidMount: function () {
+ if (this.props.shouldShowEmailConfirmation) window.addEventListener('message', this.onMessage);
+ },
+ componentWillUnmount: function () {
+ window.removeEventListener('message', this.onMessage);
+ },
+ onMessage: function (e) {
+ if (e.origin != window.location.origin) return;
+ if (e.source != this.emailConfirmationiFrame.contentWindow) return;
+ if (e.data == 'resend-done') {
+ this.hideEmailConfirmationModal();
+ } else {
+ var data = JSON.parse(e.data);
+ if (data['action'] === 'leave-page') {
+ window.location.href = data['uri'];
+ }
+ }
+ },
+ renderHomepageRows: function () {
+ var formatMessage = this.props.intl.formatMessage;
+
+ var rows = [
+
+
+ ,
+
+
+
+ ];
+
+ if (this.props.featuredGlobal.curator_top_projects &&
+ this.props.featuredGlobal.curator_top_projects.length > 4) {
+
+ rows.push(
+
+
+
+ );
+ }
+
+ if (this.props.featuredGlobal.scratch_design_studio &&
+ this.props.featuredGlobal.scratch_design_studio.length > 4) {
+
+ rows.push(
+
+
+
+ );
+ }
+
+ if (this.props.user &&
+ this.props.featuredGlobal.community_newest_projects &&
+ this.props.featuredGlobal.community_newest_projects.length > 0) {
+
+ rows.push(
+
+
+
+ );
+ }
+
+ if (this.props.sharedByFollowing && this.props.sharedByFollowing.length > 0) {
+ rows.push(
+
+
+
+ );
+ }
+
+ if (this.props.lovedByFollowing && this.props.lovedByFollowing.length > 0) {
+ rows.push(
+
+
+
+ );
+ }
+
+ if (this.props.inStudiosFollowing && this.props.inStudiosFollowing.length > 0) {
+ rows.push(
+
+
+
+ );
+ }
+
+ rows.push(
+
+
+ ,
+
+
+
+ );
+
+ return rows;
+ },
+ render: function () {
+ var featured = this.renderHomepageRows();
+
+ var formatHTMLMessage = this.props.intl.formatHTMLMessage;
+ var formatNumber = this.props.intl.formatNumber;
+ var formatMessage = this.props.intl.formatMessage;
+ var messages = {
+ 'general.viewAll': formatMessage({id: 'general.viewAll'}),
+ 'news.scratchNews': formatMessage({id: 'news.scratchNews'}),
+ 'welcome.welcomeToScratch': formatMessage({id: 'welcome.welcomeToScratch'}),
+ 'welcome.learn': formatMessage({id: 'welcome.learn'}),
+ 'welcome.tryOut': formatMessage({id: 'welcome.tryOut'}),
+ 'welcome.connect': formatMessage({id: 'welcome.connect'}),
+ 'intro.aboutScratch': formatMessage({id: 'intro.aboutScratch'}),
+ 'intro.forEducators': formatMessage({id: 'intro.forEducators'}),
+ 'intro.forParents': formatMessage({id: 'intro.forParents'}),
+ 'intro.joinScratch': formatMessage({id: 'intro.joinScratch'}),
+ 'intro.seeExamples': formatMessage({id: 'intro.seeExamples'}),
+ 'intro.tagLine': formatHTMLMessage({id: 'intro.tagLine'}),
+ 'intro.tryItOut': formatMessage({id: 'intro.tryItOut'}),
+ 'teacherbanner.greeting': formatMessage({id: 'teacherbanner.greeting'}),
+ 'teacherbanner.subgreeting': formatMessage({id: 'teacherbanner.subgreeting'}),
+ 'teacherbanner.classesButton': formatMessage({id: 'teacherbanner.classesButton'}),
+ 'teacherbanner.resourcesButton': formatMessage({id: 'general.resourcesTitle'}),
+ 'teacherbanner.faqButton': formatMessage({id: 'teacherbanner.faqButton'})
+ };
+ if (this.props.projectCount === 20000000) {
+ messages['intro.description'] = formatHTMLMessage({id: 'intro.defaultDescription'});
+ } else {
+ var count = formatNumber(this.props.projectCount);
+ messages['intro.description'] = formatHTMLMessage({id: 'intro.description'}, {value: count});
+ }
+
+ return (
+
+ {this.props.shouldShowEmailConfirmation ? [
+
+ Confirm your email
+ {' '}to enable sharing.{' '}
+ Having trouble?
+ ,
+
+ ] : []}
+ {this.props.isEducator ? [
+
+ ] : []}
+
+ {this.props.sessionStatus === sessionActions.Status.FETCHED ? (
+ Object.keys(this.props.user).length !== 0 ? [
+
+ {this.props.shouldShowWelcome ? [
+
+ ] : [
+
+ ]}
+
+
+ ] : [
+
+
+
+ ]) : []
+ }
+
+ {featured}
+
+ {this.props.isAdmin ? [
+
+ Tools
+
+
+
+ Homepage Cache
+
+
+
+
+ ] : []}
+
+
+ );
+ }
+}));
+
+module.exports = SplashPresentation;
diff --git a/src/views/splash/splash.jsx b/src/views/splash/splash.jsx
index 5578e7199..aae44b28c 100644
--- a/src/views/splash/splash.jsx
+++ b/src/views/splash/splash.jsx
@@ -6,53 +6,46 @@ var api = require('../../lib/api');
var log = require('../../lib/log');
var render = require('../../lib/render.jsx');
var sessionActions = require('../../redux/session.js');
-var shuffle = require('../../lib/shuffle.js').shuffle;
-var Activity = require('../../components/activity/activity.jsx');
-var AdminPanel = require('../../components/adminpanel/adminpanel.jsx');
-var DropdownBanner = require('../../components/dropdown-banner/banner.jsx');
-var Box = require('../../components/box/box.jsx');
-var Button = require('../../components/forms/button.jsx');
-var Carousel = require('../../components/carousel/carousel.jsx');
-var Intro = require('../../components/intro/intro.jsx');
-var IframeModal = require('../../components/modal/iframe/modal.jsx');
-var News = require('../../components/news/news.jsx');
var Page = require('../../components/page/www/page.jsx');
-var TeacherBanner = require('../../components/teacher-banner/teacher-banner.jsx');
-var Welcome = require('../../components/welcome/welcome.jsx');
-
-var MediaQuery = require('react-responsive');
-var frameless = require('../../lib/frameless');
-
-require('./splash.scss');
+var SplashPresentation = require('./presentation.jsx');
var Splash = injectIntl(React.createClass({
type: 'Splash',
getInitialState: function () {
return {
- projectCount: 14000000, // gets the shared project count
+ projectCount: 20000000, // gets the shared project count
activity: [], // recent social actions taken by users someone is following
news: [], // gets news posts from the scratch Tumblr
- featuredCustom: {}, // custom homepage rows, such as "Projects by Scratchers I'm Following"
+ sharedByFollowing: [], // "Projects by Scratchers I'm Following"
+ lovedByFollowing: [], // "Projects Loved by Scratchers I'm Following"
+ inStudiosFollowing: [], // "Projects in Studios I'm Following"
featuredGlobal: {}, // global homepage rows, such as "Featured Projects"
- showEmailConfirmationModal: true, // flag that determines whether to show banner to request email conf.
+ emailConfirmationModalOpen: false, // flag that determines whether to show banner to request email conf.
refreshCacheStatus: 'notrequested'
};
},
getDefaultProps: function () {
return {
- session: {},
- permissions: {}
+ sessionStatus: sessionActions.Status.NOT_FETCHED,
+ user: {},
+ flags: {},
+ isEducator: false,
+ isAdmin: false
};
},
componentDidUpdate: function (prevProps) {
- if (this.props.session.session.user != prevProps.session.session.user) {
- if (this.props.session.session.user) {
- this.getActivity();
- this.getFeaturedCustom();
+ if (this.props.user != prevProps.user) {
+ if (this.props.user.username) {
+ this.getActivity(this.props.user.username);
+ this.getSharedByFollowing(this.props.user.token);
+ this.getInStudiosFollowing(this.props.user.token);
+ this.getLovedByFollowing(this.props.user.token);
this.getNews();
} else {
- this.setState({featuredCustom: []});
+ this.setState({sharedByFollowing: []});
+ this.setState({lovedByFollowing: []});
+ this.setState({inStudiosFollowing: []});
this.setState({activity: []});
this.setState({news: []});
this.getProjectCount();
@@ -66,33 +59,19 @@ var Splash = injectIntl(React.createClass({
},
componentDidMount: function () {
this.getFeaturedGlobal();
- if (this.props.session.session.user) {
- this.getActivity();
- this.getFeaturedCustom();
+ if (this.props.user.username) {
+ this.getActivity(this.props.user.username);
+ this.getSharedByFollowing(this.props.user.token);
+ this.getInStudiosFollowing(this.props.user.token);
+ this.getLovedByFollowing(this.props.user.token);
this.getNews();
} else {
this.getProjectCount();
}
- if (this.shouldShowEmailConfirmation()) window.addEventListener('message', this.onMessage);
},
- componentWillUnmount: function () {
- window.removeEventListener('message', this.onMessage);
- },
- onMessage: function (e) {
- if (e.origin != window.location.origin) return;
- if (e.source != this.emailConfirmationiFrame.contentWindow) return;
- if (e.data == 'resend-done') {
- this.hideEmailConfirmationModal();
- } else {
- var data = JSON.parse(e.data);
- if (data['action'] === 'leave-page') {
- window.location.href = data['uri'];
- }
- }
- },
- getActivity: function () {
+ getActivity: function (username) {
api({
- uri: '/proxy/users/' + this.props.session.session.user.username + '/activity?limit=5'
+ uri: '/proxy/users/' + username + '/activity?limit=5'
}, function (err, body) {
if (!body) return log.error('No response body');
if (!err) return this.setState({activity: body});
@@ -106,12 +85,31 @@ var Splash = injectIntl(React.createClass({
if (!err) return this.setState({featuredGlobal: body});
}.bind(this));
},
- getFeaturedCustom: function () {
+ getSharedByFollowing: function (token) {
api({
- uri: '/proxy/users/' + this.props.session.session.user.id + '/featured'
+ uri: '/projects/following/users',
+ authentication: token
}, function (err, body) {
if (!body) return log.error('No response body');
- if (!err) return this.setState({featuredCustom: body});
+ if (!err) return this.setState({sharedByFollowing: body});
+ }.bind(this));
+ },
+ getInStudiosFollowing: function (token) {
+ api({
+ uri: '/projects/following/studios',
+ authentication: token
+ }, function (err, body) {
+ if (!body) return log.error('No response body');
+ if (!err) return this.setState({inStudiosFollowing: body});
+ }.bind(this));
+ },
+ getLovedByFollowing: function (token) {
+ api({
+ uri: '/projects/following/loves',
+ authentication: token
+ }, function (err, body) {
+ if (!body) return log.error('No response body');
+ if (!err) return this.setState({lovedByFollowing: body});
}.bind(this));
},
getNews: function () {
@@ -179,256 +177,55 @@ var Splash = injectIntl(React.createClass({
}.bind(this));
},
shouldShowWelcome: function () {
- if (!this.props.session.session.user || !this.props.session.session.flags.show_welcome) return false;
+ if (!this.props.user || !this.props.flags.show_welcome) return false;
return (
- new Date(this.props.session.session.user.dateJoined) >
+ new Date(this.props.user.dateJoined) >
new Date(new Date - 2*7*24*60*60*1000) // Two weeks ago
);
},
shouldShowEmailConfirmation: function () {
return (
- this.props.session.session.user && this.props.session.session.flags.has_outstanding_email_confirmation &&
- this.props.session.session.flags.confirm_email_banner);
- },
- renderHomepageRows: function () {
- var formatMessage = this.props.intl.formatMessage;
-
- var rows = [
-
-
- ,
-
-
-
- ];
-
- if (this.state.featuredGlobal.curator_top_projects &&
- this.state.featuredGlobal.curator_top_projects.length > 4) {
-
- rows.push(
-
-
-
-
- );
- }
-
- if (this.state.featuredGlobal.scratch_design_studio &&
- this.state.featuredGlobal.scratch_design_studio.length > 4) {
-
- rows.push(
-
-
-
-
- );
- }
-
- if (this.props.session.session.user &&
- this.state.featuredGlobal.community_newest_projects &&
- this.state.featuredGlobal.community_newest_projects.length > 0) {
-
- rows.push(
-
-
-
- );
- }
-
- if (this.state.featuredCustom.custom_projects_by_following &&
- this.state.featuredCustom.custom_projects_by_following.length > 0) {
-
- rows.push(
-
-
-
-
- );
- }
- if (this.state.featuredCustom.custom_projects_loved_by_following &&
- this.state.featuredCustom.custom_projects_loved_by_following.length > 0) {
-
- rows.push(
-
-
-
-
- );
- }
-
- if (this.state.featuredCustom.custom_projects_in_studios_following &&
- this.state.featuredCustom.custom_projects_in_studios_following.length > 0) {
-
- rows.push(
-
-
-
-
- );
- }
-
- rows.push(
-
-
-
- ,
-
-
-
-
- );
-
- return rows;
+ this.props.user && this.props.flags.has_outstanding_email_confirmation &&
+ this.props.flags.confirm_email_banner);
},
render: function () {
- var featured = this.renderHomepageRows();
- var homepageCacheState = this.getHomepageRefreshStatus();
-
- var formatHTMLMessage = this.props.intl.formatHTMLMessage;
- var formatNumber = this.props.intl.formatNumber;
- var formatMessage = this.props.intl.formatMessage;
- var messages = {
- 'general.viewAll': formatMessage({id: 'general.viewAll'}),
- 'news.scratchNews': formatMessage({id: 'news.scratchNews'}),
- 'welcome.welcomeToScratch': formatMessage({id: 'welcome.welcomeToScratch'}),
- 'welcome.learn': formatMessage({id: 'welcome.learn'}),
- 'welcome.tryOut': formatMessage({id: 'welcome.tryOut'}),
- 'welcome.connect': formatMessage({id: 'welcome.connect'}),
- 'intro.aboutScratch': formatMessage({id: 'intro.aboutScratch'}),
- 'intro.forEducators': formatMessage({id: 'intro.forEducators'}),
- 'intro.forParents': formatMessage({id: 'intro.forParents'}),
- 'intro.joinScratch': formatMessage({id: 'intro.joinScratch'}),
- 'intro.seeExamples': formatMessage({id: 'intro.seeExamples'}),
- 'intro.tagLine': formatHTMLMessage({id: 'intro.tagLine'}),
- 'intro.tryItOut': formatMessage({id: 'intro.tryItOut'}),
- 'teacherbanner.greeting': formatMessage({id: 'teacherbanner.greeting'}),
- 'teacherbanner.subgreeting': formatMessage({id: 'teacherbanner.subgreeting'}),
- 'teacherbanner.classesButton': formatMessage({id: 'teacherbanner.classesButton'}),
- 'teacherbanner.resourcesButton': formatMessage({id: 'general.resourcesTitle'}),
- 'teacherbanner.faqButton': formatMessage({id: 'teacherbanner.faqButton'})
- };
- if (this.state.projectCount === this.getInitialState().projectCount) {
- messages['intro.description'] = formatHTMLMessage({id: 'intro.defaultDescription'});
- } else {
- var count = formatNumber(this.state.projectCount);
- messages['intro.description'] = formatHTMLMessage({id: 'intro.description'}, {value: count});
- }
+ var showEmailConfirmation = this.shouldShowEmailConfirmation() || false;
+ var showWelcome = this.shouldShowWelcome();
+ var homepageRefreshStatus = this.getHomepageRefreshStatus();
return (
-
- {this.shouldShowEmailConfirmation() ? [
-
- Confirm your email
- {' '}to enable sharing.{' '}
- Having trouble?
- ,
-
- ] : []}
- {this.props.permissions.educator ? [
-
- ] : []}
-
- {this.props.session.status === sessionActions.Status.FETCHED ? (
- this.props.session.session.user ? [
-
- {this.shouldShowWelcome() ? [
-
- ] : [
-
- ]}
-
-
- ] : [
-
-
-
- ]) : []
- }
-
- {featured}
-
-
- Tools
-
-
-
- Homepage Cache
-
-
-
-
-
-
+
);
}
}));
var mapStateToProps = function (state) {
return {
- session: state.session,
- permissions: state.permissions
+ sessionStatus: state.session.status,
+ user: state.session.session.user,
+ flags: state.session.session.flags,
+ isEducator: state.permissions.educator,
+ isAdmin: state.permissions.admin
};
};
diff --git a/src/views/tips/l10n.json b/src/views/tips/l10n.json
index 7100094b5..93324a496 100644
--- a/src/views/tips/l10n.json
+++ b/src/views/tips/l10n.json
@@ -1,6 +1,6 @@
{
"tips.title": "Getting Started",
- "tips.subTitle": "Start making projects in Scratch by trying the online tutorial or downloading the PDF Guide.",
+ "tips.subTitle": "Start making projects in Scratch by trying the online tutorial or downloading the PDF Guide .",
"tips.tryGettingStarted": "Try the Getting Started tutorial",
"tips.tttHeader": "Things to Try",
"tips.tttBody": "What do you want to make with Scratch? For each activity, you can try the Tutorial , download a set of Activity Cards , or view the Educator Guide .",
diff --git a/src/views/tips/tips.jsx b/src/views/tips/tips.jsx
index da9295b50..12012f531 100644
--- a/src/views/tips/tips.jsx
+++ b/src/views/tips/tips.jsx
@@ -109,7 +109,7 @@ var Tips = injectIntl(React.createClass({
diff --git a/test/integration/README.md b/test/integration/README.md
index 413bbdc6a..25b25c673 100644
--- a/test/integration/README.md
+++ b/test/integration/README.md
@@ -15,5 +15,12 @@
* To run a single file from the command-line: `$ node_modules/.bin/tap ./test/integration/smoke-testing/filename.js --timeout=3600`
* The timeout var is for the length of the entire tap test-suite; if you are getting a timeout error, you may need to adjust this value (some of the Selenium tests take a while to run)
+### Configuration
+
+| Variable | Default | Description |
+| --------------------- | --------------------- | --------------------------------------------------------- |
+| `SMOKE_USERNAME` | `None` | Username for Scratch user you're signing in with to test |
+| `SMOKE_PASSWORD` | `None` | Password for Scratch user you're signing in with to test |
+
## Using sauce
* We're still working on setting this up; more info coming shortly
\ No newline at end of file
diff --git a/test/integration/smoke-testing/test_footer_links.js b/test/integration/smoke-testing/test_footer_links.js
index 9af93f3aa..57349d152 100644
--- a/test/integration/smoke-testing/test_footer_links.js
+++ b/test/integration/smoke-testing/test_footer_links.js
@@ -20,7 +20,7 @@ var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
var options = { timeout: 20000 };
//number of tests in the plan
-tap.plan(24);
+tap.plan(25);
tap.tearDown(function () {
//quit the instance of the browser
@@ -115,9 +115,9 @@ tap.test('clickJobsLink', options, function (t) {
// PRESS
tap.test('clickPressLink', options, function (t) {
var linkText = 'Press';
- var expectedHref = 'https://wiki.scratch.mit.edu/wiki/Scratch_Press';
+ var expectedUrl = 'https://wiki.scratch.mit.edu/wiki/Scratch_Press';
clickFooterLinks(linkText).then( function (url) {
- t.equal(url, expectedHref);
+ t.equal(url, expectedUrl);
t.end();
});
});
@@ -147,9 +147,9 @@ tap.test('clickDiscussionForumsLink', options, function (t) {
// SCRATCH WIKI
tap.test('clickScratchWikiLink', options, function (t) {
var linkText = 'Scratch Wiki';
- var expectedHref = 'https://wiki.scratch.mit.edu/wiki/Scratch_Wiki_Home';
+ var expectedUrl = 'https://wiki.scratch.mit.edu/wiki/Scratch_Wiki_Home';
clickFooterLinks(linkText).then( function (url) {
- t.equal(url, expectedHref);
+ t.equal(url, expectedUrl);
t.end();
});
});
@@ -166,10 +166,10 @@ tap.test('clickStatisticsLink', options, function (t) {
// ==== SUPPORT column ====
-// HELP PAGE
-tap.test('clickHelpPageLink', options, function (t) {
- var linkText = 'Help Page';
- var expectedHref = '/help/';
+// TIPS PAGE
+tap.test('clickTipsPageLink', options, function (t) {
+ var linkText = 'Tips';
+ var expectedHref = '/tips';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
@@ -189,7 +189,7 @@ tap.test('clickFAQLink', options, function (t) {
// OFFLINE EDITOR
tap.test('clickOfflineEditorLink', options, function (t) {
var linkText = 'Offline Editor';
- var expectedHref = '/scratch2download/';
+ var expectedHref = '/download';
clickFooterLinks(linkText).then( function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
@@ -206,12 +206,22 @@ tap.test('clickContactUsLink', options, function (t) {
});
});
+// SCRATCH STORE
+tap.test('clickScratchStoreLink', options, function (t) {
+ var linkText = 'Scratch Store';
+ var expectedUrl = 'https://scratch-foundation.myshopify.com/';
+ clickFooterLinks(linkText).then( function (url) {
+ t.equal(url, expectedUrl);
+ t.end();
+ });
+});
+
// DONATE
tap.test('clickDonateLink', options, function (t) {
var linkText = 'Donate';
- var expectedHref = 'https://secure.donationpay.org/scratchfoundation/';
+ var expectedUrl = 'https://secure.donationpay.org/scratchfoundation/';
clickFooterLinks(linkText).then( function (url) {
- t.equal(url.substr(-expectedHref.length), expectedHref);
+ t.equal(url, expectedUrl);
t.end();
});
});
@@ -253,9 +263,9 @@ tap.test('clickDMCALink', options, function (t) {
// SCRATCH ED (SCRATCHED)
tap.test('clickScratchEdLink', options, function (t) {
var linkText = 'ScratchEd';
- var expectedHref = 'http://scratched.gse.harvard.edu/';
+ var expectedUrl = 'http://scratched.gse.harvard.edu/';
clickFooterLinks(linkText).then( function (url) {
- t.equal(url, expectedHref);
+ t.equal(url, expectedUrl);
t.end();
});
});
@@ -263,9 +273,9 @@ tap.test('clickScratchEdLink', options, function (t) {
// SCRATCH JR (SCRATCHJR)
tap.test('clickScratchJrLink', options, function (t) {
var linkText = 'ScratchJr';
- var expectedHref = 'http://www.scratchjr.org/';
+ var expectedUrl = 'http://www.scratchjr.org/';
clickFooterLinks(linkText).then( function (url) {
- t.equal(url, expectedHref);
+ t.equal(url, expectedUrl);
t.end();
});
});
@@ -273,9 +283,9 @@ tap.test('clickScratchJrLink', options, function (t) {
// SCRATCH DAY
tap.test('clickScratchDayLink', options, function (t) {
var linkText = 'Scratch Day';
- var expectedHref = 'https://day.scratch.mit.edu/';
+ var expectedUrl = 'https://day.scratch.mit.edu/';
clickFooterLinks(linkText).then( function (url) {
- t.equal(url, expectedHref);
+ t.equal(url, expectedUrl);
t.end();
});
});
@@ -293,9 +303,9 @@ tap.test('clickScratchConferenceLink', options, function (t) {
// SCRATCH FOUNDATION
tap.test('clickScratchFoundationLink', options, function (t) {
var linkText = 'Scratch Foundation';
- var expectedHref = 'https://www.scratchfoundation.org/';
+ var expectedUrl = 'https://www.scratchfoundation.org/';
clickFooterLinks(linkText).then( function (url) {
- t.equal(url, expectedHref);
+ t.equal(url, expectedUrl);
t.end();
});
});
diff --git a/test/integration/smoke-testing/test_navbar_links.js b/test/integration/smoke-testing/test_navbar_links.js
index 42ac458e7..20e94ace5 100644
--- a/test/integration/smoke-testing/test_navbar_links.js
+++ b/test/integration/smoke-testing/test_navbar_links.js
@@ -18,7 +18,7 @@ var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome()).build();
//number of tests in the plan
-tap.plan(8);
+tap.plan(7);
tap.tearDown(function () {
//quit the instance of the browser
@@ -57,9 +57,9 @@ tap.test('checkExploreLinkWhenSignedOut', function (t) {
});
});
-tap.test('checkDiscussLinkWhenSignedOut', function (t) {
- var xPathLink = '//li[contains(@class, "link") and contains(@class, "discuss")]/a';
- var expectedHref = '/discuss';
+tap.test('checkTipsLinkWhenSignedOut', function (t) {
+ var xPathLink = '//li[contains(@class, "link") and contains(@class, "tips")]/a';
+ var expectedHref = '/tips';
driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
.then( function (element) {
return element.getAttribute('href');})
@@ -81,18 +81,6 @@ tap.test('checkAboutLinkWhenSignedOut', function (t) {
});
});
-tap.test('checkHelpLinkWhenSignedOut', function (t) {
- var xPathLink = '//li[contains(@class, "link") and contains(@class, "help")]/a';
- var expectedHref = '/help';
- driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
- .then( function (element) {
- return element.getAttribute('href');})
- .then( function (url) {
- t.equal(url.substr(-expectedHref.length), expectedHref);
- t.end();
- });
-});
-
// ==== Search bar ====
tap.test('checkSearchBar', function (t) {
diff --git a/test/integration/smoke-testing/test_signing_in_and_my_stuff.js b/test/integration/smoke-testing/test_signing_in_and_my_stuff.js
new file mode 100644
index 000000000..2cf21e023
--- /dev/null
+++ b/test/integration/smoke-testing/test_signing_in_and_my_stuff.js
@@ -0,0 +1,126 @@
+/*
+ * Tests signing in & My Stuff according to smoke-tests at:
+ *
+ * https://github.com/LLK/scratchr2/wiki/Smoke-Testing-Test-Cases
+ *
+ */
+
+var username = process.env.SMOKE_USERNAME;
+var password = process.env.SMOKE_PASSWORD;
+
+var tap = require('tap');
+const test = tap.test;
+const webdriver = require('selenium-webdriver');
+const By = webdriver.By;
+const until = webdriver.until;
+
+const driver = new webdriver.Builder()
+ .forBrowser('chrome')
+ .build();
+
+const findByXpath = (xpath) => {
+ return driver.wait(until.elementLocated(By.xpath(xpath), 5 * 1000));
+};
+
+const clickXpath = (xpath) => {
+ return findByXpath(xpath).then(el => el.click());
+};
+
+const clickText = (text) => {
+ return clickXpath(`//*[contains(text(), '${text}')]`);
+};
+
+const clickButton = (text) => {
+ return clickXpath(`//button[contains(text(), '${text}')]`);
+};
+
+var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
+var url = rootUrl + '/users/anyuser';
+
+tap.plan(5);
+
+tap.tearDown(function () {
+ driver.quit();
+});
+
+tap.beforeEach(function () {
+ return driver.get(url);
+});
+
+test('Sign in to Scratch using scratchr2 navbar', t => {
+ clickText('Sign in')
+ .then(() => findByXpath('//input[@id="login_dropdown_username"]'))
+ .then((element) => element.sendKeys(username))
+ .then(() => findByXpath('//input[@name="password"]'))
+ .then((element) => element.sendKeys(password))
+ .then(() => clickButton('Sign in'))
+ .then(() => findByXpath('//li[contains(@class, "logged-in-user")'
+ + 'and contains(@class, "dropdown")]/span'))
+ .then((element) => element.getText('span'))
+ .then((text) => t.match(text.toLowerCase(), username.substring(0,10).toLowerCase(),
+ 'first part of username should be displayed in navbar'))
+ .then(() => t.end());
+});
+
+test('Sign in to Scratch & verify My Stuff structure (tabs, title)', t => {
+ clickXpath('//a[@class="mystuff-icon"]')
+ .then(() => findByXpath('//div[@class="box-head"]/h2'))
+ .then((element) => element.getText('h2'))
+ .then((text) => t.equal('My Stuff', text, 'title should be My Stuff'))
+ .then(() => findByXpath('//li[@data-tab="projects"]/a'))
+ .then((element) => element.getText('a'))
+ .then((text) => t.match(text, 'All Projects', 'All Projects tab should be present'))
+ .then(() => findByXpath('//li[@data-tab="shared"]/a'))
+ .then((element) => element.getText('a'))
+ .then((text) => t.match(text, 'Shared Projects', 'Shared Projects tab should be present'))
+ .then(() => findByXpath('//li[@data-tab="unshared"]/a'))
+ .then((element) => element.getText('a'))
+ .then((text) => t.match(text, 'Unshared Projects', 'Unshared Projects tab should be present'))
+ .then(() => findByXpath('//li[@data-tab="galleries"]/a'))
+ .then((element) => element.getText('a'))
+ .then((text) => t.match(text, 'My Studios', 'My Studios tab should be present'))
+ .then(() => findByXpath('//li[@data-tab="trash"]/a'))
+ .then((element) => element.getText('a'))
+ .then((text) => t.match(text, 'Trash', 'Trash tab should be present'))
+ .then(() => t.end());
+});
+
+test('clicking See Inside should take you to the editor', t => {
+ clickXpath('//a[@class="mystuff-icon"]')
+ .then(() => findByXpath('//a[@data-control="edit"]'))
+ .then((element) => element.getText('span'))
+ .then((text) => t.equal(text, 'See inside', 'there should be a "See inside" button'))
+ .then(() => clickXpath('//a[@data-control="edit"]'))
+ .then(() => driver.getCurrentUrl())
+ .then( function (url) {
+ var expectedUrl = '/#editor';
+ t.equal(url.substr(-expectedUrl.length), expectedUrl, 'after clicking, the URL should end in #editor');
+ })
+ .then(() => t.end());
+});
+
+test('clicking a project title should take you to the project page', t => {
+ clickXpath('//a[@class="mystuff-icon"]')
+ .then(() => clickXpath('//a[@data-control="edit"]'))
+ .then(() => driver.getCurrentUrl())
+ .then( function (url) {
+ var expectedUrlRegExp = new RegExp('/projects/.*[0-9].*/?');
+ t.match(url, expectedUrlRegExp, 'after clicking, the URL should end in projects/PROJECT_ID/');
+ })
+ .then(() => t.end());
+});
+
+test('Add To button should bring up a list of studios', t => {
+ clickXpath('//a[@class="mystuff-icon"]')
+ .then(() => findByXpath('//div[@data-control="add-to"]'))
+ .then((element) => element.getText('span'))
+ .then((text) => t.equal(text, 'Add to', 'there should be an "Add to" button'))
+ .then(() => clickXpath('//div[@data-control="add-to"]'))
+ .then(() => findByXpath('//div[@class="dropdown-menu"]/ul/li'))
+ .then((element) => element.getText('span'))
+ .then( function (text) {
+ var expectedRegExp = new RegExp('.+');
+ t.match(text, expectedRegExp, 'the dropdown menu should have at least 1 text item in it');
+ })
+ .then(() => t.end());
+});
diff --git a/test/integration/smoke-testing/test_signing_in_homepage.js b/test/integration/smoke-testing/test_signing_in_homepage.js
new file mode 100644
index 000000000..c042b6291
--- /dev/null
+++ b/test/integration/smoke-testing/test_signing_in_homepage.js
@@ -0,0 +1,51 @@
+var username = process.env.SMOKE_USERNAME;
+var password = process.env.SMOKE_PASSWORD;
+
+var tap = require('tap');
+const test = tap.test;
+const webdriver = require('selenium-webdriver');
+const By = webdriver.By;
+const until = webdriver.until;
+
+const driver = new webdriver.Builder()
+ .forBrowser('chrome')
+ .build();
+
+const findByXpath = (xpath) => {
+ return driver.wait(until.elementLocated(By.xpath(xpath), 5 * 1000));
+};
+
+const clickXpath = (xpath) => {
+ return findByXpath(xpath).then(el => el.click());
+};
+
+const clickText = (text) => {
+ return clickXpath(`//*[contains(text(), '${text}')]`);
+};
+
+var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
+
+tap.plan(1);
+
+tap.tearDown(function () {
+ driver.quit();
+});
+
+tap.beforeEach(function () {
+ return driver.get(rootUrl);
+});
+
+test('Sign in to Scratch using scratch-www navbar', t => {
+ clickText('Sign in')
+ .then(() => findByXpath('//input[@id="frc-username-1088"]'))
+ .then((element) => element.sendKeys(username))
+ .then(() => findByXpath('//input[@id="frc-password-1088"]'))
+ .then((element) => element.sendKeys(password))
+ .then(() => clickXpath('//button[contains(@class, "button") and '
+ + 'contains(@class, "submit-button") and contains(@class, "white")]'))
+ .then(() => findByXpath('//span[@class="profile-name"]'))
+ .then((element) => element.getText())
+ .then((text) => t.match(text.toLowerCase(), username.substring(0,10).toLowerCase(),
+ 'first part of username should be displayed in navbar'))
+ .then(() => t.end());
+});