Merge pull request #1789 from LLK/hotfix/es6-upgrade

[Master] Upgrade to ES6, Take II
This commit is contained in:
Ray Schamp 2018-01-30 16:29:51 -05:00 committed by GitHub
commit 51533bbf39
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
188 changed files with 13697 additions and 11511 deletions

3
.babelrc Normal file
View file

@ -0,0 +1,3 @@
{
"presets": ["es2015", "react"],
}

View file

@ -1,29 +0,0 @@
{
"parser": "babel-eslint",
"rules": {
"curly": [2, "multi-line"],
"eol-last": [2],
"indent": [2, 4],
"linebreak-style": [2, "unix"],
"max-len": [2, 120, 4, {"ignoreUrls": true}],
"no-trailing-spaces": [2, { "skipBlankLines": true }],
"no-unused-vars": [2, {"args": "after-used", "varsIgnorePattern": "^_"}],
"quotes": [2, "single"],
"semi": [2, "always"],
"space-before-function-paren": [2, "always"],
"strict": [2, "never"]
},
"env": {
"browser": true,
"es6": true,
"node": true
},
"globals": {
"formatMessage": true
},
"plugins": [
"react",
"json"
],
"extends": "eslint:recommended"
}

4
.eslintrc.js Normal file
View file

@ -0,0 +1,4 @@
module.exports = {
extends: ['scratch', 'scratch/node'],
plugins: ['json']
};

View file

@ -133,3 +133,9 @@ file_filter = localizations/conference-index/<lang>.json
source_file = src/views/conference/2018/index/l10n.json source_file = src/views/conference/2018/index/l10n.json
source_lang = en source_lang = en
type = KEYVALUEJSON type = KEYVALUEJSON
[scratch-website.preview-faq-l10njson]
file_filter = localizations/preview-faq/<lang>.json
source_file = src/views/preview-faq/l10n.json
source_lang = en
type = KEYVALUEJSON

View file

@ -3,7 +3,7 @@ var defaults = require('lodash.defaults');
var fastlyConfig = require('./lib/fastly-config-methods'); var fastlyConfig = require('./lib/fastly-config-methods');
const languages = require('../languages.json'); const languages = require('../languages.json');
var route_json = require('../src/routes.json'); var routeJson = require('../src/routes.json');
const FASTLY_SERVICE_ID = process.env.FASTLY_SERVICE_ID || ''; const FASTLY_SERVICE_ID = process.env.FASTLY_SERVICE_ID || '';
const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME || ''; const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME || '';
@ -15,10 +15,10 @@ var extraAppRoutes = [
// TODO: Should this be added for every route? // TODO: Should this be added for every route?
'/\\?', '/\\?',
// View html // View html
'/[^\/]*\.html$' '/[^/]*.html$'
]; ];
var routes = route_json.map(function (route) { var routes = routeJson.map(function (route) {
return defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route); return defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route);
}); });
@ -28,9 +28,9 @@ async.auto({
if (err) return cb(err); if (err) return cb(err);
// Validate latest version before continuing // Validate latest version before continuing
if (response.active || response.locked) { if (response.active || response.locked) {
fastly.cloneVersion(response.number, function (err, response) { fastly.cloneVersion(response.number, function (e, resp) {
if (err) return cb('Failed to clone latest version: ' + err); if (e) return cb('Failed to clone latest version: ' + e);
cb(null, response.number); cb(null, resp.number);
}); });
} else { } else {
cb(null, response.number); cb(null, response.number);
@ -46,11 +46,11 @@ async.auto({
var recvCondition = '' + var recvCondition = '' +
'if (' + notPassStatement + ') {\n' + 'if (' + notPassStatement + ') {\n' +
' set req.backend = F_s3;\n' + ' set req.backend = F_s3;\n' +
' set req.http.host = \"' + S3_BUCKET_NAME + '\";\n' + ' set req.http.host = "' + S3_BUCKET_NAME + '";\n' +
'} else {\n' + '} else {\n' +
' if (!req.http.Fastly-FF) {\n' + ' if (!req.http.Fastly-FF) {\n' +
' if (req.http.X-Forwarded-For) {\n' + ' if (req.http.X-Forwarded-For) {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For \", \" client.ip;\n' + ' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For ", " client.ip;\n' +
' } else {\n' + ' } else {\n' +
' set req.http.Fastly-Temp-XFF = client.ip;\n' + ' set req.http.Fastly-Temp-XFF = client.ip;\n' +
' }\n' + ' }\n' +
@ -171,20 +171,19 @@ async.auto({
if (err) return cb(err); if (err) return cb(err);
cb(null, headers); cb(null, headers);
}); });
}]}, }]
function (err, results) { }, function (err, results) {
if (err) throw new Error(err); if (err) throw new Error(err);
if (process.env.FASTLY_ACTIVATE_CHANGES) { if (process.env.FASTLY_ACTIVATE_CHANGES) {
fastly.activateVersion(results.version, function (err, response) { fastly.activateVersion(results.version, function (e, resp) {
if (err) throw new Error(err); if (err) throw new Error(e);
process.stdout.write('Successfully configured and activated version ' + response.number + '\n'); process.stdout.write('Successfully configured and activated version ' + resp.number + '\n');
if (process.env.FASTLY_PURGE_ALL) { if (process.env.FASTLY_PURGE_ALL) {
fastly.purgeAll(FASTLY_SERVICE_ID, function (err) { fastly.purgeAll(FASTLY_SERVICE_ID, function (error) {
if (err) throw new Error(err); if (error) throw new Error(error);
process.stdout.write('Purged all.\n'); process.stdout.write('Purged all.\n');
}); });
} }
}); });
}
} }
); });

View file

@ -24,9 +24,9 @@ var FastlyConfigMethods = {
*/ */
getViewPaths: function (routes) { getViewPaths: function (routes) {
return routes.reduce(function (paths, route) { return routes.reduce(function (paths, route) {
var path = route.routeAlias || route.pattern; var p = route.routeAlias || route.pattern;
if (paths.indexOf(path) === -1) { if (paths.indexOf(p) === -1) {
paths.push(path); paths.push(p);
} }
return paths; return paths;
}, []); }, []);
@ -39,7 +39,7 @@ var FastlyConfigMethods = {
* 2. /path/:arg([regex]) :arg is removed, leaving just /path/([regex]) * 2. /path/:arg([regex]) :arg is removed, leaving just /path/([regex])
*/ */
expressPatternToRegex: function (pattern) { expressPatternToRegex: function (pattern) {
pattern = pattern.replace(/(:\w+)(\([^\)]+\))/gi, '$2'); pattern = pattern.replace(/(:\w+)(\([^)]+\))/gi, '$2');
return pattern.replace(/(:\w+)/gi, '.+?'); return pattern.replace(/(:\w+)/gi, '.+?');
}, },
@ -84,7 +84,7 @@ var FastlyConfigMethods = {
return 'redirects/' + route.pattern; return 'redirects/' + route.pattern;
}, },
/** /*
* Returns custom vcl configuration as a string that sets the varnish * Returns custom vcl configuration as a string that sets the varnish
* Time to Live (TTL) for responses that come from s3. * Time to Live (TTL) for responses that come from s3.
* *

View file

@ -19,8 +19,8 @@ module.exports = function (apiKey, serviceId) {
* *
* @return {string} * @return {string}
*/ */
fastly.getFastlyAPIPrefix = function (serviceId, version) { fastly.getFastlyAPIPrefix = function (servId, version) {
return '/service/' + encodeURIComponent(serviceId) + '/version/' + version; return '/service/' + encodeURIComponent(servId) + '/version/' + version;
}; };
/* /*
@ -32,15 +32,15 @@ module.exports = function (apiKey, serviceId) {
if (!this.serviceId) { if (!this.serviceId) {
return cb('Failed to get latest version. No serviceId configured'); return cb('Failed to get latest version. No serviceId configured');
} }
var url = '/service/'+ encodeURIComponent(this.serviceId) +'/version'; var url = '/service/' + encodeURIComponent(this.serviceId) + '/version';
this.request('GET', url, function (err, versions) { this.request('GET', url, function (err, versions) {
if (err) { if (err) {
return cb('Failed to fetch versions: ' + err); return cb('Failed to fetch versions: ' + err);
} }
var latestVersion = versions.reduce(function (latestVersion, version) { var latestVersion = versions.reduce(function (lateVersion, version) {
if (!latestVersion) return version; if (!lateVersion) return version;
if (version.number > latestVersion.number) return version; if (version.number > lateVersion.number) return version;
return latestVersion; return lateVersion;
}); });
return cb(null, latestVersion); return cb(null, latestVersion);
}); });
@ -63,16 +63,16 @@ module.exports = function (apiKey, serviceId) {
var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/condition'; var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/condition';
return this.request('PUT', putUrl, condition, function (err, response) { return this.request('PUT', putUrl, condition, function (err, response) {
if (err && err.statusCode === 404) { if (err && err.statusCode === 404) {
this.request('POST', postUrl, condition, function (err, response) { this.request('POST', postUrl, condition, function (e, resp) {
if (err) { if (e) {
return cb('Failed while inserting condition \"' + condition.statement + '\": ' + err); return cb('Failed while inserting condition "' + condition.statement + '": ' + e);
} }
return cb(null, response); return cb(null, resp);
}); });
return; return;
} }
if (err) { if (err) {
return cb('Failed to update condition \"' + condition.statement + '\": ' + err); return cb('Failed to update condition "' + condition.statement + '": ' + err);
} }
return cb(null, response); return cb(null, response);
}.bind(this)); }.bind(this));
@ -95,11 +95,11 @@ module.exports = function (apiKey, serviceId) {
var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/header'; var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/header';
return this.request('PUT', putUrl, header, function (err, response) { return this.request('PUT', putUrl, header, function (err, response) {
if (err && err.statusCode === 404) { if (err && err.statusCode === 404) {
this.request('POST', postUrl, header, function (err, response) { this.request('POST', postUrl, header, function (e, resp) {
if (err) { if (e) {
return cb('Failed to insert header: ' + err); return cb('Failed to insert header: ' + e);
} }
return cb(null, response); return cb(null, resp);
}); });
return; return;
} }
@ -127,11 +127,11 @@ module.exports = function (apiKey, serviceId) {
var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/response_object'; var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/response_object';
return this.request('PUT', putUrl, responseObj, function (err, response) { return this.request('PUT', putUrl, responseObj, function (err, response) {
if (err && err.statusCode === 404) { if (err && err.statusCode === 404) {
this.request('POST', postUrl, responseObj, function (err, response) { this.request('POST', postUrl, responseObj, function (e, resp) {
if (err) { if (e) {
return cb('Failed to insert response object: ' + err); return cb('Failed to insert response object: ' + e);
} }
return cb(null, response); return cb(null, resp);
}); });
return; return;
} }
@ -166,7 +166,7 @@ module.exports = function (apiKey, serviceId) {
this.request('PUT', url, cb); this.request('PUT', url, cb);
}; };
/** /*
* Upsert a custom vcl file. Attempts a PUT, and falls back * Upsert a custom vcl file. Attempts a PUT, and falls back
* to POST if not there already. * to POST if not there already.
* *
@ -186,16 +186,16 @@ module.exports = function (apiKey, serviceId) {
return this.request('PUT', url, content, function (err, response) { return this.request('PUT', url, content, function (err, response) {
if (err && err.statusCode === 404) { if (err && err.statusCode === 404) {
content.name = name; content.name = name;
this.request('POST', postUrl, content, function (err, response) { this.request('POST', postUrl, content, function (e, resp) {
if (err) { if (e) {
return cb('Failed while adding custom vcl \"' + name + '\": ' + err); return cb('Failed while adding custom vcl "' + name + '": ' + e);
} }
return cb(null, response); return cb(null, resp);
}); });
return; return;
} }
if (err) { if (err) {
return cb('Failed to update custom vcl \"' + name + '\": ' + err); return cb('Failed to update custom vcl "' + name + '": ' + err);
} }
return cb(null, response); return cb(null, response);
}.bind(this)); }.bind(this));

View file

@ -1,10 +1,12 @@
/** /*
* Constructor * Constructor
*/ */
function Handler (route) { const Handler = function (route) {
// Handle redirects // Handle redirects
if (route.redirect) { if (route.redirect) {
return (req, res) => { res.redirect(route.redirect); }; return (req, res) => {
res.redirect(route.redirect);
};
} }
var url = '/' + route.name + '.html'; var url = '/' + route.name + '.html';
@ -12,9 +14,9 @@ function Handler (route) {
req.url = url; req.url = url;
next(); next();
}; };
} };
/** /*
* Export a new instance * Export a new instance
*/ */
module.exports = function (route) { module.exports = function (route) {

View file

@ -1,4 +1,5 @@
{ {
"ab": "Аҧсшәа",
"ar": "العربية", "ar": "العربية",
"an": "Aragonés", "an": "Aragonés",
"ast": "Asturianu", "ast": "Asturianu",

View file

@ -33,24 +33,27 @@
"devDependencies": { "devDependencies": {
"async": "1.5.2", "async": "1.5.2",
"autoprefixer": "6.3.6", "autoprefixer": "6.3.6",
"babel-core": "6.10.4", "babel-cli": "6.26.0",
"babel-eslint": "5.0.4", "babel-core": "6.23.1",
"babel-loader": "6.2.4", "babel-eslint": "8.0.2",
"babel-preset-es2015": "6.9.0", "babel-loader": "7.1.0",
"babel-preset-react": "6.11.1", "babel-preset-es2015": "6.22.0",
"babel-preset-react": "6.22.0",
"cheerio": "1.0.0-rc.2", "cheerio": "1.0.0-rc.2",
"classnames": "2.1.3", "classnames": "2.2.5",
"cookie": "0.2.2", "cookie": "0.2.2",
"copy-webpack-plugin": "0.2.0", "copy-webpack-plugin": "0.2.0",
"create-react-class": "15.6.2",
"css-loader": "0.23.1", "css-loader": "0.23.1",
"eslint": "1.3.1", "eslint": "4.7.1",
"eslint-config-scratch": "5.0.0",
"eslint-plugin-json": "1.2.0", "eslint-plugin-json": "1.2.0",
"eslint-plugin-react": "3.3.1", "eslint-plugin-react": "7.4.0",
"exenv": "1.2.0", "exenv": "1.2.0",
"fastly": "1.2.1", "fastly": "1.2.1",
"file-loader": "0.8.4", "file-loader": "0.8.4",
"formsy-react": "0.18.0", "formsy-react": "0.19.5",
"formsy-react-components": "0.7.1", "formsy-react-components": "0.11.1",
"git-bundle-sha": "0.0.2", "git-bundle-sha": "0.0.2",
"glob": "5.0.15", "glob": "5.0.15",
"google-libphonenumber": "1.0.21", "google-libphonenumber": "1.0.21",
@ -59,6 +62,7 @@
"json-loader": "0.5.2", "json-loader": "0.5.2",
"json2po-stream": "1.0.3", "json2po-stream": "1.0.3",
"keymirror": "0.1.1", "keymirror": "0.1.1",
"lodash.bindall": "4.4.0",
"lodash.clone": "3.0.3", "lodash.clone": "3.0.3",
"lodash.defaultsdeep": "3.10.0", "lodash.defaultsdeep": "3.10.0",
"lodash.isarray": "3.0.4", "lodash.isarray": "3.0.4",
@ -70,21 +74,22 @@
"node-sass": "4.6.1", "node-sass": "4.6.1",
"pako": "0.2.8", "pako": "0.2.8",
"po2icu": "0.0.2", "po2icu": "0.0.2",
"postcss-loader": "0.8.2", "postcss-loader": "2.0.10",
"prop-types": "15.6.0",
"raven-js": "3.0.4", "raven-js": "3.0.4",
"react": "15.1.0", "react": "15.5.4",
"react-dom": "15.0.1", "react-dom": "15.5.4",
"react-intl": "2.1.2", "react-intl": "2.1.2",
"react-modal": "1.5.2", "react-modal": "3.1.11",
"react-onclickoutside": "4.1.1", "react-onclickoutside": "6.7.1",
"react-redux": "4.4.5", "react-redux": "4.4.5",
"react-responsive": "1.1.4", "react-responsive": "3.0.0",
"react-slick": "0.12.2", "react-slick": "0.12.2",
"react-telephone-input": "3.4.5", "react-telephone-input": "3.8.6",
"redux": "3.5.2", "redux": "3.5.2",
"redux-thunk": "2.0.1", "redux-thunk": "2.0.1",
"sass-lint": "1.5.1", "sass-lint": "1.5.1",
"sass-loader": "2.0.1", "sass-loader": "6.0.6",
"scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master", "scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master",
"slick-carousel": "1.5.8", "slick-carousel": "1.5.8",
"source-map-support": "0.3.2", "source-map-support": "0.3.2",
@ -92,8 +97,8 @@
"tap": "7.1.2", "tap": "7.1.2",
"url-loader": "0.5.6", "url-loader": "0.5.6",
"watch": "0.16.0", "watch": "0.16.0",
"webpack": "1.12.14", "webpack": "2.7.0",
"webpack-dev-middleware": "1.2.0", "webpack-dev-middleware": "2.0.4",
"xhr": "2.2.0" "xhr": "2.2.0"
}, },
"nyc": { "nyc": {

11
src/.eslintrc.js Normal file
View file

@ -0,0 +1,11 @@
module.exports = {
root: true,
extends: ['scratch', 'scratch/es6', 'scratch/react'],
env: {
browser: true
},
globals: {
process: true
},
plugins: ['json']
};

View file

@ -1,33 +1,35 @@
var classNames = require('classnames'); const bindAll = require('lodash.bindall');
var React = require('react'); const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./accordion.scss'); require('./accordion.scss');
var Accordion = React.createClass({ class Accordion extends React.Component {
type: 'Accordion', constructor (props) {
getDefaultProps: function () { super(props);
return { bindAll(this, [
titleAs: 'div', 'handleClick'
contentAs: 'div' ]);
}; this.state = {
},
getInitialState: function () {
return {
isOpen: false isOpen: false
}; };
}, }
toggleContent: function () { handleClick (e) {
e.preventDefault();
this.setState({isOpen: !this.state.isOpen}); this.setState({isOpen: !this.state.isOpen});
}, }
render: function () { render () {
var classes = classNames({ const classes = classNames({
'content': true, content: true,
'open': this.state.isOpen open: this.state.isOpen
}); });
return ( return (
<div className="accordion"> <div className="accordion">
<this.props.titleAs className="title" <this.props.titleAs
onClick={this.toggleContent}> className="title"
onClick={this.handleClick}
>
{this.props.title} {this.props.title}
</this.props.titleAs> </this.props.titleAs>
<this.props.contentAs className={classes}> <this.props.contentAs className={classes}>
@ -36,6 +38,16 @@ var Accordion = React.createClass({
</div> </div>
); );
} }
}); }
Accordion.propTypes = {
content: PropTypes.node,
title: PropTypes.string
};
Accordion.defaultProps = {
contentAs: 'div',
titleAs: 'div'
};
module.exports = Accordion; module.exports = Accordion;

View file

@ -1,37 +1,39 @@
var React = require('react'); const bindAll = require('lodash.bindall');
var connect = require('react-redux').connect; const connect = require('react-redux').connect;
const PropTypes = require('prop-types');
const React = require('react');
var Button = require('../forms/button.jsx'); const Button = require('../forms/button.jsx');
require('./adminpanel.scss'); require('./adminpanel.scss');
var AdminPanel = React.createClass({ class AdminPanel extends React.Component {
type: 'AdminPanel', constructor (props) {
getInitialState: function () { super(props);
return { bindAll(this, [
'handleToggleVisibility'
]);
this.state = {
showPanel: false showPanel: false
}; };
}, }
handleToggleVisibility: function (e) { handleToggleVisibility (e) {
e.preventDefault(); e.preventDefault();
this.setState({showPanel: !this.state.showPanel}); this.setState({showPanel: !this.state.showPanel});
}, }
render: function () { render () {
// make sure user is present before checking if they're an admin. Don't show anything if user not an admin. if (!this.props.isAdmin) return false;
var showAdmin = false;
if (this.props.session.session.user) {
showAdmin = this.props.session.session.permissions.admin;
}
if (!showAdmin) return false;
if (this.state.showPanel) { if (this.state.showPanel) {
return ( return (
<div id="admin-panel" className="visible"> <div
className="visible"
id="admin-panel"
>
<span <span
className="toggle" className="toggle"
onClick={this.handleToggleVisibility}> onClick={this.handleToggleVisibility}
>
x x
</span> </span>
<div className="admin-header"> <div className="admin-header">
@ -44,8 +46,15 @@ var AdminPanel = React.createClass({
<dd> <dd>
<ul className="cache-list"> <ul className="cache-list">
<li> <li>
<form method="post" action="/scratch_admin/page/clear-anon-cache/"> <form
<input type="hidden" name="path" value="/" /> action="/scratch_admin/page/clear-anon-cache/"
method="post"
>
<input
name="path"
type="hidden"
value="/"
/>
<div className="button-row"> <div className="button-row">
<span>For anonymous users:</span> <span>For anonymous users:</span>
<Button type="submit"> <Button type="submit">
@ -53,34 +62,39 @@ var AdminPanel = React.createClass({
</Button> </Button>
</div> </div>
</form> </form>
</li> </li>
</ul> </ul>
</dd> </dd>
</dl> </dl>
</div> </div>
</div> </div>
); );
} else {
return (
<div id="admin-panel" className="hidden">
<span
className="toggle"
onClick={this.handleToggleVisibility}>
&gt;
</span>
</div>
);
} }
return (
<div
className="hidden"
id="admin-panel"
>
<span
className="toggle"
onClick={this.handleToggleVisibility}
>
&gt;
</span>
</div>
);
} }
}); }
var mapStateToProps = function (state) { AdminPanel.propTypes = {
return { children: PropTypes.node,
session: state.session isAdmin: PropTypes.bool
};
}; };
var ConnectedAdminPanel = connect(mapStateToProps)(AdminPanel); const mapStateToProps = state => ({
isAdmin: state.permissions.admin
});
const ConnectedAdminPanel = connect(mapStateToProps)(AdminPanel);
module.exports = ConnectedAdminPanel; module.exports = ConnectedAdminPanel;

View file

@ -1,23 +1,22 @@
var React = require('react'); const classNames = require('classnames');
var classNames = require('classnames'); const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
var Avatar = React.createClass({ const Avatar = props => (
type: 'Avatar', <img
propTypes: { className={classNames('avatar', props.className)}
src: React.PropTypes.string {...omit(props, ['className'])}
}, />
getDefaultProps: function () { );
return {
src: '//cdn2.scratch.mit.edu/get_image/user/2584924_24x24.png?v=1438702210.96' Avatar.propTypes = {
}; className: PropTypes.string,
}, src: PropTypes.string
render: function () { };
var classes = classNames(
'avatar', Avatar.defaultProps = {
this.props.className src: '//cdn2.scratch.mit.edu/get_image/user/2584924_24x24.png?v=1438702210.96'
); };
return <img {... this.props} className={classes} />;
}
});
module.exports = Avatar; module.exports = Avatar;

View file

@ -1,40 +1,38 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./box.scss'); require('./box.scss');
var Box = React.createClass({ const Box = props => (
type: 'Box', <div className={classNames('box', props.className)}>
propTypes: { <div className="box-header">
title: React.PropTypes.string.isRequired, <h4>{props.title}</h4>
subtitle: React.PropTypes.string, <h5>{props.subtitle}</h5>
moreTitle: React.PropTypes.string, <p>
moreHref: React.PropTypes.string, <a
moreProps: React.PropTypes.object href={props.moreHref}
}, {...props.moreProps}
render: function () { >
var classes = classNames( {props.moreTitle}
'box', </a>
this.props.className </p>
); </div>
return (
<div className={classes}>
<div className="box-header">
<h4>{this.props.title}</h4>
<h5>{this.props.subtitle}</h5>
<p>
<a href={this.props.moreHref} {...this.props.moreProps}>
{this.props.moreTitle}
</a>
</p>
</div>
<div className="box-content"> <div className="box-content">
{this.props.children} {props.children}
</div> </div>
</div> </div>
); );
}
}); Box.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
moreHref: PropTypes.string,
moreProps: PropTypes.object, // eslint-disable-line react/forbid-prop-types
moreTitle: PropTypes.string,
subtitle: PropTypes.string,
title: PropTypes.string.isRequired
};
module.exports = Box; module.exports = Box;

View file

@ -1,17 +1,18 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./card.scss'); require('./card.scss');
var Card = React.createClass({ const Card = props => (
displayName: 'Card', <div className={classNames(['card', props.className])}>
render: function () { {props.children}
return ( </div>
<div className={classNames(['card', this.props.className])}> );
{this.props.children}
</div> Card.propTypes = {
); children: PropTypes.node,
} className: PropTypes.string
}); };
module.exports = Card; module.exports = Card;

View file

@ -1,95 +1,115 @@
var classNames = require('classnames'); const classNames = require('classnames');
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var React = require('react'); const PropTypes = require('prop-types');
var Slider = require('react-slick'); const React = require('react');
const Slider = require('react-slick');
var Thumbnail = require('../thumbnail/thumbnail.jsx'); const Thumbnail = require('../thumbnail/thumbnail.jsx');
var frameless = require('../../lib/frameless.js'); const frameless = require('../../lib/frameless.js');
require('slick-carousel/slick/slick.scss'); require('slick-carousel/slick/slick.scss');
require('slick-carousel/slick/slick-theme.scss'); require('slick-carousel/slick/slick-theme.scss');
require('./carousel.scss'); require('./carousel.scss');
/** const Carousel = props => {
* Displays content in horizontal scrolling box. Example usage: splash page rows. defaults(props.settings, {
*/ centerMode: false,
var Carousel = React.createClass({ dots: false,
type: 'Carousel', infinite: false,
propTypes: { lazyLoad: true,
items: React.PropTypes.array slidesToShow: 5,
}, slidesToScroll: 5,
getDefaultProps: function () { variableWidth: true,
return { responsive: [
items: require('./carousel.json'), {
showRemixes: false, breakpoint: frameless.mobile,
showLoves: false, settings: {
type: 'project'
};
},
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, arrows: true,
slidesToScroll: 1, slidesToScroll: 1,
slidesToShow: 1, slidesToShow: 1,
centerMode: true centerMode: true
}}, }
{breakpoint: frameless.tablet, settings: { },
{
breakpoint: frameless.tablet,
settings: {
slidesToScroll: 2, slidesToScroll: 2,
slidesToShow: 2 slidesToShow: 2
}}, }
{breakpoint: frameless.desktop, settings: { },
{
breakpoint: frameless.desktop,
settings: {
slidesToScroll: 4, slidesToScroll: 4,
slidesToShow: 4 slidesToShow: 4
}} }
] }
}); ]
var arrows = this.props.items.length > settings.slidesToShow; });
var classes = classNames( const arrows = props.items.length > props.settings.slidesToShow;
'carousel', return (
this.props.className <Slider
); arrows={arrows}
return ( className={classNames('carousel', props.className)}
<Slider className={classes} arrows={arrows} {... settings}> {... props.settings}
{this.props.items.map(function (item) { >
var href = ''; {props.items.map(item => {
switch (this.props.type) { let href = '';
case 'gallery': switch (props.type) {
href = '/studios/' + item.id + '/'; case 'gallery':
break; href = `/studios/${item.id}/`;
case 'project': break;
href = '/projects/' + item.id + '/'; case 'project':
break; href = `/projects/${item.id}/`;
default: break;
href = '/' + item.type + '/' + item.id + '/'; default:
} href = `/${item.type}/${item.id}/`;
}
return ( return (
<Thumbnail key={[this.key, item.id].join('.')} <Thumbnail
showLoves={this.props.showLoves} creator={item.author.username}
showRemixes={this.props.showRemixes} href={href}
type={this.props.type} key={[props.type, item.id].join('.')}
href={href} loves={item.stats.loves}
title={item.title} remixes={item.stats.remixes}
src={item.image} showLoves={props.showLoves}
creator={item.author.username} showRemixes={props.showRemixes}
remixes={item.stats.remixes} src={item.image}
loves={item.stats.loves} /> title={item.title}
); type={props.type}
}.bind(this))} />
</Slider> );
); })}
} </Slider>
}); );
};
Carousel.propTypes = {
className: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.any),
settings: PropTypes.shape({
centerMode: PropTypes.bool,
dots: PropTypes.bool,
infinite: PropTypes.bool,
lazyLoad: PropTypes.bool,
slidesToShow: PropTypes.number,
slidesToScroll: PropTypes.number,
variableWidth: PropTypes.bool,
responsive: PropTypes.array
}),
showLoves: PropTypes.bool,
showRemixes: PropTypes.bool,
type: PropTypes.string
};
Carousel.defaultProps = {
items: require('./carousel.json'),
settings: {},
showRemixes: false,
showLoves: false,
type: 'project'
};
module.exports = Carousel; module.exports = Carousel;

View file

@ -1,97 +1,118 @@
// This component handles json returned via proxy from a django server, // This component handles json returned via proxy from a django server,
// or directly from a django server, and the model structure that system // or directly from a django server, and the model structure that system
// has. // has.
var classNames = require('classnames'); const classNames = require('classnames');
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var React = require('react'); const PropTypes = require('prop-types');
var Slider = require('react-slick'); const React = require('react');
const Slider = require('react-slick');
var Thumbnail = require('../thumbnail/thumbnail.jsx'); const Thumbnail = require('../thumbnail/thumbnail.jsx');
var frameless = require('../../lib/frameless.js'); const frameless = require('../../lib/frameless.js');
require('slick-carousel/slick/slick.scss'); require('slick-carousel/slick/slick.scss');
require('slick-carousel/slick/slick-theme.scss'); require('slick-carousel/slick/slick-theme.scss');
require('./carousel.scss'); require('./carousel.scss');
/** const Carousel = props => {
* Displays content in horizontal scrolling box. Example usage: splash page rows. defaults(props.settings, {
*/ centerMode: false,
var LegacyCarousel = React.createClass({ dots: false,
type: 'LegacyCarousel', infinite: false,
propTypes: { lazyLoad: true,
items: React.PropTypes.array slidesToShow: 5,
}, slidesToScroll: 5,
getDefaultProps: function () { variableWidth: true,
return { responsive: [
items: require('./carousel.json'), {
showRemixes: false, breakpoint: frameless.mobile,
showLoves: false settings: {
};
},
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, arrows: true,
slidesToScroll: 1, slidesToScroll: 1,
slidesToShow: 1, slidesToShow: 1,
centerMode: true centerMode: true
}}, }
{breakpoint: frameless.tablet, settings: { },
{
breakpoint: frameless.tablet,
settings: {
slidesToScroll: 2, slidesToScroll: 2,
slidesToShow: 2 slidesToShow: 2
}}, }
{breakpoint: frameless.desktop, settings: { },
{
breakpoint: frameless.desktop,
settings: {
slidesToScroll: 4, slidesToScroll: 4,
slidesToShow: 4 slidesToShow: 4
}} }
] }
}); ]
var arrows = this.props.items.length > settings.slidesToShow; });
var classes = classNames( const arrows = props.items.length > props.settings.slidesToShow;
'carousel', return (
this.props.className <Slider
); arrows={arrows}
return ( className={classNames('carousel', props.className)}
<Slider className={classes} arrows={arrows} {... settings}> {... props.settings}
{this.props.items.map(function (item) { >
var href = ''; {props.items.map(item => {
switch (item.type) { let href = '';
case 'gallery': switch (item.type) {
href = '/studios/' + item.id + '/'; case 'gallery':
break; href = `/studios/${item.id}/`;
case 'project': break;
href = '/projects/' + item.id + '/'; case 'project':
break; href = `/projects/${item.id}/`;
default: break;
href = '/' + item.type + '/' + item.id + '/'; default:
} href = `/${item.type}/${item.id}/`;
}
return ( return (
<Thumbnail key={[this.key, item.id].join('.')} <Thumbnail
showLoves={this.props.showLoves} creator={item.creator}
showRemixes={this.props.showRemixes} href={href}
type={item.type} key={[props.type, item.id].join('.')}
href={href} loves={item.love_count}
title={item.title} remixes={item.remixers_count}
src={item.thumbnail_url} showLoves={props.showLoves}
creator={item.creator} showRemixes={props.showRemixes}
remixes={item.remixers_count} src={item.thumbnail_url}
loves={item.love_count} /> title={item.title}
); type={item.type}
}.bind(this))} />
</Slider> );
); })}
} </Slider>
}); );
};
module.exports = LegacyCarousel; Carousel.propTypes = {
className: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.any),
settings: PropTypes.shape({
centerMode: PropTypes.bool,
dots: PropTypes.bool,
infinite: PropTypes.bool,
lazyLoad: PropTypes.bool,
slidesToShow: PropTypes.number,
slidesToScroll: PropTypes.number,
variableWidth: PropTypes.bool,
responsive: PropTypes.array
}),
showLoves: PropTypes.bool,
showRemixes: PropTypes.bool,
type: PropTypes.string
};
Carousel.defaultProps = {
items: require('./carousel.json'),
settings: {},
showRemixes: false,
showLoves: false,
type: 'project'
};
module.exports = Carousel;

View file

@ -1,34 +1,33 @@
var classNames = require('classnames'); const classNames = require('classnames');
var FormattedRelative = require('react-intl').FormattedRelative; const FormattedRelative = require('react-intl').FormattedRelative;
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var EmojiText = require('../emoji-text/emoji-text.jsx'); const EmojiText = require('../emoji-text/emoji-text.jsx');
require('./comment.scss'); require('./comment.scss');
var CommentText = React.createClass({ const CommentText = props => (
type: 'CommentText', <div className={classNames('comment-text', props.className)}>
propTypes: { <EmojiText
comment: React.PropTypes.string.isRequired, className="mod-comment"
datetimeCreated: React.PropTypes.string, text={props.comment}
className: React.PropTypes.string />
}, {typeof props.datetimeCreated === 'undefined' ? [] : [
render: function () { <p
var classes = classNames( className="comment-text-timestamp"
'comment-text', key="comment-text-timestamp"
this.props.class >
); <FormattedRelative value={new Date(props.datetimeCreated)} />
return ( </p>
<div className={classes}> ]}
<EmojiText className="mod-comment" text={this.props.comment} /> </div>
{typeof this.props.datetimeCreated !== 'undefined' ? [ );
<p className="comment-text-timestamp">
<FormattedRelative value={new Date(this.props.datetimeCreated)} /> CommentText.propTypes = {
</p> className: PropTypes.string,
] : []} comment: PropTypes.string.isRequired,
</div> datetimeCreated: PropTypes.string
); };
}
});
module.exports = CommentText; module.exports = CommentText;

View file

@ -1,22 +1,29 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./deck.scss'); require('./deck.scss');
var Deck = React.createClass({ const Deck = props => (
displayName: 'Deck', <div className={classNames(['deck', props.className])}>
render: function () { <div className="inner">
return ( <a
<div className={classNames(['deck', this.props.className])}> aria-label="Scratch"
<div className="inner"> href="/"
<a href="/" aria-label="Scratch"> >
<img className="logo" src="/images/logo_sm.png" /> <img
</a> className="logo"
{this.props.children} src="/images/logo_sm.png"
</div> />
</div> </a>
); {props.children}
} </div>
}); </div>
);
Deck.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = Deck; module.exports = Deck;

View file

@ -1,33 +1,29 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./banner.scss'); require('./banner.scss');
/** const Banner = props => (
* Container for messages displayed below the nav bar that can be dismissed <div className={classNames('banner', props.className)}>
* (See: email not confirmed banner) <div className="inner">
*/ {props.children}
var Banner = React.createClass({ {props.onRequestDismiss ? [
type: 'Banner', <a
propTypes: { className="close"
onRequestDismiss: React.PropTypes.func href="#"
}, key="close"
render: function () { onClick={props.onRequestDismiss}
var classes = classNames( >x</a>
'banner', ] : []}
this.props.className </div>
); </div>
return ( );
<div className={classes}>
<div className="inner"> Banner.propTypes = {
{this.props.children} children: PropTypes.node,
{this.props.onRequestDismiss ? [ className: PropTypes.string,
<a className="close" key="close" href="#" onClick={this.props.onRequestDismiss}>x</a> onRequestDismiss: PropTypes.func
] : []} };
</div>
</div>
);
}
});
module.exports = Banner; module.exports = Banner;

View file

@ -1,40 +1,46 @@
var React = require('react'); const bindAll = require('lodash.bindall');
var classNames = require('classnames'); const classNames = require('classnames');
const onClickOutside = require('react-onclickoutside').default;
const PropTypes = require('prop-types');
const React = require('react');
require('./dropdown.scss'); require('./dropdown.scss');
var Dropdown = React.createClass({ class Dropdown extends React.Component {
type: 'Dropdown', constructor (props) {
mixins: [ super(props);
require('react-onclickoutside') bindAll(this, [
], 'handleClickOutside'
propTypes: { ]);
onRequestClose: React.PropTypes.func, }
isOpen: React.PropTypes.bool handleClickOutside () {
},
getDefaultProps: function () {
return {
as: 'div',
isOpen: false
};
},
handleClickOutside: function () {
if (this.props.isOpen) { if (this.props.isOpen) {
this.props.onRequestClose(); this.props.onRequestClose();
} }
}, }
render: function () { render () {
var classes = classNames(
'dropdown',
this.props.className,
{open: this.props.isOpen}
);
return ( return (
<this.props.as className={classes}> <this.props.as
className={classNames('dropdown', this.props.className, {
open: this.props.isOpen
})}
>
{this.props.children} {this.props.children}
</this.props.as> </this.props.as>
); );
} }
}); }
module.exports = Dropdown; Dropdown.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
isOpen: PropTypes.bool,
onRequestClose: PropTypes.func.isRequired
};
Dropdown.defaultProps = {
as: 'div',
isOpen: false
};
module.exports = onClickOutside(Dropdown);

View file

@ -24,8 +24,12 @@
} }
a { a {
background-color: transparent; &:link,
color: $type-white; &:visited,
&:active {
background-color: transparent;
color: $type-white;
}
} }
input { input {

View file

@ -1,33 +1,25 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./emoji-text.scss'); require('./emoji-text.scss');
var EmojiText = React.createClass({ const EmojiText = props => (
type: 'EmojiText', <props.as
propTyes: { className={classNames('emoji-text', props.className)}
text: React.PropTypes.string.isRequired, dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
className: React.PropTypes.string __html: props.text
}, }}
getDefaultProps: function () { />
return { );
as: 'p'
}; EmojiText.propTypes = {
}, className: PropTypes.string,
render: function () { text: PropTypes.string.isRequired
var classes = classNames( };
'emoji-text',
this.props.className EmojiText.defaultProps = {
); as: 'p'
return ( };
<this.props.as
className={classes}
dangerouslySetInnerHTML={{
__html: this.props.text
}}
/>
);
}
});
module.exports = EmojiText; module.exports = EmojiText;

View file

@ -1,26 +1,22 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./flex-row.scss'); require('./flex-row.scss');
var FlexRow = React.createClass({ const FlexRow = props => (
type: 'FlexRow', <props.as className={classNames('flex-row', props.className)}>
getDefaultProps: function () { {props.children}
return { </props.as>
as: 'div' );
};
}, FlexRow.propTypes = {
render: function () { children: PropTypes.node,
var classes = classNames( className: PropTypes.string
'flex-row', };
this.props.className
); FlexRow.defaultProps = {
return ( as: 'div'
<this.props.as className={classes}> };
{this.props.children}
</this.props.as>
);
}
});
module.exports = FlexRow; module.exports = FlexRow;

View file

@ -1,125 +1,152 @@
var React = require('react'); const React = require('react');
var FlexRow = require('../../../flex-row/flex-row.jsx'); const FlexRow = require('../../../flex-row/flex-row.jsx');
var FooterBox = require('../../container/footer.jsx'); const FooterBox = require('../../container/footer.jsx');
require('../footer.scss'); require('../footer.scss');
var ConferenceFooter = React.createClass({ const ConferenceFooter = () => (
type: 'ConferenceFooter', <FooterBox>
render: function () { <div className="collaborators">
return ( <h4>Sponsors</h4>
<FooterBox> <FlexRow as="ul">
<div className="collaborators"> <li className="odl">
<h4>Sponsors</h4> <a href="https://odl.mit.edu/">
<img
alt="MIT Office of Digital Learning"
src="/images/conference/footer/mit-odl.png"
/>
</a>
</li>
<li className="intel">
<a href="http://www.intel.com/content/www/us/en/homepage.html">
<img
alt="Intel"
src="/images/conference/footer/intel.png"
/>
</a>
</li>
<li className="lego">
<a href="http://www.legofoundation.com/">
<img
alt="LEGO Foundation"
src="/images/conference/footer/lego-foundation.png"
/>
</a>
</li>
<li className="google">
<a href="http://www.google.com/">
<img
alt="Google"
src="/images/conference/footer/google.png"
/>
</a>
</li>
<li className="siegel">
<a href="http://www.siegelendowment.org/">
<img
alt="Siegel Family Endowment"
src="/images/conference/footer/siegel-endowment.png"
/>
</a>
</li>
<li className="nostarch">
<a href="https://www.nostarch.com/">
<img
alt="No Starch Press"
src="/images/conference/footer/no-starch.png"
/>
</a>
</li>
<li className="scratchfoundation">
<a href="http://www.scratchfoundation.org/">
<img
alt="Scratch Foundation"
src="/images/conference/footer/scratch-foundation.png"
/>
</a>
</li>
</FlexRow>
</div>
<FlexRow className="scratch-links">
<div className="family">
<h4>Scratch Family</h4>
<FlexRow>
<FlexRow
as="ul"
className="column"
>
<li>
<a href="https://scratch.mit.edu">Scratch</a>
</li>
<li>
<a href="http://www.scratchjr.org/">ScratchJr</a>
</li>
</FlexRow>
<FlexRow
as="ul"
className="column"
>
<li>
<a href="http://www.scratchfoundation.org/">Scratch Foundation</a>
</li>
<li>
<a href="http://scratched.gse.harvard.edu/">ScratchEd</a>
</li>
</FlexRow>
<FlexRow
as="ul"
className="column"
>
<li>
<a href="http://day.scratch.mit.edu">Scratch Day</a>
</li>
</FlexRow>
</FlexRow>
<p className="legal">
Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab.
</p>
</div>
<div className="media">
<div className="contact-us">
<h4>Contact</h4>
<p>
<a href="mailto:help@scratch.mit.edu">
Email Us
</a>
</p>
</div>
<div className="social">
<FlexRow as="ul"> <FlexRow as="ul">
<li className="odl"> <li>
<a href="https://odl.mit.edu/"> <a href="//www.twitter.com/scratch">
<img src="/images/conference/footer/mit-odl.png" <img
alt="MIT Office of Digital Learning" /> alt="scratch twitter"
src="/images/conference/footer/twitter.png"
/>
</a> </a>
</li> </li>
<li className="intel"> <li>
<a href="http://www.intel.com/content/www/us/en/homepage.html"> <a href="//www.facebook.com/scratchteam">
<img src="/images/conference/footer/intel.png" <img
alt="Intel" /> alt="scratch facebook"
src="/images/conference/footer/facebook.png"
/>
</a> </a>
</li> </li>
<li className="lego"> <li>
<a href="http://www.legofoundation.com/"> <a href="http://medium.com/scratchfoundation-blog">
<img src="/images/conference/footer/lego-foundation.png" <img
alt="LEGO Foundation" /> alt="scratch foundation blog"
</a> src="/images/conference/footer/medium.png"
</li> />
<li className="google">
<a href="http://www.google.com/">
<img src="/images/conference/footer/google.png"
alt="Google" />
</a>
</li>
<li className="siegel">
<a href="http://www.siegelendowment.org/">
<img src="/images/conference/footer/siegel-endowment.png"
alt="Siegel Family Endowment" />
</a>
</li>
<li className="nostarch">
<a href="https://www.nostarch.com/">
<img src="/images/conference/footer/no-starch.png"
alt="No Starch Press" />
</a>
</li>
<li className="scratchfoundation">
<a href="http://www.scratchfoundation.org/">
<img src="/images/conference/footer/scratch-foundation.png"
alt="Scratch Foundation" />
</a> </a>
</li> </li>
</FlexRow> </FlexRow>
</div> </div>
<FlexRow className="scratch-links"> </div>
<div className="family"> </FlexRow>
<h4>Scratch Family</h4> </FooterBox>
<FlexRow> );
<FlexRow as="ul" className="column">
<li>
<a href="https://scratch.mit.edu" target="_blank">Scratch</a>
</li>
<li>
<a href="http://www.scratchjr.org/" target="_blank">ScratchJr</a>
</li>
</FlexRow>
<FlexRow as="ul" className="column">
<li>
<a href="http://www.scratchfoundation.org/" target="_blank">Scratch Foundation</a>
</li>
<li>
<a href="http://scratched.gse.harvard.edu/" target="_blank">ScratchEd</a>
</li>
</FlexRow>
<FlexRow as="ul" className="column">
<li>
<a href="http://day.scratch.mit.edu" target="_blank">Scratch Day</a>
</li>
</FlexRow>
</FlexRow>
<p className="legal">
Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab.
</p>
</div>
<div className="media">
<div className="contact-us">
<h4>Contact</h4>
<p>
<a href="mailto:help@scratch.mit.edu" target="_blank">
Email Us
</a>
</p>
</div>
<div className="social">
<FlexRow as="ul">
<li>
<a href="//www.twitter.com/scratch" target="_blank">
<img src="/images/conference/footer/twitter.png" alt="scratch twitter" />
</a>
</li>
<li>
<a href="//www.facebook.com/scratchteam" target="_blank">
<img src="/images/conference/footer/facebook.png" alt="scratch facebook" />
</a>
</li>
<li>
<a href="http://medium.com/scratchfoundation-blog" target="_blank">
<img src="/images/conference/footer/medium.png" alt="scratch foundation blog" />
</a>
</li>
</FlexRow>
</div>
</div>
</FlexRow>
</FooterBox>
);
}
});
module.exports = ConferenceFooter; module.exports = ConferenceFooter;

View file

@ -1,84 +1,100 @@
var React = require('react'); const injectIntl = require('react-intl').injectIntl;
var ReactIntl = require('react-intl'); const intlShape = require('react-intl').intlShape;
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
var injectIntl = ReactIntl.injectIntl; const FlexRow = require('../../../flex-row/flex-row.jsx');
var FormattedMessage = ReactIntl.FormattedMessage; const FooterBox = require('../../container/footer.jsx');
const LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
var FlexRow = require('../../../flex-row/flex-row.jsx');
var FooterBox = require('../../container/footer.jsx');
var LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
require('../footer.scss'); require('../footer.scss');
var ConferenceFooter = React.createClass({ const ConferenceFooter = props => (
type: 'ConferenceFooter', <FooterBox>
render: function () { <FlexRow className="scratch-links">
return ( <div className="family">
<FooterBox> <h4><FormattedMessage id="footer.scratchFamily" /></h4>
<FlexRow className="scratch-links"> <FlexRow>
<div className="family"> <FlexRow
<h4><FormattedMessage id='footer.scratchFamily' /></h4> as="ul"
<FlexRow> className="column"
<FlexRow as="ul" className="column"> >
<li> <li>
<a href="https://scratch.mit.edu" target="_blank">Scratch</a> <a href="https://scratch.mit.edu">Scratch</a>
</li> </li>
<li> <li>
<a href="http://www.scratchjr.org/" target="_blank">ScratchJr</a> <a href="http://www.scratchjr.org/">ScratchJr</a>
</li> </li>
</FlexRow> </FlexRow>
<FlexRow as="ul" className="column"> <FlexRow
<li> as="ul"
<a href="http://www.scratchfoundation.org/" target="_blank">Scratch Foundation</a> className="column"
</li> >
<li> <li>
<a href="http://scratched.gse.harvard.edu/" target="_blank">ScratchEd</a> <a href="http://www.scratchfoundation.org/">Scratch Foundation</a>
</li> </li>
</FlexRow> <li>
<FlexRow as="ul" className="column"> <a href="http://scratched.gse.harvard.edu/">ScratchEd</a>
<li> </li>
<a href="http://day.scratch.mit.edu" target="_blank">Scratch Day</a> </FlexRow>
</li> <FlexRow
</FlexRow> as="ul"
</FlexRow> className="column"
<p className="legal"> >
<FormattedMessage id='general.copyright' /> <li>
</p> <a href="http://day.scratch.mit.edu">Scratch Day</a>
</div> </li>
<div className="media"> </FlexRow>
<div className="contact-us">
<h4>Contact</h4>
<p>
<a href="mailto:help@scratch.mit.edu" target="_blank">
Email Us
</a>
</p>
</div>
<div className="social">
<FlexRow as="ul">
<li>
<a href="//www.twitter.com/scratch" target="_blank">
<img src="/images/conference/footer/twitter.png" alt="scratch twitter" />
</a>
</li>
<li>
<a href="//www.facebook.com/scratchteam" target="_blank">
<img src="/images/conference/footer/facebook.png" alt="scratch facebook" />
</a>
</li>
<li>
<a href="http://medium.com/scratchfoundation-blog" target="_blank">
<img src="/images/conference/footer/medium.png" alt="scratch foundation blog" />
</a>
</li>
</FlexRow>
</div>
</div>
</FlexRow> </FlexRow>
<LanguageChooser locale={this.props.intl.locale} /> <p className="legal">
</FooterBox> <FormattedMessage id="general.copyright" />
); </p>
} </div>
}); <div className="media">
<div className="contact-us">
<h4>Contact</h4>
<p>
<a href="mailto:help@scratch.mit.edu">
Email Us
</a>
</p>
</div>
<div className="social">
<FlexRow as="ul">
<li>
<a href="//www.twitter.com/scratch">
<img
alt="scratch twitter"
src="/images/conference/footer/twitter.png"
/>
</a>
</li>
<li>
<a href="//www.facebook.com/scratchteam">
<img
alt="scratch facebook"
src="/images/conference/footer/facebook.png"
/>
</a>
</li>
<li>
<a href="http://medium.com/scratchfoundation-blog">
<img
alt="scratch foundation blog"
src="/images/conference/footer/medium.png"
/>
</a>
</li>
</FlexRow>
</div>
</div>
</FlexRow>
<LanguageChooser locale={props.intl.locale} />
</FooterBox>
);
ConferenceFooter.propTypes = {
intl: intlShape
};
module.exports = injectIntl(ConferenceFooter); module.exports = injectIntl(ConferenceFooter);

View file

@ -1,84 +1,146 @@
var React = require('react'); const FormattedMessage = require('react-intl').FormattedMessage;
var ReactIntl = require('react-intl'); const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const React = require('react');
var injectIntl = ReactIntl.injectIntl; const FlexRow = require('../../../flex-row/flex-row.jsx');
var FormattedMessage = ReactIntl.FormattedMessage; const FooterBox = require('../../container/footer.jsx');
const LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
var FlexRow = require('../../../flex-row/flex-row.jsx');
var FooterBox = require('../../container/footer.jsx');
var LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
require('../footer.scss'); require('../footer.scss');
var ConferenceFooter = React.createClass({ const ConferenceFooter = props => (
type: 'ConferenceFooter', <FooterBox>
render: function () { <FlexRow className="scratch-links">
return ( <div className="family">
<FooterBox> <h4><FormattedMessage id="footer.scratchFamily" /></h4>
<FlexRow className="scratch-links"> <FlexRow>
<div className="family"> <FlexRow
<h4><FormattedMessage id='footer.scratchFamily' /></h4> as="ul"
<FlexRow> className="column"
<FlexRow as="ul" className="column"> >
<li> <li>
<a href="https://scratch.mit.edu" target="_blank">Scratch</a> <a
</li> href="https://scratch.mit.edu"
<li> rel="noopener noreferrer"
<a href="http://www.scratchjr.org/" target="_blank">ScratchJr</a> target="_blank"
</li> >
</FlexRow> Scratch
<FlexRow as="ul" className="column"> </a>
<li> </li>
<a href="http://www.scratchfoundation.org/" target="_blank">Scratch Foundation</a> <li>
</li> <a
<li> href="http://www.scratchjr.org/"
<a href="http://scratched.gse.harvard.edu/" target="_blank">ScratchEd</a> rel="noopener noreferrer"
</li> target="_blank"
</FlexRow> >
<FlexRow as="ul" className="column"> ScratchJr
<li> </a>
<a href="http://day.scratch.mit.edu" target="_blank">Scratch Day</a> </li>
</li> </FlexRow>
</FlexRow> <FlexRow
</FlexRow> as="ul"
<p className="legal"> className="column"
<FormattedMessage id='general.copyright' /> >
</p> <li>
</div> <a
<div className="media"> href="http://www.scratchfoundation.org/"
<div className="contact-us"> rel="noopener noreferrer"
<h4>Contact</h4> target="_blank"
<p> >
<a href="mailto:conference@scratch.mit.edu" target="_blank"> Scratch Foundation
Email Us </a>
</a> </li>
</p> <li>
</div> <a
<div className="social"> href="http://scratched.gse.harvard.edu/"
<FlexRow as="ul"> rel="noopener noreferrer"
<li> target="_blank"
<a href="//www.twitter.com/scratch" target="_blank"> >
<img src="/images/conference/footer/twitter.png" alt="scratch twitter" /> ScratchEd
</a> </a>
</li> </li>
<li> </FlexRow>
<a href="//www.facebook.com/scratchteam" target="_blank"> <FlexRow
<img src="/images/conference/footer/facebook.png" alt="scratch facebook" /> as="ul"
</a> className="column"
</li> >
<li> <li>
<a href="http://medium.com/scratchfoundation-blog" target="_blank"> <a
<img src="/images/conference/footer/medium.png" alt="scratch foundation blog" /> href="http://day.scratch.mit.edu"
</a> rel="noopener noreferrer"
</li> target="_blank"
</FlexRow> >
</div> Scratch Day
</div> </a>
</li>
</FlexRow>
</FlexRow> </FlexRow>
<LanguageChooser locale={this.props.intl.locale} /> <p className="legal">
</FooterBox> <FormattedMessage id="general.copyright" />
); </p>
} </div>
}); <div className="media">
<div className="contact-us">
<h4>Contact</h4>
<p>
<a
href="mailto:conference@scratch.mit.edu"
rel="noopener noreferrer"
target="_blank"
>
Email Us
</a>
</p>
</div>
<div className="social">
<FlexRow as="ul">
<li>
<a
href="//www.twitter.com/scratch"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="scratch twitter"
src="/images/conference/footer/twitter.png"
/>
</a>
</li>
<li>
<a
href="//www.facebook.com/scratchteam"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="scratch facebook"
src="/images/conference/footer/facebook.png"
/>
</a>
</li>
<li>
<a
href="http://medium.com/scratchfoundation-blog"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="scratch foundation blog"
src="/images/conference/footer/medium.png"
/>
</a>
</li>
</FlexRow>
</div>
</div>
</FlexRow>
<LanguageChooser locale={props.intl.locale} />
</FooterBox>
);
ConferenceFooter.propTypes = {
intl: intlShape
};
module.exports = injectIntl(ConferenceFooter); module.exports = injectIntl(ConferenceFooter);

View file

@ -1,16 +1,16 @@
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./footer.scss'); require('./footer.scss');
var FooterBox = React.createClass({ const FooterBox = props => (
type: 'FooterBox', <div className="inner">
render: function () { {props.children}
return ( </div>
<div className="inner"> );
{this.props.children}
</div> FooterBox.propTypes = {
); children: PropTypes.node
} };
});
module.exports = FooterBox; module.exports = FooterBox;

View file

@ -1,226 +1,225 @@
var React = require('react'); const FormattedMessage = require('react-intl').FormattedMessage;
var ReactIntl = require('react-intl'); const injectIntl = require('react-intl').injectIntl;
var FormattedMessage = ReactIntl.FormattedMessage; const intlShape = require('react-intl').intlShape;
var injectIntl = ReactIntl.injectIntl; const MediaQuery = require('react-responsive').default;
const React = require('react');
var FooterBox = require('../container/footer.jsx'); const FooterBox = require('../container/footer.jsx');
var LanguageChooser = require('../../languagechooser/languagechooser.jsx'); const LanguageChooser = require('../../languagechooser/languagechooser.jsx');
var MediaQuery = require('react-responsive'); const frameless = require('../../../lib/frameless');
var frameless = require('../../../lib/frameless');
require('./footer.scss'); require('./footer.scss');
var Footer = React.createClass({ const Footer = props => (
type: 'Footer', <FooterBox>
render: function () { <MediaQuery maxWidth={frameless.tablet - 1}>
return ( <div className="lists">
<FooterBox> <dl>
<MediaQuery maxWidth={frameless.tablet - 1}> <dd>
<div className="lists"> <a href="/about">
<dl> <FormattedMessage id="general.aboutScratch" />
<dd> </a>
<a href="/about"> </dd>
<FormattedMessage id='general.aboutScratch' /> <dd>
</a> <a href="/jobs">
</dd> <FormattedMessage id="general.jobs" />
<dd> </a>
<a href="/jobs"> </dd>
<FormattedMessage id='general.jobs' /> <dd>
</a> <a href="/contact-us/">
</dd> <FormattedMessage id="general.contactUs" />
<dd> </a>
<a href="/contact-us/"> </dd>
<FormattedMessage id='general.contactUs' /> </dl>
</a> <dl>
</dd> <dd>
</dl> <a href="/terms_of_use">
<dl> <FormattedMessage id="general.termsOfUse" />
<dd> </a>
<a href="/terms_of_use"> </dd>
<FormattedMessage id='general.termsOfUse' /> <dd>
</a> <a href="/privacy_policy">
</dd> <FormattedMessage id="general.privacyPolicy" />
<dd> </a>
<a href="/privacy_policy"> </dd>
<FormattedMessage id='general.privacyPolicy' /> <dd>
</a> <a href="/community_guidelines">
</dd> <FormattedMessage id="general.guidelines" />
<dd> </a>
<a href="/community_guidelines"> </dd>
<FormattedMessage id='general.guidelines' /> </dl>
</a> </div>
</dd> </MediaQuery>
</dl> <MediaQuery minWidth={frameless.tablet}>
</div> <div className="lists">
</MediaQuery> <dl>
<MediaQuery minWidth={frameless.tablet}> <dt>
<div className="lists"> <FormattedMessage id="general.about" />
<dl> </dt>
<dt> <dd>
<FormattedMessage id='general.about' /> <a href="/about">
</dt> <FormattedMessage id="general.aboutScratch" />
<dd> </a>
<a href="/about"> </dd>
<FormattedMessage id='general.aboutScratch' /> <dd>
</a> <a href="/parents/">
</dd> <FormattedMessage id="general.forParents" />
<dd> </a>
<a href="/parents/"> </dd>
<FormattedMessage id='general.forParents' /> <dd>
</a> <a href="/educators">
</dd> <FormattedMessage id="general.forEducators" />
<dd> </a>
<a href="/educators"> </dd>
<FormattedMessage id='general.forEducators' /> <dd>
</a> <a href="/developers">
</dd> <FormattedMessage id="general.forDevelopers" />
<dd> </a>
<a href="/developers"> </dd>
<FormattedMessage id='general.forDevelopers' /> <dd>
</a> <a href="/info/credits">
</dd> <FormattedMessage id="general.credits" />
<dd> </a>
<a href="/info/credits"> </dd>
<FormattedMessage id='general.credits' /> <dd>
</a> <a href="/jobs">
</dd> <FormattedMessage id="general.jobs" />
<dd> </a>
<a href="/jobs"> </dd>
<FormattedMessage id='general.jobs' /> <dd>
</a> <a href="http://wiki.scratch.mit.edu/wiki/Scratch_Press">
</dd> <FormattedMessage id="general.press" />
<dd> </a>
<a href="http://wiki.scratch.mit.edu/wiki/Scratch_Press"> </dd>
<FormattedMessage id='general.press' /> </dl>
</a> <dl>
</dd> <dt>
</dl> <FormattedMessage id="general.community" />
<dl> </dt>
<dt> <dd>
<FormattedMessage id='general.community' /> <a href="/community_guidelines">
</dt> <FormattedMessage id="general.guidelines" />
<dd> </a>
<a href="/community_guidelines"> </dd>
<FormattedMessage id='general.guidelines' /> <dd>
</a> <a href="/discuss/">
</dd> <FormattedMessage id="footer.discuss" />
<dd> </a>
<a href="/discuss/"> </dd>
<FormattedMessage id='footer.discuss' /> <dd>
</a> <a href="https://wiki.scratch.mit.edu/">
</dd> <FormattedMessage id="general.wiki" />
<dd> </a>
<a href="https://wiki.scratch.mit.edu/"> </dd>
<FormattedMessage id='general.wiki' /> <dd>
</a> <a href="/statistics/">
</dd> <FormattedMessage id="general.statistics" />
<dd> </a>
<a href="/statistics/"> </dd>
<FormattedMessage id='general.statistics' /> </dl>
</a>
</dd>
</dl>
<dl> <dl>
<dt> <dt>
<FormattedMessage id='general.support' /> <FormattedMessage id="general.support" />
</dt> </dt>
<dd> <dd>
<a href="/tips"> <a href="/tips">
<FormattedMessage id='general.tips' /> <FormattedMessage id="general.tips" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/info/faq"> <a href="/info/faq">
<FormattedMessage id='general.faq' /> <FormattedMessage id="general.faq" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/download"> <a href="/download">
<FormattedMessage id='general.offlineEditor' /> <FormattedMessage id="general.offlineEditor" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/contact-us/"> <a href="/contact-us/">
<FormattedMessage id='general.contactUs' /> <FormattedMessage id="general.contactUs" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/store"> <a href="/store">
<FormattedMessage id='general.scratchStore' /> <FormattedMessage id="general.scratchStore" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="https://secure.donationpay.org/scratchfoundation/"> <a href="https://secure.donationpay.org/scratchfoundation/">
<FormattedMessage id='general.donate'/> <FormattedMessage id="general.donate" />
</a> </a>
</dd> </dd>
</dl> </dl>
<dl> <dl>
<dt> <dt>
<FormattedMessage id='general.legal'/> <FormattedMessage id="general.legal" />
</dt> </dt>
<dd> <dd>
<a href="/terms_of_use"> <a href="/terms_of_use">
<FormattedMessage id='general.termsOfUse' /> <FormattedMessage id="general.termsOfUse" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/privacy_policy"> <a href="/privacy_policy">
<FormattedMessage id='general.privacyPolicy' /> <FormattedMessage id="general.privacyPolicy" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/DMCA"> <a href="/DMCA">
<FormattedMessage id='general.dmca' /> <FormattedMessage id="general.dmca" />
</a> </a>
</dd> </dd>
</dl> </dl>
<dl> <dl>
<dt> <dt>
<FormattedMessage id='footer.scratchFamily' /> <FormattedMessage id="footer.scratchFamily" />
</dt> </dt>
<dd> <dd>
<a href="http://scratched.gse.harvard.edu/"> <a href="http://scratched.gse.harvard.edu/">
<FormattedMessage id='general.scratchEd' /> <FormattedMessage id="general.scratchEd" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="http://www.scratchjr.org/"> <a href="http://www.scratchjr.org/">
<FormattedMessage id='general.scratchJr' /> <FormattedMessage id="general.scratchJr" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="http://day.scratch.mit.edu/"> <a href="http://day.scratch.mit.edu/">
Scratch Day Scratch Day
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/conference"> <a href="/conference">
<FormattedMessage id='general.scratchConference' /> <FormattedMessage id="general.scratchConference" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="http://www.scratchfoundation.org/"> <a href="http://www.scratchfoundation.org/">
<FormattedMessage id='general.scratchFoundation' /> <FormattedMessage id="general.scratchFoundation" />
</a> </a>
</dd> </dd>
</dl> </dl>
</div> </div>
</MediaQuery> </MediaQuery>
<LanguageChooser locale={this.props.intl.locale} /> <LanguageChooser locale={props.intl.locale} />
<div className="copyright"> <div className="copyright">
<p> <p>
<FormattedMessage id='general.copyright' /> <FormattedMessage id="general.copyright" />
</p> </p>
</div> </div>
</FooterBox> </FooterBox>
); );
}
}); Footer.propTypes = {
intl: intlShape.isRequired
};
module.exports = injectIntl(Footer); module.exports = injectIntl(Footer);

View file

@ -1,22 +1,26 @@
var React = require('react'); const classNames = require('classnames');
var classNames = require('classnames'); const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
require('./button.scss'); require('./button.scss');
var Button = React.createClass({ const Button = props => {
type: 'Button', const classes = classNames('button', props.className);
propTypes: {
}, return (
render: function () { <button
var classes = classNames( className={classes}
'button', {...omit(props, ['className', 'children'])}
this.props.className >
); {props.children}
return ( </button>
<button {... this.props} className={classes} >{this.props.children}</button> );
); };
}
}); Button.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = Button; module.exports = Button;

View file

@ -1,28 +1,28 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./charcount.scss'); require('./charcount.scss');
var CharCount = React.createClass({ const CharCount = props => (
type: 'CharCount', <p
getDefaultProps: function () { className={classNames('char-count', props.className, {
return { overmax: (props.currentCharacters > props.maxCharacters)
maxCharacters: 0, })}
currentCharacters: 0 >
}; {props.currentCharacters}/{props.maxCharacters}
}, </p>
render: function () { );
var classes = classNames(
'char-count', CharCount.propTypes = {
this.props.className, className: PropTypes.string,
{overmax: (this.props.currentCharacters > this.props.maxCharacters)} currentCharacters: PropTypes.number,
); maxCharacters: PropTypes.number
return ( };
<p className={classes}>
{this.props.currentCharacters}/{this.props.maxCharacters} CharCount.defaultProps = {
</p> currentCharacters: 0,
); maxCharacters: 0
} };
});
module.exports = CharCount; module.exports = CharCount;

View file

@ -1,25 +1,25 @@
var classNames = require('classnames'); const classNames = require('classnames');
var FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup; const FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup;
var React = require('react'); const PropTypes = require('prop-types');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; const React = require('react');
var inputHOC = require('./input-hoc.jsx');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss'); require('./row.scss');
require('./checkbox-group.scss'); require('./checkbox-group.scss');
var CheckboxGroup = React.createClass({ const CheckboxGroup = props => (
type: 'CheckboxGroup', <div className={classNames('checkbox-group', props.className)}>
render: function () { <FRCCheckboxGroup
var classes = classNames( className={classNames('checkbox-group', props.className)}
'checkbox-group', {... props}
this.props.className />
); </div>
return ( );
<div className={classes}>
<FRCCheckboxGroup {... this.props} className={classes} /> CheckboxGroup.propTypes = {
</div> className: PropTypes.string
); };
}
});
module.exports = inputHOC(defaultValidationHOC(CheckboxGroup)); module.exports = inputHOC(defaultValidationHOC(CheckboxGroup));

View file

@ -1,23 +1,23 @@
var classNames = require('classnames'); const classNames = require('classnames');
var FRCCheckbox = require('formsy-react-components').Checkbox; const FRCCheckbox = require('formsy-react-components').Checkbox;
var React = require('react'); const PropTypes = require('prop-types');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; const React = require('react');
var inputHOC = require('./input-hoc.jsx');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss'); require('./row.scss');
require('./checkbox.scss'); require('./checkbox.scss');
var Checkbox = React.createClass({ const Checkbox = props => (
type: 'Checkbox', <FRCCheckbox
render: function () { rowClassName={classNames('checkbox-row', props.className)}
var classes = classNames( {...props}
'checkbox-row', />
this.props.className );
);
return ( Checkbox.propTypes = {
<FRCCheckbox rowClassName={classes} {... this.props} /> className: PropTypes.string
); };
}
});
module.exports = inputHOC(defaultValidationHOC(Checkbox)); module.exports = inputHOC(defaultValidationHOC(Checkbox));

View file

@ -1,47 +1,61 @@
var classNames = require('classnames'); const bindAll = require('lodash.bindall');
var Formsy = require('formsy-react'); const classNames = require('classnames');
var omit = require('lodash.omit'); const Formsy = require('formsy-react');
var React = require('react'); const omit = require('lodash.omit');
var validations = require('./validations.jsx').validations; const PropTypes = require('prop-types');
const React = require('react');
for (var validation in validations) { const validations = require('./validations.jsx').validations;
for (const validation in validations) {
Formsy.addValidationRule(validation, validations[validation]); Formsy.addValidationRule(validation, validations[validation]);
} }
var Form = React.createClass({ class Form extends React.Component {
getDefaultProps: function () { constructor (props) {
return { super(props);
noValidate: true, bindAll(this, [
onChange: function () {} 'handleChange'
}; ]);
}, this.state = {
getInitialState: function () {
return {
allValues: {} allValues: {}
}; };
}, }
onChange: function (currentValues, isChanged) { handleChange (currentValues, isChanged) {
this.setState({allValues: omit(currentValues, 'all')}); this.setState({allValues: omit(currentValues, 'all')});
this.props.onChange(currentValues, isChanged); this.props.onChange(currentValues, isChanged);
}, }
render: function () { render () {
var classes = classNames(
'form',
this.props.className
);
return ( return (
<Formsy.Form {... this.props} className={classes} ref="formsy" onChange={this.onChange}> <Formsy.Form
{React.Children.map(this.props.children, function (child) { className={classNames('form', this.props.className)}
ref={form => {
this.formsy = form;
}}
onChange={this.handleChange}
{...this.props}
>
{React.Children.map(this.props.children, child => {
if (!child) return child; if (!child) return child;
if (child.props.name === 'all') { if (child.props.name === 'all') {
return React.cloneElement(child, {value: this.state.allValues}); return React.cloneElement(child, {value: this.state.allValues});
} else {
return child;
} }
}.bind(this))} return child;
})}
</Formsy.Form> </Formsy.Form>
); );
} }
}); }
Form.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
onChange: PropTypes.func
};
Form.defaultProps = {
noValidate: true,
onChange: function () {}
};
module.exports = Form; module.exports = Form;

View file

@ -1,5 +1,6 @@
var Formsy = require('formsy-react'); const Formsy = require('formsy-react');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./general-error.scss'); require('./general-error.scss');
@ -10,13 +11,18 @@ require('./general-error.scss');
* give it a name, and apply your validation error to * give it a name, and apply your validation error to
* the name of the GeneralError component. * the name of the GeneralError component.
*/ */
module.exports = Formsy.HOC(React.createClass({ const GeneralError = props => {
render: function () { if (!props.showError()) return null;
if (!this.props.showError()) return null; return (
return ( <p className="general-error">
<p className="general-error"> {props.getErrorMessage()}
{this.props.getErrorMessage()} </p>
</p> );
); };
}
})); GeneralError.propTypes = {
getErrorMessage: PropTypes.func,
showError: PropTypes.func
};
module.exports = Formsy.HOC(GeneralError);

View file

@ -1,20 +1,32 @@
var React = require('react'); const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
module.exports = function InputComponentMixin (Component) { /**
var InputComponent = React.createClass({ * Higher-order component for building an input field
getDefaultProps: function () { * @param {React.Component} Component an input component
return { * @return {React.Component} a wrapped input component
messages: { */
'general.notRequired': 'Not Required' module.exports = Component => {
} const InputComponent = props => (
}; <Component
}, help={props.required ? null : props.messages['general.notRequired']}
render: function () { {...omit(props, ['messages'])}
return ( />
<Component help={this.props.required ? null : this.props.messages['general.notRequired']} );
{...this.props} />
); InputComponent.propTypes = {
messages: PropTypes.shape({
'general.notRequired': PropTypes.string
}),
required: PropTypes.oneOfType([PropTypes.bool, PropTypes.string])
};
InputComponent.defaultProps = {
messages: {
'general.notRequired': 'Not Required'
} }
}); };
return InputComponent; return InputComponent;
}; };

View file

@ -1,46 +1,57 @@
var classNames = require('classnames'); const bindAll = require('lodash.bindall');
var FRCInput = require('formsy-react-components').Input; const classNames = require('classnames');
var React = require('react'); const FRCInput = require('formsy-react-components').Input;
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; const omit = require('lodash.omit');
var inputHOC = require('./input-hoc.jsx'); const PropTypes = require('prop-types');
const React = require('react');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./input.scss'); require('./input.scss');
require('./row.scss'); require('./row.scss');
var Input = React.createClass({ class Input extends React.Component {
type: 'Input', constructor (props) {
getDefaultProps: function () { super(props);
return {}; bindAll(this, [
}, 'handleInvalid',
getInitialState: function () { 'handleValid'
return { ]);
this.state = {
status: '' status: ''
}; };
}, }
onValid: function () { handleValid () {
this.setState({ this.setState({
status: 'pass' status: 'pass'
}); });
}, }
onInvalid: function () { handleInvalid () {
this.setState({ this.setState({
status: 'fail' status: 'fail'
}); });
}, }
render: function () { render () {
var classes = classNames(
this.state.status,
this.props.className,
{'no-label': (typeof this.props.label === 'undefined')}
);
return ( return (
<FRCInput {... this.props} <FRCInput
className="input" className="input"
rowClassName={classes} rowClassName={classNames(
onValid={this.onValid} this.state.status,
onInvalid={this.onInvalid} /> this.props.className,
{'no-label': (typeof this.props.label === 'undefined')}
)}
onInvalid={this.handleInvalid}
onValid={this.handleValid}
{...omit(this.props, ['className'])}
/>
); );
} }
}); }
Input.propTypes = {
className: PropTypes.string,
label: PropTypes.string
};
module.exports = inputHOC(defaultValidationHOC(Input)); module.exports = inputHOC(defaultValidationHOC(Input));

View file

@ -1,23 +1,33 @@
var allCountries = require('react-telephone-input/lib/country_data').allCountries; const allCountries = require('react-telephone-input/lib/country_data').allCountries;
var classNames = require('classnames'); const classNames = require('classnames');
var ComponentMixin = require('formsy-react-components').ComponentMixin; const ComponentMixin = require('formsy-react-components').ComponentMixin;
var FormsyMixin = require('formsy-react').Mixin; const createReactClass = require('create-react-class');
var React = require('react'); const FormsyMixin = require('formsy-react').Mixin;
var ReactPhoneInput = require('react-telephone-input/lib/withStyles'); const omit = require('lodash.omit');
var Row = require('formsy-react-components').Row; const PropTypes = require('prop-types');
const React = require('react');
const ReactPhoneInput = require('react-telephone-input/lib/withStyles').default;
const Row = require('formsy-react-components').Row;
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
var inputHOC = require('./input-hoc.jsx'); const inputHOC = require('./input-hoc.jsx');
var intl = require('../../lib/intl.jsx'); const intl = require('../../lib/intl.jsx');
var validationHOCFactory = require('./validations.jsx').validationHOCFactory; const validationHOCFactory = require('./validations.jsx').validationHOCFactory;
var allIso2 = allCountries.map(function (country) {return country.iso2;}); const allIso2 = allCountries.map(country => (country.iso2));
require('./row.scss'); require('./row.scss');
require('./phone-input.scss'); require('./phone-input.scss');
var PhoneInput = React.createClass({ const PhoneInput = createReactClass({ // eslint-disable-line react/prefer-es6-class
displayName: 'PhoneInput', displayName: 'PhoneInput',
propTypes: {
className: PropTypes.string,
defaultCountry: PropTypes.string,
disabled: PropTypes.bool,
name: PropTypes.string,
onChange: PropTypes.func
},
mixins: [ mixins: [
FormsyMixin, FormsyMixin,
ComponentMixin ComponentMixin
@ -31,29 +41,34 @@ var PhoneInput = React.createClass({
defaultCountry: 'us' defaultCountry: 'us'
}; };
}, },
onChangeInput: function (number, country) { handleChangeInput: function (number, country) {
var value = {national_number: number, country_code: country}; const value = {
national_number: number,
country_code: country
};
this.setValue(value); this.setValue(value);
this.props.onChange(this.props.name, value); this.props.onChange(this.props.name, value);
}, },
render: function () { render: function () {
var defaultCountry = PhoneInput.getDefaultProps().defaultCountry; let defaultCountry = PhoneInput.getDefaultProps().defaultCountry;
if (allIso2.indexOf(this.props.defaultCountry.toLowerCase()) !== -1) { if (allIso2.indexOf(this.props.defaultCountry.toLowerCase()) !== -1) {
defaultCountry = this.props.defaultCountry.toLowerCase(); defaultCountry = this.props.defaultCountry.toLowerCase();
} }
return ( return (
<Row {... this.getRowProperties()} <Row
htmlFor={this.getId()} htmlFor={this.getId()}
rowClassName={classNames('phone-input', this.props.className)} rowClassName={classNames('phone-input', this.props.className)}
{...this.getRowProperties()}
> >
<div className="input-group"> <div className="input-group">
<ReactPhoneInput className="form-control" <ReactPhoneInput
{... this.props} className="form-control"
defaultCountry={defaultCountry} defaultCountry={defaultCountry}
onChange={this.onChangeInput} disabled={this.isFormDisabled() || this.props.disabled}
id={this.getId()} id={this.getId()}
label={null} label={null}
disabled={this.isFormDisabled() || this.props.disabled} onChange={this.handleChangeInput}
{...omit(this.props, ['className', 'disabled', 'onChange'])}
/> />
{this.renderHelp()} {this.renderHelp()}
{this.renderErrorMessage()} {this.renderErrorMessage()}
@ -63,7 +78,7 @@ var PhoneInput = React.createClass({
} }
}); });
var phoneValidationHOC = validationHOCFactory({ const phoneValidationHOC = validationHOCFactory({
isPhone: <intl.FormattedMessage id="teacherRegistration.validationPhoneNumber" /> isPhone: <intl.FormattedMessage id="teacherRegistration.validationPhoneNumber" />
}); });

View file

@ -1,23 +1,23 @@
var classNames = require('classnames'); const classNames = require('classnames');
var FRCRadioGroup = require('formsy-react-components').RadioGroup; const FRCRadioGroup = require('formsy-react-components').RadioGroup;
var React = require('react'); const PropTypes = require('prop-types');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; const React = require('react');
var inputHOC = require('./input-hoc.jsx');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss'); require('./row.scss');
require('./radio-group.scss'); require('./radio-group.scss');
var RadioGroup = React.createClass({ const RadioGroup = props => (
type: 'RadioGroup', <FRCRadioGroup
render: function () { className={classNames('radio-group', props.className)}
var classes = classNames( {... props}
'radio-group', />
this.props.className );
);
return ( RadioGroup.propTypes = {
<FRCRadioGroup {... this.props} className={classes} /> className: PropTypes.string
); };
}
});
module.exports = inputHOC(defaultValidationHOC(RadioGroup)); module.exports = inputHOC(defaultValidationHOC(RadioGroup));

View file

@ -1,33 +1,31 @@
var classNames = require('classnames'); const classNames = require('classnames');
var defaults = require('lodash.defaultsdeep'); const defaults = require('lodash.defaultsdeep');
var FRCSelect = require('formsy-react-components').Select; const FRCSelect = require('formsy-react-components').Select;
var React = require('react'); const PropTypes = require('prop-types');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; const React = require('react');
var inputHOC = require('./input-hoc.jsx');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss'); require('./row.scss');
require('./select.scss'); require('./select.scss');
var Select = React.createClass({ const Select = props => {
type: 'Select', if (props.required && !props.value) {
propTypes: { props = defaults({}, props, {value: props.options[0].value});
},
render: function () {
var classes = classNames(
'select',
this.props.className
);
var props = this.props;
if (this.props.required && !this.props.value) {
props = defaults({}, this.props, {value: this.props.options[0].value});
}
return (
<div className={classes}>
<FRCSelect {... props} />
</div>
);
} }
}); return (
<div className={classNames('select', props.className)}>
<FRCSelect {...props} />
</div>
);
};
Select.propTypes = {
className: PropTypes.string,
options: PropTypes.arrayOf(PropTypes.any),
required: PropTypes.bool,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
};
module.exports = inputHOC(defaultValidationHOC(Select)); module.exports = inputHOC(defaultValidationHOC(Select));

View file

@ -1,25 +1,25 @@
var classNames = require('classnames'); const classNames = require('classnames');
var FRCTextarea = require('formsy-react-components').Textarea; const FRCTextarea = require('formsy-react-components').Textarea;
var React = require('react'); const omit = require('lodash.omit');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; const PropTypes = require('prop-types');
var inputHOC = require('./input-hoc.jsx'); const React = require('react');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss'); require('./row.scss');
require('./textarea.scss'); require('./textarea.scss');
var TextArea = React.createClass({ const TextArea = props => (
type: 'TextArea', <FRCTextarea
render: function () { className="textarea"
var classes = classNames( rowClassName={classNames('textarea-row', props.className)}
'textarea-row', {...omit(props, ['className'])}
this.props.className />
); );
return (
<FRCTextarea {... this.props} TextArea.propTypes = {
className="textarea" className: PropTypes.string
rowClassName={classes} /> };
);
}
});
module.exports = inputHOC(defaultValidationHOC(TextArea)); module.exports = inputHOC(defaultValidationHOC(TextArea));

View file

@ -1,48 +1,46 @@
var defaults = require('lodash.defaultsdeep'); const defaults = require('lodash.defaultsdeep');
var intl = require('../../lib/intl.jsx'); const intl = require('../../lib/intl.jsx');
var libphonenumber = require('google-libphonenumber'); const libphonenumber = require('google-libphonenumber');
var phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance(); const omit = require('lodash.omit');
var React = require('react'); const phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance();
const PropTypes = require('prop-types');
module.exports = {}; const React = require('react');
module.exports.validations = { module.exports.validations = {
notEquals: function (values, value, neq) { notEquals: (values, value, neq) => (value !== neq),
return value !== neq; notEqualsField: (values, value, field) => (value !== values[field]),
}, isPhone: (values, value) => {
notEqualsField: function (values, value, field) {
return value !== values[field];
},
isPhone: function (values, value) {
if (typeof value === 'undefined') return true; if (typeof value === 'undefined') return true;
if (value && value.national_number === '+') return true; if (value && value.national_number === '+') return true;
try { try {
var parsed = phoneNumberUtil.parse(value.national_number, value.country_code.iso2); const parsed = phoneNumberUtil.parse(value.national_number, value.country_code.iso2);
return phoneNumberUtil.isValidNumber(parsed);
} catch (err) { } catch (err) {
return false; return false;
} }
return phoneNumberUtil.isValidNumber(parsed);
} }
}; };
module.exports.validations.notEqualsUsername = module.exports.validations.notEquals; module.exports.validations.notEqualsUsername = module.exports.validations.notEquals;
module.exports.validationHOCFactory = function (defaultValidationErrors) { module.exports.validationHOCFactory = defaultValidationErrors => (Component => {
return function (Component) { const ValidatedComponent = props => (
var ValidatedComponent = React.createClass({ <Component
render: function () { validationErrors={defaults(
var validationErrors = defaults( {},
{}, defaultValidationErrors,
defaultValidationErrors, props.validationErrors
this.props.validationErrors )}
); {...omit(props, ['validationErrors'])}
return ( />
<Component {...this.props} validationErrors={validationErrors} /> );
);
} ValidatedComponent.propTypes = {
}); validationErrors: PropTypes.object // eslint-disable-line react/forbid-prop-types
return ValidatedComponent;
}; };
};
return ValidatedComponent;
});
module.exports.defaultValidationHOC = module.exports.validationHOCFactory({ module.exports.defaultValidationHOC = module.exports.validationHOCFactory({
isDefaultRequiredValue: <intl.FormattedMessage id="form.validationRequired" /> isDefaultRequiredValue: <intl.FormattedMessage id="form.validationRequired" />

View file

@ -1,79 +1,68 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var Thumbnail = require('../thumbnail/thumbnail.jsx'); const Thumbnail = require('../thumbnail/thumbnail.jsx');
var FlexRow = require('../flex-row/flex-row.jsx'); const FlexRow = require('../flex-row/flex-row.jsx');
require('./grid.scss'); require('./grid.scss');
var Grid = React.createClass({ const Grid = props => (
type: 'Grid', <div className={classNames('grid', props.className)}>
getDefaultProps: function () { <FlexRow>
return { {props.items.map((item, key) => {
items: require('./grid.json'), const href = `/${props.itemType}/${item.id}/`;
itemType: 'projects', if (props.itemType === 'projects') {
showLoves: false, return (
showFavorites: false, <Thumbnail
showRemixes: false, avatar={`https://cdn2.scratch.mit.edu/get_image/user/${item.author.id}_32x32.png`}
showViews: false, creator={item.author.username}
showAvatar: false favorites={item.stats.favorites}
}; href={href}
}, key={key}
render: function () { loves={item.stats.loves}
var classes = classNames( remixes={item.stats.remixes}
'grid', showAvatar={props.showAvatar}
this.props.className showFavorites={props.showFavorites}
); showLoves={props.showLoves}
return ( showRemixes={props.showRemixes}
<div className={classes}> showViews={props.showViews}
<FlexRow> src={item.image}
{this.props.items.map(function (item, key) { title={item.title}
var href = '/' + this.props.itemType + '/' + item.id + '/'; type={'project'}
views={item.stats.views}
/>
);
}
return (
<Thumbnail
href={href}
key={key}
owner={item.owner}
src={item.image}
title={item.title}
type={'gallery'}
/>
);
})}
</FlexRow>
</div>
);
if (this.props.itemType == 'projects') { Grid.propTypes = {
return ( className: PropTypes.string,
<Thumbnail itemType: PropTypes.string,
key={key} items: PropTypes.arrayOf(PropTypes.object)
showLoves={this.props.showLoves} };
showFavorites={this.props.showFavorites}
showRemixes={this.props.showRemixes} Grid.defaultProps = {
showViews={this.props.showViews} items: require('./grid.json'),
showAvatar={this.props.showAvatar} itemType: 'projects',
type={'project'} showLoves: false,
href={href} showFavorites: false,
title={item.title} showRemixes: false,
src={item.image} showViews: false,
avatar={ showAvatar: false
'https://uploads.scratch.mit.edu/users/avatars/' + };
item.author.id +
'.png'
}
creator={item.author.username}
loves={item.stats.loves}
favorites={item.stats.favorites}
remixes={item.stats.remixes}
views={item.stats.views}
/>
);
}
else {
return (
<Thumbnail
key={key}
type={'gallery'}
href={href}
title={item.title}
src={item.image}
srcDefault={'https://uploads.scratch.mit.edu/galleries/thumbnails/default.png'}
owner={item.owner}
/>
);
}
}.bind(this))}
</FlexRow>
</div>
);
}
});
module.exports = Grid; module.exports = Grid;

View file

@ -1,39 +1,34 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
var TitleBanner = require('../../components/title-banner/title-banner.jsx'); const React = require('react');
const TitleBanner = require('../../components/title-banner/title-banner.jsx');
require('./informationpage.scss'); require('./informationpage.scss');
/** /*
* Container for a table of contents * Container for a table of contents
* alongside a long body of text * alongside a long body of text
*/ */
var InformationPage = React.createClass({ const InformationPage = props => (
type: 'InformationPage', <div className="information-page">
propTypes: { <TitleBanner className="masthead">
title: React.PropTypes.string.isRequired <div className="inner">
}, <h1 className="title-banner-h1">
render: function () { {props.title}
var classes = classNames( </h1>
'info-outer',
'inner',
this.props.className
);
return (
<div className="information-page">
<TitleBanner className="masthead">
<div className="inner">
<h1 className="title-banner-h1">
{this.props.title}
</h1>
</div>
</TitleBanner>
<div className={classes}>
{this.props.children}
</div>
</div> </div>
); </TitleBanner>
} <div className={classNames('info-outer', 'inner', props.className)}>
}); {props.children}
</div>
</div>
);
InformationPage.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
title: PropTypes.string.isRequired
};
module.exports = InformationPage; module.exports = InformationPage;

View file

@ -1,111 +1,128 @@
var connect = require('react-redux').connect; const bindAll = require('lodash.bindall');
var React = require('react'); const connect = require('react-redux').connect;
const PropTypes = require('prop-types');
const React = require('react');
var sessionActions = require('../../redux/session.js'); const sessionActions = require('../../redux/session.js');
var IframeModal = require('../modal/iframe/modal.jsx'); const IframeModal = require('../modal/iframe/modal.jsx');
var Registration = require('../registration/registration.jsx'); const Registration = require('../registration/registration.jsx');
require('./intro.scss'); require('./intro.scss');
var Intro = React.createClass({ class Intro extends React.Component {
type: 'Intro', constructor (props) {
getDefaultProps: function () { super(props);
return { bindAll(this, [
messages: { 'handleShowVideo',
'intro.aboutScratch': 'ABOUT SCRATCH', 'handleCloseVideo',
'intro.forEducators': 'FOR EDUCATORS', 'handleJoinClick',
'intro.forParents': 'FOR PARENTS', 'handleCloseRegistration',
'intro.itsFree': 'it\'s free!', 'handleCompleteRegistration'
'intro.joinScratch': 'JOIN SCRATCH', ]);
'intro.seeExamples': 'SEE EXAMPLES', this.state = {
'intro.tagLine': 'Create stories, games, and animations<br /> Share with others around the world',
'intro.tryItOut': 'TRY IT OUT',
'intro.description': 'A creative learning community with <span class="project-count"> ' +
'over 14 million </span>projects shared'
},
session: {}
};
},
getInitialState: function () {
return {
videoOpen: false videoOpen: false
}; };
}, }
showVideo: function () { handleShowVideo () {
this.setState({videoOpen: true}); this.setState({videoOpen: true});
}, }
closeVideo: function () { handleCloseVideo () {
this.setState({videoOpen: false}); this.setState({videoOpen: false});
}, }
handleJoinClick: function (e) { handleJoinClick (e) {
e.preventDefault(); e.preventDefault();
this.setState({'registrationOpen': true}); this.setState({registrationOpen: true});
}, }
closeRegistration: function () { handleCloseRegistration () {
this.setState({'registrationOpen': false}); this.setState({registrationOpen: false});
}, }
completeRegistration: function () { handleCompleteRegistration () {
this.props.dispatch(sessionActions.refreshSession()); this.props.dispatch(sessionActions.refreshSession());
this.closeRegistration(); this.closeRegistration();
}, }
render: function () { render () {
return ( return (
<div className="intro"> <div className="intro">
<div className="content"> <div className="content">
<h1 dangerouslySetInnerHTML={{__html: this.props.messages['intro.tagLine']}}> <h1
</h1> dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
__html: this.props.messages['intro.tagLine']
}}
/>
<div className="sprites"> <div className="sprites">
<a className="sprite sprite-1" href="/projects/editor/?tip_bar=getStarted"> <a
className="sprite sprite-1"
href="/projects/editor/?tip_bar=getStarted"
>
<img <img
alt="Scratch Cat"
className="costume costume-1" className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-a.png" src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-a.png"
alt="Scratch Cat" /> />
<img <img
alt="Scratch Cat"
className="costume costume-2" className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-b.png" src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-b.png"
alt="Scratch Cat" /> />
<div className="circle"></div> <div className="circle" />
<div className="text"> <div className="text">
{this.props.messages['intro.tryItOut']} {this.props.messages['intro.tryItOut']}
</div> </div>
</a> </a>
<a className="sprite sprite-2" href="/starter_projects/"> <a
className="sprite sprite-2"
href="/starter_projects/"
>
<img <img
alt="Tera"
className="costume costume-1" className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-a.png" src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-a.png"
alt="Tera" /> />
<img <img
alt="Tera"
className="costume costume-2" className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-b.png" src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-b.png"
alt="Tera" /> />
<div className="circle"></div> <div className="circle" />
<div className="text"> <div className="text">
{this.props.messages['intro.seeExamples']} {this.props.messages['intro.seeExamples']}
</div> </div>
</a> </a>
<a className="sprite sprite-3" href="#" onClick={this.handleJoinClick}> <a
className="sprite sprite-3"
href="#"
onClick={this.handleJoinClick}
>
<img <img
alt="Gobo"
className="costume costume-1" className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-a.png" src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-a.png"
alt="Gobo" /> />
<img <img
alt="Gobo"
className="costume costume-2" className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-b.png" src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-b.png"
alt="Gobo" /> />
<div className="circle"></div> <div className="circle" />
<div className="text"> <div className="text">
{this.props.messages['intro.joinScratch']} {this.props.messages['intro.joinScratch']}
</div> </div>
<div className="text subtext">{this.props.messages['intro.itsFree']}</div> <div className="text subtext">{this.props.messages['intro.itsFree']}</div>
</a> </a>
<Registration key="registration" <Registration
isOpen={this.state.registrationOpen} isOpen={this.state.registrationOpen}
onRequestClose={this.closeRegistration} key="registration"
onRegistrationDone={this.completeRegistration} /> onRegistrationDone={this.handleCompleteRegistration}
onRequestClose={this.handleCloseRegistration}
/>
</div> </div>
<div className="description" <div
dangerouslySetInnerHTML={{__html: this.props.messages['intro.description']}}></div> className="description"
dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
__html: this.props.messages['intro.description']
}}
/>
<div className="links"> <div className="links">
<a href="/about/"> <a href="/about/">
{this.props.messages['intro.aboutScratch']} {this.props.messages['intro.aboutScratch']}
@ -113,33 +130,70 @@ var Intro = React.createClass({
<a href="/educators/"> <a href="/educators/">
{this.props.messages['intro.forEducators']} {this.props.messages['intro.forEducators']}
</a> </a>
<a className="last" href="/parents/"> <a
className="last"
href="/parents/"
>
{this.props.messages['intro.forParents']} {this.props.messages['intro.forParents']}
</a> </a>
</div> </div>
</div> </div>
<div className="video"> <div className="video">
<div className="play-button" onClick={this.showVideo}></div> <div
<img src="//cdn.scratch.mit.edu/scratchr2/static/images/hp-video-screenshot.png" className="play-button"
alt="Intro Video" /> onClick={this.handleShowVideo}
/>
<img
alt="Intro Video"
src="//cdn.scratch.mit.edu/scratchr2/static/images/hp-video-screenshot.png"
/>
</div> </div>
<IframeModal <IframeModal
className="mod-intro-video" className="mod-intro-video"
isOpen={this.state.videoOpen} isOpen={this.state.videoOpen}
onRequestClose={this.closeVideo}
src="//player.vimeo.com/video/65583694?title=0&amp;byline=0&amp;portrait=0" src="//player.vimeo.com/video/65583694?title=0&amp;byline=0&amp;portrait=0"
onRequestClose={this.handleCloseVideo}
/> />
</div> </div>
); );
} }
}); }
var mapStateToProps = function (state) { Intro.propTypes = {
return { dispatch: PropTypes.func.isRequired,
session: state.session messages: PropTypes.shape({
}; 'intro.aboutScratch': PropTypes.string,
'intro.forEducators': PropTypes.string,
'intro.forParents': PropTypes.string,
'intro.itsFree': PropTypes.string,
'intro.joinScratch': PropTypes.string,
'intro.seeExamples': PropTypes.string,
'intro.tagLine': PropTypes.string,
'intro.tryItOut': PropTypes.string,
'intro.description': PropTypes.string
})
}; };
var ConnectedIntro = connect(mapStateToProps)(Intro); Intro.defaultProps = {
messages: {
'intro.aboutScratch': 'ABOUT SCRATCH',
'intro.forEducators': 'FOR EDUCATORS',
'intro.forParents': 'FOR PARENTS',
'intro.itsFree': 'it\'s free!',
'intro.joinScratch': 'JOIN SCRATCH',
'intro.seeExamples': 'SEE EXAMPLES',
'intro.tagLine': 'Create stories, games, and animations<br /> Share with others around the world',
'intro.tryItOut': 'TRY IT OUT',
'intro.description': 'A creative learning community with <span class="project-count"> ' +
'over 14 million </span>projects shared'
},
session: {}
};
const mapStateToProps = state => ({
session: state.session
});
const ConnectedIntro = connect(mapStateToProps)(Intro);
module.exports = ConnectedIntro; module.exports = ConnectedIntro;

View file

@ -1,46 +1,57 @@
var classNames = require('classnames'); const bindAll = require('lodash.bindall');
var React = require('react'); const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
var jar = require('../../lib/jar.js'); const jar = require('../../lib/jar.js');
var languages = require('../../../languages.json'); const languages = require('../../../languages.json');
var Form = require('../forms/form.jsx'); const Form = require('../forms/form.jsx');
var Select = require('../forms/select.jsx'); const Select = require('../forms/select.jsx');
require('./languagechooser.scss'); require('./languagechooser.scss');
/** /**
* Footer dropdown menu that allows one to change their language. * Footer dropdown menu that allows one to change their language.
*/ */
var LanguageChooser = React.createClass({ class LanguageChooser extends React.Component {
type: 'LanguageChooser', constructor (props) {
getDefaultProps: function () { super(props);
return { bindAll(this, [
languages: languages, 'handleSetLanguage'
locale: 'en' ]);
}; }
}, handleSetLanguage (name, value) {
onSetLanguage: function (name, value) {
jar.set('scratchlanguage', value); jar.set('scratchlanguage', value);
window.location.reload(); window.location.reload();
}, }
render: function () { render () {
var classes = classNames( const languageOptions = Object.keys(this.props.languages).map(value => ({
'language-chooser', value: value,
this.props.className label: this.props.languages[value]
); }));
var languageOptions = Object.keys(this.props.languages).map(function (value) {
return {value: value, label: this.props.languages[value]};
}.bind(this));
return ( return (
<Form className={classes}> <Form className={classNames('language-chooser', this.props.className)}>
<Select name="language" <Select
options={languageOptions} required
value={this.props.locale} name="language"
onChange={this.onSetLanguage} options={languageOptions}
required /> value={this.props.locale}
onChange={this.handleSetLanguage}
/>
</Form> </Form>
); );
} }
}); }
LanguageChooser.propTypes = {
className: PropTypes.string,
languages: PropTypes.object, // eslint-disable-line react/forbid-prop-types
locale: PropTypes.string
};
LanguageChooser.defaultProps = {
languages: languages,
locale: 'en'
};
module.exports = LanguageChooser; module.exports = LanguageChooser;

View file

@ -1,66 +1,108 @@
var React = require('react'); const bindAll = require('lodash.bindall');
var FormattedMessage = require('react-intl').FormattedMessage; const FormattedMessage = require('react-intl').FormattedMessage;
const PropTypes = require('prop-types');
const React = require('react');
var log = require('../../lib/log.js'); const log = require('../../lib/log.js');
var Form = require('../forms/form.jsx'); const Form = require('../forms/form.jsx');
var Input = require('../forms/input.jsx'); const Input = require('../forms/input.jsx');
var Button = require('../forms/button.jsx'); const Button = require('../forms/button.jsx');
var Spinner = require('../spinner/spinner.jsx'); const Spinner = require('../spinner/spinner.jsx');
require('./login.scss'); require('./login.scss');
var Login = React.createClass({ class Login extends React.Component {
type: 'Login', constructor (props) {
propTypes: { super(props);
onLogIn: React.PropTypes.func, bindAll(this, [
error: React.PropTypes.string 'handleSubmit'
}, ]);
getInitialState: function () { this.state = {
return {
waiting: false waiting: false
}; };
}, }
handleSubmit: function (formData) { handleSubmit (formData) {
this.setState({waiting: true}); this.setState({waiting: true});
this.props.onLogIn(formData, function (err) { this.props.onLogIn(formData, err => {
if (err) log.error(err); if (err) log.error(err);
this.setState({waiting: false}); this.setState({waiting: false});
}.bind(this)); });
}, }
render: function () { render () {
var error; let error;
if (this.props.error) { if (this.props.error) {
error = <div className="error">{this.props.error}</div>; error = <div className="error">{this.props.error}</div>;
} }
return ( return (
<div className="login"> <div className="login">
<Form onSubmit={this.handleSubmit}> <Form onSubmit={this.handleSubmit}>
<label htmlFor="username" key="usernameLabel"> <label
<FormattedMessage id='general.username' /> htmlFor="username"
key="usernameLabel"
>
<FormattedMessage id="general.username" />
</label> </label>
<Input type="text" ref="username" name="username" maxLength="30" key="usernameInput" required /> <Input
<label htmlFor="password" key="passwordLabel"> required
<FormattedMessage id='general.password' /> key="usernameInput"
maxLength="30"
name="username"
ref={input => {
this.username = input;
}}
type="text"
/>
<label
htmlFor="password"
key="passwordLabel"
>
<FormattedMessage id="general.password" />
</label> </label>
<Input type="password" ref="password" name="password" key="passwordInput" required /> <Input
required
key="passwordInput"
name="password"
ref={input => {
this.password = input;
}}
type="password"
/>
{this.state.waiting ? [ {this.state.waiting ? [
<Button className="submit-button white" type="submit" disabled="disabled" key="submitButton"> <Button
className="submit-button white"
disabled="disabled"
key="submitButton"
type="submit"
>
<Spinner /> <Spinner />
</Button> </Button>
] : [ ] : [
<Button className="submit-button white" type="submit" key="submitButton"> <Button
<FormattedMessage id='general.signIn' /> className="submit-button white"
key="submitButton"
type="submit"
>
<FormattedMessage id="general.signIn" />
</Button> </Button>
]} ]}
<a className="right" href="/accounts/password_reset/" key="passwordResetLink"> <a
<FormattedMessage id='login.needHelp' /> className="right"
href="/accounts/password_reset/"
key="passwordResetLink"
>
<FormattedMessage id="login.needHelp" />
</a> </a>
{error} {error}
</Form> </Form>
</div> </div>
); );
} }
}); }
Login.propTypes = {
error: PropTypes.string,
onLogIn: PropTypes.func
};
module.exports = Login; module.exports = Login;

View file

@ -1,60 +1,68 @@
var classNames = require('classnames'); const bindAll = require('lodash.bindall');
var React = require('react'); const classNames = require('classnames');
var MediaQuery = require('react-responsive'); const MediaQuery = require('react-responsive').default;
var frameless = require('../../lib/frameless'); const PropTypes = require('prop-types');
const React = require('react');
const frameless = require('../../lib/frameless');
require('./masonrygrid.scss'); require('./masonrygrid.scss');
var MasonryGrid = React.createClass({ class MasonryGrid extends React.Component {
type: 'MasonryGrid', constructor (props) {
getDefaultProps: function () { super(props);
return { bindAll(this, [
as: 'div' 'reorderColumns'
}; ]);
}, }
reorderColumns: function (items, cols) { reorderColumns (items, cols) {
var a1 = []; const a1 = [];
var a2 = []; const a2 = [];
var a3 = []; const a3 = [];
var i = 0; let i = 0;
//only implemented for 2 and 3 columns so far - easy to extend if needed // only implemented for 2 and 3 columns so far - easy to extend if needed
if (cols > 1 && cols < 4) { if (cols > 1 && cols < 4) {
for (i=0;i<items.length;i++){ for (i = 0; i < items.length; i++){
var col = (i+cols)%cols; const col = (i + cols) % cols;
if (col === 0) { if (col === 0) {
a1.push(items[i]); a1.push(items[i]);
} } else if (col === 1) {
else if (col === 1) {
a2.push(items[i]); a2.push(items[i]);
} } else if (col === 2) {
else if (col === 2) {
a3.push(items[i]); a3.push(items[i]);
} }
} }
return a1.concat(a2,a3); return a1.concat(a2, a3);
} else {
return items;
} }
}, return items;
render: function () { }
var classes = classNames( render () {
'masonry',
this.props.className
);
return ( return (
<this.props.as className={classes}> <this.props.as className={classNames('masonry', this.props.className)}>
<MediaQuery maxWidth={frameless.tablet - 1} > <MediaQuery maxWidth={frameless.tablet - 1}>
{this.props.children} {this.props.children}
</MediaQuery> </MediaQuery>
<MediaQuery minWidth={frameless.tablet} maxWidth={frameless.desktop - 1} > <MediaQuery
maxWidth={frameless.desktop - 1}
minWidth={frameless.tablet}
>
{this.reorderColumns(this.props.children, 2)} {this.reorderColumns(this.props.children, 2)}
</MediaQuery> </MediaQuery>
<MediaQuery minWidth={frameless.desktop} > <MediaQuery minWidth={frameless.desktop}>
{this.reorderColumns(this.props.children, 3)} {this.reorderColumns(this.props.children, 3)}
</MediaQuery> </MediaQuery>
</this.props.as> </this.props.as>
); );
} }
}); }
MasonryGrid.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
MasonryGrid.defaultProps = {
as: 'div'
};
module.exports = MasonryGrid; module.exports = MasonryGrid;

View file

@ -1,39 +1,53 @@
var React = require('react'); const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
const Box = require('../box/box.jsx');
const LegacyCarousel = require('../carousel/legacy-carousel.jsx');
const IframeModal = require('../modal/iframe/modal.jsx');
const NestedCarousel = require('../nestedcarousel/nestedcarousel.jsx');
require('./microworld.scss'); require('./microworld.scss');
var Box = require('../box/box.jsx'); class Microworld extends React.Component {
var LegacyCarousel = require('../carousel/legacy-carousel.jsx'); constructor (props) {
var IframeModal = require('../modal/iframe/modal.jsx'); super(props);
var NestedCarousel = require('../nestedcarousel/nestedcarousel.jsx'); bindAll(this, [
'markVideoOpen',
var Microworld = React.createClass({ 'markVideoClosed',
type: 'Microworld', 'renderVideos',
propTypes: { 'renderVideo',
microworldData: React.PropTypes.node.isRequired 'renderEditorWindow',
}, 'renderTips',
markVideoOpen: function (key) { 'renderStarterProject',
{/* When a video is clicked, mark it as an open video, so the video Modal will open. 'renderProjectIdeasBox',
Key is the number of the video, so distinguish between different videos on the page */} 'renderForum',
'renderDesignStudio'
var videoOpenArr = this.state.videoOpen; ]);
videoOpenArr[key] = true; this.state = {
this.setState({videoOpen: videoOpenArr});
},
markVideoClosed: function (key) {
{/* When a video's x is clicked, mark it as closed, so the video Modal will disappear.
Key is the number of the video, so distinguish between different videos on the page */}
var videoOpenArr = this.state.videoOpen;
videoOpenArr[key] = false;
this.setState({videoOpen: videoOpenArr});
},
getInitialState: function () {
return {
videoOpen: {} videoOpen: {}
}; };
}, }
renderVideos: function () { markVideoOpen (key) {
var videos = this.props.microworldData.videos; /*
When a video is clicked, mark it as an open video, so the video Modal will open.
Key is the number of the video, so distinguish between different videos on the page
*/
const videoOpenArr = this.state.videoOpen;
videoOpenArr[key] = true;
this.setState({videoOpen: videoOpenArr});
}
markVideoClosed (key) {
/*
When a video's x is clicked, mark it as closed, so the video Modal will disappear.
Key is the number of the video, so distinguish between different videos on the page
*/
const videoOpenArr = this.state.videoOpen;
videoOpenArr[key] = false;
this.setState({videoOpen: videoOpenArr});
}
renderVideos () {
const videos = this.props.microworldData.videos;
if (!videos || videos.length <= 0) { if (!videos || videos.length <= 0) {
return null; return null;
} }
@ -48,26 +62,32 @@ var Microworld = React.createClass({
</div> </div>
</div> </div>
); );
}, }
renderVideo: function (video, key) { renderVideo (video, key) {
return ( return (
<div> <div>
<div className="video"> <div className="video">
<div className="play-button" onClick={this.markVideoOpen.bind(this, key)}> <div
</div> className="play-button"
onClick={() => { // eslint-disable-line react/jsx-no-bind
this.markVideoOpen(key);
}}
/>
<img src={video.image} /> <img src={video.image} />
</div> </div>
<IframeModal <IframeModal
className="mod-microworld-video" className="mod-microworld-video"
isOpen={this.state.videoOpen[key]} isOpen={this.state.videoOpen[key]}
onRequestClose={this.markVideoClosed.bind(this, key)}
src={video.link} src={video.link}
onRequestClose={() => { // eslint-disable-line react/jsx-no-bind
this.markVideoClosed(key);
}}
/> />
</div> </div>
); );
}, }
renderEditorWindow: function () { renderEditorWindow () {
var projectId = this.props.microworldData.microworld_project_id; const projectId = this.props.microworldData.microworld_project_id;
if (!projectId) { if (!projectId) {
return null; return null;
@ -75,30 +95,37 @@ var Microworld = React.createClass({
return ( return (
<div className="editor section"> <div className="editor section">
<h1 className="sectionheader">Start Creating!</h1> <h1 className="sectionheader">Start Creating!</h1>
<iframe src={'//scratch.mit.edu/projects/embed-editor/' + projectId + '/?isMicroworld=true'} <iframe
frameBorder="0"> </iframe> frameBorder="0"
src={`//scratch.mit.edu/projects/embed-editor/${projectId}/?isMicroworld=true`}
/>
{this.renderTips()} {this.renderTips()}
</div> </div>
); );
}, }
renderTips: function () { renderTips () {
var tips = this.props.microworldData.tips; const tips = this.props.microworldData.tips;
if (!tips || tips.length <= 0) { if (!tips || tips.length <= 0) {
return null; return null;
} }
return ( return (
<div className="box nestedcarousel"> <div className="box nestedcarousel">
<div className="box-header"> <div className="box-header" />
</div>
<div className="box-content"> <div className="box-content">
<NestedCarousel items={tips} settings={{slidesToShow:1,slidesToScroll:1}}/> <NestedCarousel
items={tips}
settings={{
slidesToShow: 1,
slidesToScroll: 1
}}
/>
</div> </div>
</div> </div>
); );
}, }
renderStarterProject: function () { renderStarterProject () {
var starterProjects = this.props.microworldData.starter_projects; const starterProjects = this.props.microworldData.starter_projects;
if (!starterProjects || starterProjects.length <= 0){ if (!starterProjects || starterProjects.length <= 0){
return null; return null;
} }
@ -107,39 +134,42 @@ var Microworld = React.createClass({
<div className="project-ideas"> <div className="project-ideas">
<h1 className="sectionheader">Check out ideas for more projects</h1> <h1 className="sectionheader">Check out ideas for more projects</h1>
<Box <Box
key="starter_projects"
title="More Starter Projects" title="More Starter Projects"
key="starter_projects"> >
<LegacyCarousel items={starterProjects} /> <LegacyCarousel items={starterProjects} />
</Box> </Box>
</div> </div>
); );
}, }
renderProjectIdeasBox: function () { renderProjectIdeasBox () {
var communityProjects = this.props.microworldData.community_projects; const communityProjects = this.props.microworldData.community_projects;
if (!communityProjects || communityProjects.size <= 0) { if (!communityProjects || communityProjects.size <= 0) {
return null; return null;
} }
var featured = communityProjects.featured_projects; const featured = communityProjects.featured_projects;
var all = communityProjects.newest_projects; const all = communityProjects.newest_projects;
var rows = []; const rows = [];
if (featured && featured.length > 0){ if (featured && featured.length > 0){
rows.push( rows.push(
<Box <Box
key="community_featured_projects"
title="Featured Community Projects" title="Featured Community Projects"
key="community_featured_projects"> >
<LegacyCarousel items={featured} /> <LegacyCarousel items={featured} />
</Box> </Box>
); );
} }
if (all && all.length > 0) { if (all && all.length > 0) {
rows.push( rows.push(
<Box <Box
title="All Community Projects" key="community_all_projects"
key="community_all_projects"> title="All Community Projects"
<LegacyCarousel items={all} /> >
</Box> <LegacyCarousel items={all} />
</Box>
); );
} }
if (rows.length <= 0) { if (rows.length <= 0) {
@ -151,67 +181,88 @@ var Microworld = React.createClass({
{rows} {rows}
</div> </div>
); );
}, }
renderForum: function () { renderForum () {
if (!this.props.microworldData.show_forum) { if (!this.props.microworldData.show_forum) {
return null; return null;
} }
return ( return (
<div className="forum"> <div className="forum">
<h1 className="sectionheader">Chat with others!</h1> <h1 className="sectionheader">Chat with others!</h1>
<img src="/images/forum-image.png"/> <img src="/images/forum-image.png" />
</div> </div>
); );
}, }
renderDesignStudio: function () { renderDesignStudio () {
var designChallenge = this.props.microworldData.design_challenge; const designChallenge = this.props.microworldData.design_challenge;
if (!designChallenge) { if (!designChallenge) {
return null; return null;
} }
let studioHref = '';
if (designChallenge.studio_id) { if (designChallenge.studio_id) {
var studioHref = 'https://scratch.mit.edu//studios/' + designChallenge.studio_id + '/'; studioHref = `https://scratch.mit.edu//studios/${designChallenge.studio_id}/`;
} }
if (designChallenge.project_id) { if (designChallenge.project_id) {
return ( return (
<div className="side-by-side section"> <div className="side-by-side section">
<h1 className="sectionheader">Join our Design Challenge!</h1> <h1 className="sectionheader">Join our Design Challenge!</h1>
<div className="design-studio"> <div className="design-studio">
<iframe src={'https://scratch.mit.edu/projects/' + designChallenge.project_id + <iframe
'/#fullscreen'} frameBorder="0"> </iframe> frameBorder="0"
src={`https://scratch.mit.edu/projects/${designChallenge.project_id}/#fullscreen`}
/>
</div> </div>
<div className="design-studio-projects"> <div className="design-studio-projects">
<Box title="Examples" <Box
key="scratch_design_studio" key="scratch_design_studio"
moreTitle={studioHref ? 'Visit the studio' : null} moreHref={studioHref ? studioHref : null}
moreHref={studioHref ? studioHref : null}> moreTitle={studioHref ? 'Visit the studio' : null}
title="Examples"
>
{/* The two carousels are used to show two rows of projects, one above the {/* 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. */} other. This should be probably be changed, to allow better scrolling. */}
<LegacyCarousel settings={{slidesToShow:2,slidesToScroll:2}} <LegacyCarousel
items={this.props.microworldData.design_challenge.studio1} /> items={this.props.microworldData.design_challenge.studio1}
<LegacyCarousel settings={{slidesToShow:2,slidesToScroll:2}} settings={{
items={this.props.microworldData.design_challenge.studio2} /> slidesToShow: 2,
slidesToScroll: 2
}}
/>
<LegacyCarousel
items={this.props.microworldData.design_challenge.studio2}
settings={{
slidesToShow: 2,
slidesToScroll: 2
}}
/>
</Box> </Box>
</div> </div>
</div> </div>
); );
} else {
return (
<div className="section">
<h1 className="sectionheader">Join our Design Challenge!</h1>
<Box
title="design Challenge Projects"
key="scratch_design_studio"
moreTitle={studioHref ? 'Visit the studio' : null}
moreHref={studioHref ? studioHref : null}>
<LegacyCarousel items={this.props.microworldData.design_challenge.studio1.concat(
this.props.microworldData.design_challenge.studio2)} />
</Box>
</div>
);
} }
}, return (
render: function () { <div className="section">
<h1 className="sectionheader">Join our Design Challenge!</h1>
<Box
key="scratch_design_studio"
moreHref={studioHref ? studioHref : null}
moreTitle={studioHref ? 'Visit the studio' : null}
title="design Challenge Projects"
>
<LegacyCarousel
items={
this.props.microworldData.design_challenge.studio1.concat(
this.props.microworldData.design_challenge.studio2
)
}
/>
`</Box>
</div>
);
}
render () {
return ( return (
<div className="inner microworld"> <div className="inner microworld">
<div className="top-banner section"> <div className="top-banner section">
@ -231,6 +282,10 @@ var Microworld = React.createClass({
); );
} }
}); }
Microworld.propTypes = {
microworldData: PropTypes.node.isRequired
};
module.exports = Microworld; module.exports = Microworld;

View file

@ -1,7 +1,9 @@
var classNames = require('classnames'); const bindAll = require('lodash.bindall');
var omit = require('lodash.omit'); const classNames = require('classnames');
var React = require('react'); const omit = require('lodash.omit');
var ReactModal = require('react-modal'); const PropTypes = require('prop-types');
const React = require('react');
const ReactModal = require('react-modal');
require('./modal.scss'); require('./modal.scss');
@ -10,47 +12,55 @@ ReactModal.setAppElement(document.getElementById('view'));
/** /**
* Container for pop up windows (See: registration window) * Container for pop up windows (See: registration window)
*/ */
var Modal = React.createClass({ class Modal extends React.Component {
type: 'Modal', constructor (props) {
propTypes: { super(props);
className: React.PropTypes.string, bindAll(this, [
overlayClassName: React.PropTypes.string 'handleRequestClose'
}, ]);
requestClose: function () { }
handleRequestClose () {
return this.modal.portal.requestClose(); return this.modal.portal.requestClose();
}, }
render: function () { render () {
var modalClasses = classNames(
'modal-content',
this.props.className
);
var overlayClasses = classNames(
'modal-overlay',
this.props.overlayClassName
);
return ( return (
<ReactModal <ReactModal
ref={ appElement={document.getElementById('view')}
function (component) { className={{
this.modal = component; base: classNames('modal-content', this.props.className),
}.bind(this) afterOpen: classNames('modal-content', this.props.className),
} beforeClose: classNames('modal-content', this.props.className)
className={modalClasses} }}
overlayClassName={overlayClasses} overlayClassName={{
base: classNames('modal-overlay', this.props.overlayClassName),
afterOpen: classNames('modal-overlay', this.props.overlayClassName),
beforeClose: classNames('modal-overlay', this.props.overlayClassName)
}}
ref={component => {
this.modal = component;
}}
{...omit(this.props, ['className', 'overlayClassName'])} {...omit(this.props, ['className', 'overlayClassName'])}
> >
<div className="modal-content-close" onClick={this.requestClose}> <div
className="modal-content-close"
onClick={this.handleRequestClose}
>
<img <img
alt="close-icon"
className="modal-content-close-img" className="modal-content-close-img"
src="/svgs/modal/close-x.svg" src="/svgs/modal/close-x.svg"
alt="close-icon"
/> />
</div> </div>
{this.props.children} {this.props.children}
</ReactModal> </ReactModal>
); );
} }
}); }
Modal.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
overlayClassName: PropTypes.string
};
module.exports = Modal; module.exports = Modal;

View file

@ -21,7 +21,7 @@
background-color: transparentize($ui-blue, .3); background-color: transparentize($ui-blue, .3);
} }
.ReactModal__Content:focus { .modal-content:focus {
outline: none; outline: none;
} }

View file

@ -1,34 +1,28 @@
var classNames = require('classnames'); const classNames = require('classnames');
var omit = require('lodash.omit'); const omit = require('lodash.omit');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var Modal = require('../base/modal.jsx'); const Modal = require('../base/modal.jsx');
require('./modal.scss'); require('./modal.scss');
var IframeModal = React.createClass({ const IframeModal = props => (
propTypes: { <Modal {...omit(props, ['src'])}>
isOpen: React.PropTypes.bool, <iframe
onRequestClose: React.PropTypes.func, className={classNames('modal-content-iframe', props.className)}
className: React.PropTypes.string, ref={props.componentRef}
componentRef: React.PropTypes.func, src={props.src}
src: React.PropTypes.string />
}, </Modal>
render: function () { );
var iframeClasses = classNames(
'modal-content-iframe', IframeModal.propTypes = {
this.props.className className: PropTypes.string,
); componentRef: PropTypes.func,
return ( isOpen: PropTypes.bool,
<Modal {...omit(this.props, ['src'])}> onRequestClose: PropTypes.func,
<iframe src: PropTypes.string
ref={this.props.componentRef} };
src={this.props.src}
className={iframeClasses}
/>
</Modal>
);
}
});
module.exports = IframeModal; module.exports = IframeModal;

View file

@ -1,121 +1,138 @@
var FormattedMessage = require('react-intl').FormattedMessage; const FormattedMessage = require('react-intl').FormattedMessage;
var MediaQuery = require('react-responsive'); const MediaQuery = require('react-responsive').default;
var omit = require('lodash.omit'); const omit = require('lodash.omit');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var FlexRow = require('../../flex-row/flex-row.jsx'); const FlexRow = require('../../flex-row/flex-row.jsx');
var frameless = require('../../../lib/frameless'); const frameless = require('../../../lib/frameless');
var Modal = require('../base/modal.jsx'); const Modal = require('../base/modal.jsx');
var TitleBanner = require('../../title-banner/title-banner.jsx'); const TitleBanner = require('../../title-banner/title-banner.jsx');
require('../../forms/button.scss'); require('../../forms/button.scss');
require('./modal.scss'); require('./modal.scss');
var TTTModal = React.createClass({ const TTTModal = props => (
propTypes: { <Modal
title: React.PropTypes.string.isRequired, className="mod-ttt"
description: React.PropTypes.string.isRequired, {...omit(
tutorialLoc: React.PropTypes.string.isRequired, props,
activityLoc: React.PropTypes.string.isRequired, [
guideLoc: React.PropTypes.string.isRequired, 'title',
thumbUrl: React.PropTypes.string.isRequired, 'description',
bannerUrl: React.PropTypes.string.isRequired 'tutorialLoc',
}, 'activityLoc',
render: function () { 'guideLoc',
var modalOmit = [ 'thumbUrl',
'title', 'bannerUrl'
'description', ]
'tutorialLoc', )}
'activityLoc', >
'guideLoc', <TitleBanner className="mod-ttt">
'thumbUrl', <MediaQuery minWidth={frameless.mobile}>
'bannerUrl' <img
]; alt=""
return ( className="mod-ttt-img"
<Modal src={props.bannerUrl}
className="mod-ttt" />
{...omit(this.props, modalOmit)} </MediaQuery>
<MediaQuery maxWidth={frameless.mobile - 1}>
<img
alt=""
className="mod-ttt-img"
src={props.thumbUrl}
/>
</MediaQuery>
</TitleBanner>
<div className="ttt-title">
<h2>{props.title}</h2>
<p className="ttt-description">{props.description}</p>
</div>
<ul className="modal-content-ttt">
<FlexRow
as="li"
className="mod-ttt-item"
> >
<TitleBanner className="mod-ttt"> <div className="modal-content-ttt-text">
<MediaQuery minWidth={frameless.mobile}> <div className="modal-content-ttt-title">
<img className="mod-ttt-img" src={this.props.bannerUrl} alt="" /> <img
</MediaQuery> alt="tutorial-icon"
<MediaQuery maxWidth={frameless.mobile - 1}> className="modal-content-ttt-title-img"
<img className="mod-ttt-img" src={this.props.thumbUrl} alt="" /> src="/svgs/ttt/tutorial.svg"
</MediaQuery> />
</TitleBanner> <FormattedMessage id="ttt.tutorial" />
<div className="ttt-title"> </div>
<h2>{this.props.title}</h2> <p className="modal-content-ttt-subtitle">
<p className="ttt-description">{this.props.description}</p> <FormattedMessage id="ttt.tutorialSubtitle" />
</p>
</div> </div>
<ul className="modal-content-ttt"> <a
<FlexRow as="li" className="mod-ttt-item"> className="button white mod-ttt-item"
<div className="modal-content-ttt-text"> href={props.tutorialLoc}
<div className="modal-content-ttt-title"> >
<img <FormattedMessage id="tile.tryIt" />
className="modal-content-ttt-title-img" </a>
src="/svgs/ttt/tutorial.svg" </FlexRow>
alt="tutorial-icon" <FlexRow
/> as="li"
<FormattedMessage id="ttt.tutorial" /> className="mod-ttt-item"
</div> >
<p className="modal-content-ttt-subtitle"> <div className="modal-content-ttt-text">
<FormattedMessage id="ttt.tutorialSubtitle" /> <div className="modal-content-ttt-title">
</p> <img
</div> alt="activity-cards-icon"
<a className="modal-content-ttt-title-img"
href={this.props.tutorialLoc} src="/svgs/ttt/activity-cards.svg"
className="button white mod-ttt-item" />
> <FormattedMessage id="ttt.activityTitle" />
<FormattedMessage id="tile.tryIt" /> </div>
</a> <p className="modal-content-ttt-subtitle">
</FlexRow> <FormattedMessage id="ttt.activitySubtitle" />
<FlexRow as="li" className="mod-ttt-item"> </p>
<div className="modal-content-ttt-text"> </div>
<div className="modal-content-ttt-title"> <a
<img className="button white mod-ttt-item"
className="modal-content-ttt-title-img" href={props.activityLoc}
src="/svgs/ttt/activity-cards.svg" >
alt="activity-cards-icon" <FormattedMessage id="ttt.open" />
/> </a>
<FormattedMessage id="ttt.activityTitle" /> </FlexRow>
</div> <FlexRow
<p className="modal-content-ttt-subtitle"> as="li"
<FormattedMessage id="ttt.activitySubtitle" /> className="mod-ttt-item"
</p> >
</div> <div className="modal-content-ttt-text">
<a <div className="modal-content-ttt-title">
href={this.props.activityLoc} <img
className="button white mod-ttt-item" alt="educator-guide-icon"
> className="modal-content-ttt-title-img"
<FormattedMessage id="ttt.open" /> src="/svgs/ttt/educator-guide.svg"
</a> />
</FlexRow> <FormattedMessage id="ttt.educatorTitle" />
<FlexRow as="li" className="mod-ttt-item"> </div>
<div className="modal-content-ttt-text"> <p className="modal-content-ttt-subtitle">
<div className="modal-content-ttt-title"> <FormattedMessage id="ttt.educatorSubtitle" />
<img </p>
className="modal-content-ttt-title-img" </div>
src="/svgs/ttt/educator-guide.svg" <a
alt="educator-guide-icon" className="button white mod-ttt-item"
/> href={props.guideLoc}
<FormattedMessage id="ttt.educatorTitle" /> >
</div> <FormattedMessage id="ttt.open" />
<p className="modal-content-ttt-subtitle"> </a>
<FormattedMessage id="ttt.educatorSubtitle" /> </FlexRow>
</p> </ul>
</div> </Modal>
<a );
href={this.props.guideLoc}
className="button white mod-ttt-item" TTTModal.propTypes = {
> activityLoc: PropTypes.string.isRequired,
<FormattedMessage id="ttt.open" /> bannerUrl: PropTypes.string.isRequired,
</a> description: PropTypes.string.isRequired,
</FlexRow> guideLoc: PropTypes.string.isRequired,
</ul> thumbUrl: PropTypes.string.isRequired,
</Modal> title: PropTypes.string.isRequired,
); tutorialLoc: PropTypes.string.isRequired
} };
});
module.exports = TTTModal; module.exports = TTTModal;

View file

@ -1,21 +1,18 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./navigation.scss'); require('./navigation.scss');
var NavigationBox = React.createClass({ const NavigationBox = props => (
type: 'NavigationBox', <div className={classNames('inner', props.className)}>
render: function () { {props.children}
var classes = classNames( </div>
'inner', );
this.props.className
); NavigationBox.propTypes = {
return ( children: PropTypes.node,
<div className={classes}> className: PropTypes.string
{this.props.children} };
</div>
);
}
});
module.exports = NavigationBox; module.exports = NavigationBox;

View file

@ -1,42 +1,55 @@
var React = require('react'); const React = require('react');
var NavigationBox = require('../../base/navigation.jsx'); const NavigationBox = require('../../base/navigation.jsx');
require('./navigation.scss'); require('./navigation.scss');
var Navigation = React.createClass({ const Navigation = () => (
type: 'Navigation', <NavigationBox>
render: function () { <ul className="ul mod-2016">
return ( <li className="li-left mod-logo mod-2016">
<NavigationBox> <a
<ul className="ul mod-2016"> className="logo-a"
<li className="li-left mod-logo mod-2016"> href="/conference/2016"
<a href="/conference/2016" className="logo-a"> >
<img <img
src="/images/logo_sm.png" alt="Scratch Logo"
alt="Scratch Logo" className="logo-a-image"
className="logo-a-image" src="/images/logo_sm.png"
/> />
<p className="logo-a-title">Conference</p> <p className="logo-a-title">Conference</p>
</a>
</li>
<li className="li-right mod-2016">
<ul className="li-right-ul mod-2016">
<li className="link expect">
<a
className="link-a"
href="/conference/2016/expect"
>
What to Expect
</a> </a>
</li> </li>
<li className="li-right mod-2016"> <li className="link plan">
<ul className="li-right-ul mod-2016"> <a
<li className="link expect"> className="link-a"
<a href="/conference/2016/expect" className="link-a">What to Expect</a> href="/conference/2016/plan"
</li> >
<li className="link plan"> Plan Your Visit
<a href="/conference/2016/plan" className="link-a">Plan Your Visit</a> </a>
</li> </li>
<li className="link schedule"> <li className="link schedule">
<a href="/conference/2016/schedule" className="link-a">Schedule</a> <a
</li> className="link-a"
</ul> href="/conference/2016/schedule"
>
Schedule
</a>
</li> </li>
</ul> </ul>
</NavigationBox> </li>
); </ul>
} </NavigationBox>
}); );
module.exports = Navigation; module.exports = Navigation;

View file

@ -1,29 +1,27 @@
var React = require('react'); const React = require('react');
var NavigationBox = require('../../base/navigation.jsx'); const NavigationBox = require('../../base/navigation.jsx');
require('./navigation.scss'); require('./navigation.scss');
var Navigation = React.createClass({ const Navigation = () => (
type: 'Navigation', <NavigationBox>
render: function () { <ul className="ul mod-2017">
return ( <li className="li-left mod-logo mod-2017">
<NavigationBox> <a
<ul className="ul mod-2017"> className="logo-a"
<li className="li-left mod-logo mod-2017"> href="/conference"
<a href="/conference" className="logo-a"> >
<img <img
src="/images/logo_sm.png" alt="Scratch Logo"
alt="Scratch Logo" className="logo-a-image"
className="logo-a-image" src="/images/logo_sm.png"
/> />
<p className="logo-a-title">Conferences</p> <p className="logo-a-title">Conferences</p>
</a> </a>
</li> </li>
</ul> </ul>
</NavigationBox> </NavigationBox>
); );
}
});
module.exports = Navigation; module.exports = Navigation;

View file

@ -1,29 +1,27 @@
var React = require('react'); const React = require('react');
var NavigationBox = require('../../base/navigation.jsx'); const NavigationBox = require('../../base/navigation.jsx');
require('./navigation.scss'); require('./navigation.scss');
var Navigation = React.createClass({ const Navigation = () => (
type: 'Navigation', <NavigationBox>
render: function () { <ul className="ul mod-2018">
return ( <li className="li-left mod-logo mod-2018">
<NavigationBox> <a
<ul className="ul mod-2018"> className="logo-a"
<li className="li-left mod-logo mod-2018"> href="/"
<a href="/" className="logo-a"> >
<img <img
src="/images/logo_sm.png" alt="Scratch Logo"
alt="Scratch Logo" className="logo-a-image"
className="logo-a-image" src="/images/logo_sm.png"
/> />
<p className="logo-a-title">Conferences</p> <p className="logo-a-title">Conferences</p>
</a> </a>
</li> </li>
</ul> </ul>
</NavigationBox> </NavigationBox>
); );
}
});
module.exports = Navigation; module.exports = Navigation;

View file

@ -1,31 +1,48 @@
var classNames = require('classnames'); const bindAll = require('lodash.bindall');
var connect = require('react-redux').connect; const classNames = require('classnames');
var React = require('react'); const connect = require('react-redux').connect;
var ReactIntl = require('react-intl'); const FormattedMessage = require('react-intl').FormattedMessage;
var FormattedMessage = ReactIntl.FormattedMessage; const injectIntl = require('react-intl').injectIntl;
var injectIntl = ReactIntl.injectIntl; const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
var messageCountActions = require('../../../redux/message-count.js'); const messageCountActions = require('../../../redux/message-count.js');
var sessionActions = require('../../../redux/session.js'); const sessionActions = require('../../../redux/session.js');
var api = require('../../../lib/api'); const api = require('../../../lib/api');
var Avatar = require('../../avatar/avatar.jsx'); const Avatar = require('../../avatar/avatar.jsx');
var Button = require('../../forms/button.jsx'); const Button = require('../../forms/button.jsx');
var Dropdown = require('../../dropdown/dropdown.jsx'); const Dropdown = require('../../dropdown/dropdown.jsx');
var Form = require('../../forms/form.jsx'); const Form = require('../../forms/form.jsx');
var Input = require('../../forms/input.jsx'); const Input = require('../../forms/input.jsx');
var log = require('../../../lib/log.js'); const log = require('../../../lib/log.js');
var Login = require('../../login/login.jsx'); const Login = require('../../login/login.jsx');
var Modal = require('../../modal/base/modal.jsx'); const Modal = require('../../modal/base/modal.jsx');
var NavigationBox = require('../base/navigation.jsx'); const NavigationBox = require('../base/navigation.jsx');
var Registration = require('../../registration/registration.jsx'); const Registration = require('../../registration/registration.jsx');
require('./navigation.scss'); require('./navigation.scss');
var Navigation = React.createClass({ class Navigation extends React.Component {
type: 'Navigation', constructor (props) {
getInitialState: function () { super(props);
return { bindAll(this, [
'getProfileUrl',
'handleJoinClick',
'handleLoginClick',
'handleCloseLogin',
'handleLogIn',
'handleLogOut',
'handleAccountNavClick',
'handleCloseAccountNav',
'showCanceledDeletion',
'handleCloseCanceledDeletion',
'handleCloseRegistration',
'handleCompleteRegistration',
'handleSearchSubmit'
]);
this.state = {
accountNavOpen: false, accountNavOpen: false,
canceledDeletionOpen: false, canceledDeletionOpen: false,
loginOpen: false, loginOpen: false,
@ -33,155 +50,153 @@ var Navigation = React.createClass({
registrationOpen: false, registrationOpen: false,
messageCountIntervalId: -1 // javascript method interval id for getting messsage count. messageCountIntervalId: -1 // javascript method interval id for getting messsage count.
}; };
}, }
getDefaultProps: function () { componentDidMount () {
return {
session: {},
unreadMessageCount: 0, // bubble number to display how many notifications someone has.
searchTerm: ''
};
},
componentDidMount: function () {
if (this.props.session.session.user) { if (this.props.session.session.user) {
var intervalId = setInterval(function () { const intervalId = setInterval(() => {
this.props.dispatch(messageCountActions.getCount(this.props.session.session.user.username)); this.props.dispatch(
}.bind(this), 120000); // check for new messages every 2 mins. messageCountActions.getCount(this.props.session.session.user.username)
this.setState({'messageCountIntervalId': intervalId}); );
}, 120000); // check for new messages every 2 mins.
this.setState({ // eslint-disable-line react/no-did-mount-set-state
messageCountIntervalId: intervalId
});
} }
}, }
componentDidUpdate: function (prevProps) { componentDidUpdate (prevProps) {
if (prevProps.session.session.user != this.props.session.session.user) { if (prevProps.session.session.user !== this.props.session.session.user) {
this.setState({ this.setState({ // eslint-disable-line react/no-did-update-set-state
'loginOpen': false, loginOpen: false,
'accountNavOpen': false accountNavOpen: false
}); });
if (this.props.session.session.user) { if (this.props.session.session.user) {
var intervalId = setInterval(function () { const intervalId = setInterval(() => {
this.props.dispatch(messageCountActions.getCount(this.props.session.session.user.username)); this.props.dispatch(
}.bind(this), 120000); // check for new messages every 2 mins. messageCountActions.getCount(this.props.session.session.user.username)
this.setState({'messageCountIntervalId': intervalId}); );
}, 120000); // check for new messages every 2 mins.
this.setState({ // eslint-disable-line react/no-did-update-set-state
messageCountIntervalId: intervalId
});
} else { } else {
// clear message count check, and set to default id. // clear message count check, and set to default id.
clearInterval(this.state.messageCountIntervalId); clearInterval(this.state.messageCountIntervalId);
this.props.dispatch(messageCountActions.setCount(0)); this.props.dispatch(messageCountActions.setCount(0));
this.setState({ this.setState({ // eslint-disable-line react/no-did-update-set-state
'messageCountIntervalId': -1 messageCountIntervalId: -1
}); });
} }
} }
}, }
componentWillUnmount: function () { componentWillUnmount () {
// clear message interval if it exists // clear message interval if it exists
if (this.state.messageCountIntervalId != -1) { if (this.state.messageCountIntervalId !== -1) {
clearInterval(this.state.messageCountIntervalId); clearInterval(this.state.messageCountIntervalId);
this.props.dispatch(messageCountActions.setCount(0)); this.props.dispatch(messageCountActions.setCount(0));
this.setState({ this.setState({
'messageCountIntervalId': -1 messageCountIntervalId: -1
}); });
} }
}, }
getProfileUrl: function () { getProfileUrl () {
if (!this.props.session.session.user) return; if (!this.props.session.session.user) return;
return '/users/' + this.props.session.session.user.username + '/'; return `/users/${this.props.session.session.user.username}/`;
}, }
handleJoinClick: function (e) { handleJoinClick (e) {
e.preventDefault(); e.preventDefault();
this.setState({'registrationOpen': true}); this.setState({registrationOpen: true});
}, }
handleLoginClick: function (e) { handleLoginClick (e) {
e.preventDefault(); e.preventDefault();
this.setState({'loginOpen': !this.state.loginOpen}); this.setState({loginOpen: !this.state.loginOpen});
}, }
closeLogin: function () { handleCloseLogin () {
this.setState({'loginOpen': false}); this.setState({loginOpen: false});
}, }
handleLogIn: function (formData, callback) { handleLogIn (formData, callback) {
this.setState({'loginError': null}); this.setState({loginError: null});
formData['useMessages'] = true; formData.useMessages = true;
api({ api({
method: 'post', method: 'post',
host: '', host: '',
uri: '/accounts/login/', uri: '/accounts/login/',
json: formData, json: formData,
useCsrf: true useCsrf: true
}, function (err, body) { }, (err, body) => {
if (err) this.setState({'loginError': err.message}); if (err) this.setState({loginError: err.message});
if (body) { if (body) {
body = body[0]; body = body[0];
if (!body.success) { if (body.success) {
this.handleCloseLogin();
body.messages.map(message => { // eslint-disable-line array-callback-return
if (message.message === 'canceled-deletion') {
this.showCanceledDeletion();
}
});
this.props.dispatch(sessionActions.refreshSession());
} else {
if (body.redirect) { if (body.redirect) {
window.location = body.redirect; window.location = body.redirect;
} }
// Update login error message to a friendlier one if it exists // Update login error message to a friendlier one if it exists
this.setState({'loginError': body.msg}); this.setState({loginError: body.msg});
} else {
this.closeLogin();
body.messages.map(function (message) {
if (message.message == 'canceled-deletion') {
this.showCanceledDeletion();
}
}.bind(this));
this.props.dispatch(sessionActions.refreshSession());
} }
} }
// JS error already logged by api mixin // JS error already logged by api mixin
callback(); callback();
}.bind(this)); });
}, }
handleLogOut: function (e) { handleLogOut (e) {
e.preventDefault(); e.preventDefault();
api({ api({
host: '', host: '',
method: 'post', method: 'post',
uri: '/accounts/logout/', uri: '/accounts/logout/',
useCsrf: true useCsrf: true
}, function (err) { }, err => {
if (err) log.error(err); if (err) log.error(err);
this.closeLogin(); this.handleCloseLogin();
window.location = '/'; window.location = '/';
}.bind(this)); });
}, }
handleAccountNavClick: function (e) { handleAccountNavClick (e) {
e.preventDefault(); e.preventDefault();
this.setState({'accountNavOpen': true}); this.setState({accountNavOpen: true});
}, }
closeAccountNav: function () { handleCloseAccountNav () {
this.setState({'accountNavOpen': false}); this.setState({accountNavOpen: false});
}, }
showCanceledDeletion: function () { showCanceledDeletion () {
this.setState({'canceledDeletionOpen': true}); this.setState({canceledDeletionOpen: true});
}, }
closeCanceledDeletion: function () { handleCloseCanceledDeletion () {
this.setState({'canceledDeletionOpen': false}); this.setState({canceledDeletionOpen: false});
}, }
closeRegistration: function () { handleCloseRegistration () {
this.setState({'registrationOpen': false}); this.setState({registrationOpen: false});
}, }
completeRegistration: function () { handleCompleteRegistration () {
this.props.dispatch(sessionActions.refreshSession()); this.props.dispatch(sessionActions.refreshSession());
this.closeRegistration(); this.handleCloseRegistration();
}, }
onSearchSubmit: function (formData) { handleSearchSubmit (formData) {
window.location.href = '/search/projects?q=' + encodeURIComponent(formData.q); window.location.href = `/search/projects?q=${encodeURIComponent(formData.q)}`;
}, }
render: function () { render () {
var classes = classNames({ const createLink = this.props.session.session.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home';
'logged-in': this.props.session.session.user
});
var messageClasses = classNames({
'message-count': true,
'show': this.props.unreadMessageCount > 0
});
var dropdownClasses = classNames({
'user-info': true,
'open': this.state.accountNavOpen
});
var formatMessage = this.props.intl.formatMessage;
var createLink = this.props.session.session.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home';
return ( return (
<NavigationBox className={classes}> <NavigationBox
className={classNames({
'logged-in': this.props.session.session.user
})}
>
<ul> <ul>
<li className="logo"><a href="/" aria-label="Scratch"></a></li> <li className="logo">
<a
aria-label="Scratch"
href="/"
/>
</li>
<li className="link create"> <li className="link create">
<a href={createLink}> <a href={createLink}>
@ -205,47 +220,76 @@ var Navigation = React.createClass({
</li> </li>
<li className="search"> <li className="search">
<Form onSubmit={this.onSearchSubmit}> <Form onSubmit={this.handleSearchSubmit}>
<Button type="submit" className="btn-search" /> <Button
<Input type="text" className="btn-search"
value={this.props.searchTerm} type="submit"
aria-label={formatMessage({id: 'general.search'})} />
placeholder={formatMessage({id: 'general.search'})} <Input
name="q" /> aria-label={this.props.intl.formatMessage({id: 'general.search'})}
name="q"
placeholder={this.props.intl.formatMessage({id: 'general.search'})}
type="text"
value={this.props.searchTerm}
/>
</Form> </Form>
</li> </li>
{this.props.session.status === sessionActions.Status.FETCHED ? ( {this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [ this.props.session.session.user ? [
<li className="link right messages" key="messages"> <li
className="link right messages"
key="messages"
>
<a <a
href="/messages/" href="/messages/"
title={formatMessage({id: 'general.messages'})}> title={this.props.intl.formatMessage({id: 'general.messages'})}
>
<span className={messageClasses}>{this.props.unreadMessageCount}</span> <span
className={classNames({
'message-count': true,
'show': this.props.unreadMessageCount > 0
})}
>{this.props.unreadMessageCount}</span>
<FormattedMessage id="general.messages" /> <FormattedMessage id="general.messages" />
</a> </a>
</li>, </li>,
<li className="link right mystuff" key="mystuff"> <li
className="link right mystuff"
key="mystuff"
>
<a <a
href="/mystuff/" href="/mystuff/"
title={formatMessage({id: 'general.myStuff'})}> title={this.props.intl.formatMessage({id: 'general.myStuff'})}
>
<FormattedMessage id="general.myStuff" /> <FormattedMessage id="general.myStuff" />
</a> </a>
</li>, </li>,
<li className="link right account-nav" key="account-nav"> <li
<a className={dropdownClasses} className="link right account-nav"
href="#" onClick={this.handleAccountNavClick}> key="account-nav"
<Avatar src={this.props.session.session.user.thumbnailUrl} alt="" /> >
<span className='profile-name'> <a
className={classNames({
'user-info': true,
'open': this.state.accountNavOpen
})}
href="#"
onClick={this.handleAccountNavClick}
>
<Avatar
alt=""
src={this.props.session.session.user.thumbnailUrl}
/>
<span className="profile-name">
{this.props.session.session.user.username} {this.props.session.session.user.username}
</span> </span>
</a> </a>
<Dropdown <Dropdown
as="ul" as="ul"
isOpen={this.state.accountNavOpen} className={process.env.SCRATCH_ENV}
onRequestClose={this.closeAccountNav} isOpen={this.state.accountNavOpen}
className={process.env.SCRATCH_ENV}> onRequestClose={this.handleCloseAccountNav}
>
<li> <li>
<a href={this.getProfileUrl()}> <a href={this.getProfileUrl()}>
<FormattedMessage id="general.profile" /> <FormattedMessage id="general.profile" />
@ -264,8 +308,8 @@ var Navigation = React.createClass({
</li> </li>
] : []} ] : []}
{this.props.permissions.student ? [ {this.props.permissions.student ? [
<li> <li key="my-class-li">
<a href={'/classes/' + this.props.session.session.user.classroomId + '/'}> <a href={`/classes/${this.props.session.session.user.classroomId}/`}>
<FormattedMessage id="general.myClass" /> <FormattedMessage id="general.myClass" />
</a> </a>
</li> </li>
@ -276,69 +320,123 @@ var Navigation = React.createClass({
</a> </a>
</li> </li>
<li className="divider"> <li className="divider">
<a href="#" onClick={this.handleLogOut}> <a
href="#"
onClick={this.handleLogOut}
>
<FormattedMessage id="navigation.signOut" /> <FormattedMessage id="navigation.signOut" />
</a> </a>
</li> </li>
</Dropdown> </Dropdown>
</li> </li>
] : [ ] : [
<li className="link right join" key="join"> <li
<a href="#" onClick={this.handleJoinClick}> className="link right join"
key="join"
>
<a
href="#"
onClick={this.handleJoinClick}
>
<FormattedMessage id="general.joinScratch" /> <FormattedMessage id="general.joinScratch" />
</a> </a>
</li>, </li>,
<Registration <Registration
key="registration" isOpen={this.state.registrationOpen}
isOpen={this.state.registrationOpen} key="registration"
onRequestClose={this.closeRegistration} onRegistrationDone={this.handleCompleteRegistration}
onRegistrationDone={this.completeRegistration} />, onRequestClose={this.handleCloseRegistration}
<li className="link right login-item" key="login"> />,
<li
className="link right login-item"
key="login"
>
<a <a
href="#"
onClick={this.handleLoginClick}
className="ignore-react-onclickoutside" className="ignore-react-onclickoutside"
key="login-link"> href="#"
<FormattedMessage id="general.signIn" /> key="login-link"
</a> onClick={this.handleLoginClick}
>
<FormattedMessage id="general.signIn" />
</a>
<Dropdown <Dropdown
className="login-dropdown with-arrow" className="login-dropdown with-arrow"
isOpen={this.state.loginOpen} isOpen={this.state.loginOpen}
onRequestClose={this.closeLogin} key="login-dropdown"
key="login-dropdown"> onRequestClose={this.handleCloseLogin}
>
<Login <Login
error={this.state.loginError}
onLogIn={this.handleLogIn} onLogIn={this.handleLogIn}
error={this.state.loginError} /> />
</Dropdown> </Dropdown>
</li> </li>
]) : [ ]) : []}
]}
</ul> </ul>
<Modal isOpen={this.state.canceledDeletionOpen} <Modal
onRequestClose={this.closeCanceledDeletion} isOpen={this.state.canceledDeletionOpen}
style={{content:{padding: 15}}}> style={{
content: {
padding: 15
}
}}
onRequestClose={this.handleCloseCanceledDeletion}
>
<h4>Your Account Will Not Be Deleted</h4> <h4>Your Account Will Not Be Deleted</h4>
<h4><FormattedMessage id="general.noDeletionTitle" /></h4>
<p> <p>
Your account was scheduled for deletion but you logged in. Your account has been reactivated. <FormattedMessage
If you didnt request for your account to be deleted, you should id="general.noDeletionDescription"
{' '}<a href="/accounts/password_reset/">change your password</a>{' '} values={{
to make sure your account is secure. resetLink: <a href="/accounts/password_reset/">
{this.props.intl.formatMessage({id: 'general.noDeletionLink'})}
</a>
}}
/>
</p> </p>
</Modal> </Modal>
</NavigationBox> </NavigationBox>
); );
} }
}); }
var mapStateToProps = function (state) { Navigation.propTypes = {
return { dispatch: PropTypes.func,
session: state.session, intl: intlShape,
permissions: state.permissions, permissions: PropTypes.shape({
unreadMessageCount: state.messageCount.messageCount, admin: PropTypes.bool,
searchTerm: state.navigation social: PropTypes.bool,
}; educator: PropTypes.bool,
educator_invitee: PropTypes.bool,
student: PropTypes.bool
}),
searchTerm: PropTypes.string,
session: PropTypes.shape({
session: PropTypes.shape({
user: PropTypes.shape({
classroomId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
thumbnailUrl: PropTypes.string,
username: PropTypes.string
})
}),
status: PropTypes.string
}),
unreadMessageCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
}; };
var ConnectedNavigation = connect(mapStateToProps)(Navigation); Navigation.defaultProps = {
session: {},
unreadMessageCount: 0, // bubble number to display how many notifications someone has.
searchTerm: ''
};
const mapStateToProps = state => ({
session: state.session,
permissions: state.permissions,
unreadMessageCount: state.messageCount.messageCount,
searchTerm: state.navigation
});
const ConnectedNavigation = connect(mapStateToProps)(Navigation);
module.exports = injectIntl(ConnectedNavigation); module.exports = injectIntl(ConnectedNavigation);

View file

@ -1,75 +1,84 @@
var classNames = require('classnames'); const classNames = require('classnames');
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var React = require('react'); const PropTypes = require('prop-types');
var Slider = require('react-slick'); const React = require('react');
const Slider = require('react-slick');
var Thumbnail = require('../thumbnail/thumbnail.jsx'); const Thumbnail = require('../thumbnail/thumbnail.jsx');
require('slick-carousel/slick/slick.scss'); require('slick-carousel/slick/slick.scss');
require('slick-carousel/slick/slick-theme.scss'); require('slick-carousel/slick/slick-theme.scss');
require('./nestedcarousel.scss'); require('./nestedcarousel.scss');
{/* /*
NestedCarousel is used to show a carousel, where each slide is composed of a few NestedCarousel is used to show a carousel, where each slide is composed of a few
thumbnails (for example, to show step-by-syep tips, where each stage has a few steps). thumbnails (for example, to show step-by-syep tips, where each stage has a few steps).
It creates the thumbnails without links. It creates the thumbnails without links.
Each slide has a title, and then a list of thumbnails, that will be shown together. Each slide has a title, and then a list of thumbnails, that will be shown together.
*/} */
var NestedCarousel = React.createClass({ const NestedCarousel = props => {
type: 'NestedCarousel', defaults(props.settings, {
propTypes: { dots: true,
items: React.PropTypes.array infinite: false,
}, lazyLoad: true,
getDefaultProps: function () { slidesToShow: 1,
return { slidesToScroll: 1,
items: require('./nestedcarousel.json') variableWidth: false
}; });
},
render: function () {
var settings = this.props.settings || {};
defaults(settings, {
dots: true,
infinite: false,
lazyLoad: true,
slidesToShow: 1,
slidesToScroll: 1,
variableWidth: false
});
var arrows = this.props.items.length > settings.slidesToShow; const arrows = props.items.length > props.settings.slidesToShow;
const stages = [];
var classes = classNames( for (let i = 0; i < props.items.length; i++) {
'nestedcarousel', const items = props.items[i].thumbnails;
'carousel', const thumbnails = [];
this.props.className for (let j = 0; j < items.length; j++) {
); const item = items[j];
thumbnails.push(
var stages = []; <Thumbnail
for (var i=0; i < this.props.items.length; i++) { key={`inner_${i}_${j}`}
var items = this.props.items[i].thumbnails; linkTitle={false}
var thumbnails = []; src={item.thumbnailUrl}
for (var j=0; j < items.length; j++) { title={item.title}
var item = items[j]; />
thumbnails.push( );
<Thumbnail key={'inner_' + i + '_' + j}
title={item.title}
src={item.thumbnailUrl}
linkTitle = {false} />);
}
stages.push(
<div key={'outer_' + i}>
<h3>{this.props.items[i].title}</h3>
{thumbnails}
</div>);
} }
return ( stages.push(
<Slider className={classes} arrows={arrows} {... settings}> <div key={`outer_${i}`}>
{stages} <h3>{props.items[i].title}</h3>
</Slider> {thumbnails}
</div>
); );
} }
}); return (
<Slider
arrows={arrows}
className={classNames('nestedcarousel', 'carousel', props.className)}
{...props.settings}
>
{stages}
</Slider>
);
};
NestedCarousel.propTypes = {
className: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.object),
settings: PropTypes.shape({
dots: PropTypes.bool,
infinite: PropTypes.bool,
lazyLoad: PropTypes.bool,
slidesToShow: PropTypes.number,
slidesToScroll: PropTypes.number,
variableWidth: PropTypes.bool
})
};
NestedCarousel.defaultProps = {
settings: {},
items: require('./nestedcarousel.json')
};
module.exports = NestedCarousel; module.exports = NestedCarousel;

View file

@ -1,54 +1,53 @@
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var Box = require('../box/box.jsx'); const Box = require('../box/box.jsx');
require('./news.scss'); require('./news.scss');
var News = React.createClass({ const News = props => (
type: 'News', <Box
propTypes: { className="news"
items: React.PropTypes.array moreHref="/discuss/5/"
}, moreTitle={props.messages['general.viewAll']}
getDefaultProps: function () { title={props.messages['news.scratchNews']}
return { >
items: require('./news.json'), <ul>
messages: { {props.items.map(item => (
'general.viewAll': 'View All', <li key={item.id}>
'news.scratchNews': 'Scratch News' <a href={item.url}>
} <img
}; alt=""
}, className="news-image"
render: function () { height="53"
return ( src={item.image}
<Box width="53"
className="news" />
title={this.props.messages['news.scratchNews']} <div className="news-description">
moreTitle={this.props.messages['general.viewAll']} <h4>{item.headline}</h4>
moreHref="/discuss/5/"> <p>{item.copy}</p>
</div>
</a>
</li>
))}
</ul>
</Box>
);
<ul> News.propTypes = {
{this.props.items.map(function (item) { items: PropTypes.arrayOf(PropTypes.object),
return ( messages: PropTypes.shape({
<li key={item.id}> 'general.viewAll': PropTypes.string,
<a href={item.url}> 'news.scratchNews': PropTypes.string
<img src={item.image} })
className="news-image" };
width="53"
height="53" News.defaultProps = {
alt="" items: require('./news.json'),
/> messages: {
<div className="news-description"> 'general.viewAll': 'View All',
<h4>{item.headline}</h4> 'news.scratchNews': 'Scratch News'
<p>{item.copy}</p>
</div>
</a>
</li>
);
})}
</ul>
</Box>
);
} }
}); };
module.exports = News; module.exports = News;

View file

@ -1,27 +1,27 @@
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var Navigation = require('../../../navigation/conference/2016/navigation.jsx'); const Navigation = require('../../../navigation/conference/2016/navigation.jsx');
var Footer = require('../../../footer/conference/2016/footer.jsx'); const Footer = require('../../../footer/conference/2016/footer.jsx');
require('../page.scss'); require('../page.scss');
var Page = React.createClass({ const Page = props => (
type: 'Page', <div className="page mod-conference">
render: function () { <div id="navigation">
return ( <Navigation />
<div className="page mod-conference"> </div>
<div id="navigation"> <div id="view">
<Navigation /> {props.children}
</div> </div>
<div id="view"> <div id="footer">
{this.props.children} <Footer />
</div> </div>
<div id="footer"> </div>
<Footer /> );
</div>
</div> Page.propTypes = {
); children: PropTypes.node
} };
});
module.exports = Page; module.exports = Page;

View file

@ -1,27 +1,27 @@
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var Navigation = require('../../../navigation/conference/2017/navigation.jsx'); const Navigation = require('../../../navigation/conference/2017/navigation.jsx');
var Footer = require('../../../footer/conference/2017/footer.jsx'); const Footer = require('../../../footer/conference/2017/footer.jsx');
require('../page.scss'); require('../page.scss');
var Page = React.createClass({ const Page = props => (
type: 'Page', <div className="page mod-conference">
render: function () { <div id="navigation">
return ( <Navigation />
<div className="page mod-conference"> </div>
<div id="navigation"> <div id="view">
<Navigation /> {props.children}
</div> </div>
<div id="view"> <div id="footer">
{this.props.children} <Footer />
</div> </div>
<div id="footer"> </div>
<Footer /> );
</div>
</div> Page.propTypes = {
); children: PropTypes.node
} };
});
module.exports = Page; module.exports = Page;

View file

@ -1,27 +1,27 @@
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var Navigation = require('../../../navigation/conference/2018/navigation.jsx'); const Navigation = require('../../../navigation/conference/2018/navigation.jsx');
var Footer = require('../../../footer/conference/2018/footer.jsx'); const Footer = require('../../../footer/conference/2018/footer.jsx');
require('../page.scss'); require('../page.scss');
var Page = React.createClass({ const Page = props => (
type: 'Page', <div className="page mod-conference">
render: function () { <div id="navigation">
return ( <Navigation />
<div className="page mod-conference"> </div>
<div id="navigation"> <div id="view">
<Navigation /> {props.children}
</div> </div>
<div id="view"> <div id="footer">
{this.props.children} <Footer />
</div> </div>
<div id="footer"> </div>
<Footer /> );
</div>
</div> Page.propTypes = {
); children: PropTypes.node
} };
});
module.exports = Page; module.exports = Page;

View file

@ -1,29 +1,31 @@
var React = require('react'); const classNames = require('classnames');
var classNames = require('classnames'); const PropTypes = require('prop-types');
const React = require('react');
var Navigation = require('../../navigation/www/navigation.jsx'); const Navigation = require('../../navigation/www/navigation.jsx');
var Footer = require('../../footer/www/footer.jsx'); const Footer = require('../../footer/www/footer.jsx');
var Page = React.createClass({ const Page = props => (
type: 'Page', <div className="page">
render: function () { <div
var classes = classNames({ className={classNames({
'staging': process.env.SCRATCH_ENV == 'staging' staging: process.env.SCRATCH_ENV === 'staging'
}); })}
return ( id="navigation"
<div className="page"> >
<div id="navigation" className={classes}> <Navigation />
<Navigation /> </div>
</div> <div id="view">
<div id="view"> {props.children}
{this.props.children} </div>
</div> <div id="footer">
<div id="footer"> <Footer />
<Footer /> </div>
</div> </div>
</div> );
);
} Page.propTypes = {
}); children: PropTypes.node
};
module.exports = Page; module.exports = Page;

View file

@ -1,42 +1,45 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
module.exports = React.createClass({ const Progression = props => {
displayName: 'Progression', const childProps = {
propTypes: { activeStep: props.step,
step: function (props, propName, componentName) { totalSteps: React.Children.count(props.children)
var stepValidator = function (props, propName) { };
if (props[propName] > -1 && props[propName] < props.children.length) { return (
return null; <div
} else { className={classNames('progression', props.className)}
return new Error('Prop `step` out of range'); {...props}
>
{React.Children.map(props.children, (child, id) => {
if (id === props.step) {
return React.cloneElement(child, childProps);
} }
}; })}
return ( </div>
React.PropTypes.number.isRequired(props, propName, componentName) || );
stepValidator(props, propName, componentName) };
);
} Progression.propTypes = {
}, children: PropTypes.node,
getDefaultProps: function () { className: PropTypes.string,
return { step: function (props, propName, componentName) {
step: 0 const stepValidator = (propz, name) => {
}; if (propz[name] > -1 && propz[name] < propz.children.length) {
}, return null;
render: function () { }
var childProps = { return new Error('Prop `step` out of range');
activeStep: this.props.step,
totalSteps: React.Children.count(this.props.children)
}; };
return ( return (
<div {... this.props} (typeof props[propName] === 'number' ? null : new Error('Not a number')) ||
className={classNames('progression', this.props.className)}> stepValidator(props, propName, componentName)
{React.Children.map(this.props.children, function (child, id) {
if (id === this.props.step) {
return React.cloneElement(child, childProps);
}
}, this)}
</div>
); );
} }
}); };
Progression.defaultProps = {
step: 0
};
module.exports = Progression;

View file

@ -1,53 +1,62 @@
var React = require('react'); const bindAll = require('lodash.bindall');
var IframeModal = require('../modal/iframe/modal.jsx'); const PropTypes = require('prop-types');
const React = require('react');
const IframeModal = require('../modal/iframe/modal.jsx');
require('./registration.scss'); require('./registration.scss');
var Registration = React.createClass({ class Registration extends React.Component {
propTypes: { constructor (props) {
isOpen: React.PropTypes.bool, super(props);
onRegistrationDone: React.PropTypes.func, bindAll(this, [
onRequestClose: React.PropTypes.func 'handleMessage',
}, 'toggleMessageListener'
onMessage: function (e) { ]);
if (e.origin != window.location.origin) return; }
if (e.source != this.registrationIframe.contentWindow) return; componentDidMount () {
if (e.data == 'registration-done') this.props.onRegistrationDone(); if (this.props.isOpen) this.toggleMessageListener(true);
if (e.data == 'registration-relaunch') { }
componentDidUpdate (prevProps) {
this.toggleMessageListener(this.props.isOpen && !prevProps.isOpen);
}
componentWillUnmount () {
this.toggleMessageListener(false);
}
handleMessage (e) {
if (e.origin !== window.location.origin) return;
if (e.source !== this.registrationIframe.contentWindow) return;
if (e.data === 'registration-done') this.props.onRegistrationDone();
if (e.data === 'registration-relaunch') {
this.registrationIframe.contentWindow.location.reload(); this.registrationIframe.contentWindow.location.reload();
} }
}, }
toggleMessageListener: function (present) { toggleMessageListener (present) {
if (present) { if (present) {
window.addEventListener('message', this.onMessage); window.addEventListener('message', this.handleMessage);
} else { } else {
window.removeEventListener('message', this.onMessage); window.removeEventListener('message', this.handleMessage);
} }
}, }
componentDidMount: function () { render () {
if (this.props.isOpen) this.toggleMessageListener(true);
},
componentDidUpdate: function (prevProps) {
this.toggleMessageListener(this.props.isOpen && !prevProps.isOpen);
},
componentWillUnmount: function () {
this.toggleMessageListener(false);
},
render: function () {
return ( return (
<IframeModal <IframeModal
isOpen={this.props.isOpen}
onRequestClose={this.props.onRequestClose}
className="mod-registration" className="mod-registration"
componentRef={ componentRef={iframe => { // eslint-disable-line react/jsx-no-bind
function (iframe) { this.registrationIframe = iframe;
this.registrationIframe = iframe; }}
}.bind(this) isOpen={this.props.isOpen}
}
src="/accounts/standalone-registration/" src="/accounts/standalone-registration/"
onRequestClose={this.props.onRequestClose}
/> />
); );
} }
}); }
Registration.propTypes = {
isOpen: PropTypes.bool,
onRegistrationDone: PropTypes.func,
onRequestClose: PropTypes.func
};
module.exports = Registration; module.exports = Registration;

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,18 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./slide.scss'); require('./slide.scss');
var Slide = React.createClass({ const Slide = props => (
displayName: 'Slide', <div className={classNames(['slide', props.className])}>
render: function () { {props.children}
return ( </div>
<div className={classNames(['slide', this.props.className])}> );
{this.props.children}
</div> Slide.propTypes = {
); children: PropTypes.node,
} className: PropTypes.string
}); };
module.exports = Slide; module.exports = Slide;

View file

@ -1,57 +1,47 @@
var classNames = require('classnames'); const classNames = require('classnames');
var FormattedRelative = require('react-intl').FormattedRelative; const FormattedRelative = require('react-intl').FormattedRelative;
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var FlexRow = require('../flex-row/flex-row.jsx'); const FlexRow = require('../flex-row/flex-row.jsx');
require('./social-message.scss'); require('./social-message.scss');
var SocialMessage = React.createClass({ const SocialMessage = props => (
type: 'SocialMessage', <props.as className={classNames('social-message', props.className)}>
propTypes: { <FlexRow className="mod-social-message">
as: React.PropTypes.string, <div className="social-message-content">
datetime: React.PropTypes.string.isRequired, {typeof props.iconSrc === 'undefined' ? [] : [
iconSrc: React.PropTypes.string, <img
iconAlt: React.PropTypes.string, alt={props.iconAlt}
imgClassName: React.PropTypes.string className={classNames('social-message-icon', props.imgClassName)}
}, key="social-message-icon"
getDefaultProps: function () { src={props.iconSrc}
return { />
as: 'li' ]}
}; <div>
}, {props.children}
render: function () { </div>
var classes = classNames( </div>
'social-message', <span className="social-message-date">
this.props.className <FormattedRelative value={new Date(props.datetime)} />
); </span>
var imgClass = classNames( </FlexRow>
'social-message-icon', </props.as>
this.props.imgClassName );
);
return ( SocialMessage.propTypes = {
<this.props.as className={classes}> as: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
<FlexRow className="mod-social-message"> children: PropTypes.node,
<div className="social-message-content"> className: PropTypes.string,
{typeof this.props.iconSrc !== 'undefined' ? [ datetime: PropTypes.string.isRequired,
<img iconAlt: PropTypes.string,
key="social-message-icon" iconSrc: PropTypes.string,
className={imgClass} imgClassName: PropTypes.string
src={this.props.iconSrc} };
alt={this.props.iconAlt}
/> SocialMessage.defaultProps = {
] : []} as: 'li'
<div> };
{this.props.children}
</div>
</div>
<span className="social-message-date">
<FormattedRelative value={new Date(this.props.datetime)} />
</span>
</FlexRow>
</this.props.as>
);
}
});
module.exports = SocialMessage; module.exports = SocialMessage;

View file

@ -1,20 +1,18 @@
var range = require('lodash.range'); const range = require('lodash.range');
var React = require('react'); const React = require('react');
require('./spinner.scss'); require('./spinner.scss');
var Spinner = React.createClass({ // Adapted from http://tobiasahlin.com/spinkit/
// Adapted from http://tobiasahlin.com/spinkit/ const Spinner = () => (
type: 'Spinner', <div className="spinner">
render: function () { {range(1, 13).map(id => (
return ( <div
<div className="spinner"> className={`circle${id} circle`}
{range(1,13).map(function (id) { key={`circle${id}`}
return <div className={'circle' + id + ' circle'}></div>; />
})} ))}
</div> </div>
); );
}
});
module.exports = Spinner; module.exports = Spinner;

View file

@ -1,28 +1,29 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./stepnavigation.scss'); require('./stepnavigation.scss');
var StepNavigation = React.createClass({ const StepNavigation = props => (
type: 'Navigation', <ul className={classNames('step-navigation', props.className)}>
render: function () { {Array.apply(null, Array(props.steps)).map((v, step) => (
return ( <li
<ul className={classNames('step-navigation', this.props.className)}> className={classNames({
{Array.apply(null, Array(this.props.steps)).map(function (v, step) { active: step < props.active,
return ( selected: step === props.active
<li key={step} })}
className={classNames({ key={step}
active: step < this.props.active, >
selected: step === this.props.active <div className="indicator" />
})} </li>
> ))}
<div className="indicator" /> </ul>
</li> );
);
}.bind(this))} StepNavigation.propTypes = {
</ul> active: PropTypes.number,
); className: PropTypes.string,
} steps: PropTypes.number
}); };
module.exports = StepNavigation; module.exports = StepNavigation;

View file

@ -1,36 +1,38 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./subnavigation.scss'); require('./subnavigation.scss');
/** /*
* Container for a custom, horizontal list of navigation elements * Container for a custom, horizontal list of navigation elements
* that can be displayed within a view or component. * that can be displayed within a view or component.
*/ */
var SubNavigation = React.createClass({ const SubNavigation = props => (
type: 'SubNavigation', <div
getDefaultProps: function () { className={classNames(
return {
align: 'middle'
};
},
render: function () {
var classes = classNames(
[ [
'sub-nav', 'sub-nav',
this.props.className props.className
], ],
{ {
'sub-nav-align-left': this.props.align === 'left', 'sub-nav-align-left': props.align === 'left',
'sub-nav-align-right': this.props.align === 'right' 'sub-nav-align-right': props.align === 'right'
} }
); )}
return ( >
<div className={classes}> {props.children}
{this.props.children} </div>
</div> );
);
} SubNavigation.propTypes = {
}); align: PropTypes.string,
children: PropTypes.node,
className: PropTypes.string
};
SubNavigation.defaultProps = {
align: 'middle'
};
module.exports = SubNavigation; module.exports = SubNavigation;

View file

@ -1,28 +1,26 @@
var classNames = require('classnames'); const classNames = require('classnames');
var SubNavigation = require('../../components/subnavigation/subnavigation.jsx'); const PropTypes = require('prop-types');
var React = require('react'); const React = require('react');
const SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
require('./tabs.scss'); require('./tabs.scss');
/** /*
* Container for a custom, horizontal list of navigation elements * Container for a custom, horizontal list of navigation elements
* that can be displayed within a view or component. * that can be displayed within a view or component.
*/ */
var Tabs = React.createClass({ const Tabs = props => (
type: 'Tabs', <div className="tab-background">
render: function () { <SubNavigation className={classNames('tabs', props.className)}>
var classes = classNames( {props.children}
'tabs', </SubNavigation>
this.props.className </div>
); );
return (
<div className='tab-background'> Tabs.propTypes = {
<SubNavigation className={classes}> children: PropTypes.node,
{this.props.children} className: PropTypes.string
</SubNavigation> };
</div>
);
}
});
module.exports = Tabs; module.exports = Tabs;

View file

@ -1,86 +1,100 @@
var classNames = require('classnames'); const classNames = require('classnames');
var connect = require('react-redux').connect; const connect = require('react-redux').connect;
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var sessionActions = require('../../redux/session.js'); const sessionActions = require('../../redux/session.js');
var TitleBanner = require('../title-banner/title-banner.jsx'); const TitleBanner = require('../title-banner/title-banner.jsx');
var Button = require('../forms/button.jsx'); const Button = require('../forms/button.jsx');
var FlexRow = require('../flex-row/flex-row.jsx'); const FlexRow = require('../flex-row/flex-row.jsx');
require('./teacher-banner.scss'); require('./teacher-banner.scss');
var TeacherBanner = React.createClass({ const TeacherBanner = props => (
type: 'TeacherBanner', <TitleBanner className={classNames('teacher-banner', props.className)}>
getDefaultProps: function () { <FlexRow className="inner">
return { <div className="welcome">
messages: { {props.sessionStatus === sessionActions.Status.FETCHED ? (
'teacherbanner.greeting': 'Hi', props.user ? [
'teacherbanner.subgreeting': 'Teacher Account', <h3 key="greeting">
'teacherbanner.classesButton': 'My Classes', {props.messages['teacherbanner.greeting']},{' '}
'teacherbanner.resourcesButton': 'Educator Resources', {props.user.username}
'teacherbanner.faqButton': 'Teacher Account FAQ' </h3>,
}, <p
session: {} className="title-banner-p"
}; key="subgreeting"
}, >
render: function () { {props.messages['teacherbanner.subgreeting']}
var classes = classNames( </p>
'teacher-banner', ] : []
this.props.className ) : []}
); </div>
return ( <FlexRow className="quick-links">
<TitleBanner className={classes}> {props.sessionStatus === sessionActions.Status.FETCHED ? (
<FlexRow className="inner"> props.user ? [
<div className="welcome"> <a
{this.props.session.status === sessionActions.Status.FETCHED ? ( href="/educators/classes"
this.props.session.session.user ? [ key="classes-button"
<h3 key="greeting"> >
{this.props.messages['teacherbanner.greeting']},{' '} <Button>
{this.props.session.session.user.username} {props.messages['teacherbanner.classesButton']}
</h3>, </Button>
<p </a>,
key="subgreeting" <a
className="title-banner-p" href="/info/educators"
> key="resources-button"
{this.props.messages['teacherbanner.subgreeting']} >
</p> <Button>
] : [] {props.messages['teacherbanner.resourcesButton']}
): []} </Button>
</div> </a>,
<FlexRow className="quick-links"> <a
{this.props.session.status === sessionActions.Status.FETCHED ? ( href="/educators/faq"
this.props.session.session.user ? [ key="faq-button"
<a href="/educators/classes" key="classes-button"> >
<Button> <Button>
{this.props.messages['teacherbanner.classesButton']} {props.messages['teacherbanner.faqButton']}
</Button> </Button>
</a>, </a>
<a href="/info/educators" key="resources-button"> ] : []
<Button> ) : []}
{this.props.messages['teacherbanner.resourcesButton']} </FlexRow>
</Button> </FlexRow>
</a>, </TitleBanner>
<a href="/educators/faq" key="faq-button"> );
<Button>
{this.props.messages['teacherbanner.faqButton']}
</Button>
</a>
] : []
): []}
</FlexRow>
</FlexRow>
</TitleBanner>
);
}
});
var mapStateToProps = function (state) { TeacherBanner.propTypes = {
return { className: PropTypes.string,
session: state.session messages: PropTypes.shape({
}; 'teacherbanner.greeting': PropTypes.string,
'teacherbanner.subgreeting': PropTypes.string,
'teacherbanner.classesButton': PropTypes.string,
'teacherbanner.resourcesButton': PropTypes.string,
'teacherbanner.faqButton': PropTypes.string
}),
sessionStatus: PropTypes.string,
user: PropTypes.shape({
username: PropTypes.string
})
}; };
var ConnectedTeacherBanner = connect(mapStateToProps)(TeacherBanner); TeacherBanner.defaultProps = {
messages: {
'teacherbanner.greeting': 'Hi',
'teacherbanner.subgreeting': 'Teacher Account',
'teacherbanner.classesButton': 'My Classes',
'teacherbanner.resourcesButton': 'Educator Resources',
'teacherbanner.faqButton': 'Teacher Account FAQ'
},
user: {}
};
const mapStateToProps = state => ({
sessionStatus: state.session.status,
user: state.session.session.user
});
const ConnectedTeacherBanner = connect(mapStateToProps)(TeacherBanner);
module.exports = ConnectedTeacherBanner; module.exports = ConnectedTeacherBanner;

View file

@ -1,186 +1,168 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./thumbnail.scss'); require('./thumbnail.scss');
var Thumbnail = React.createClass({ const Thumbnail = props => {
type: 'Thumbnail', const extra = [];
propTypes: { const info = [];
src: React.PropTypes.string
},
getInitialState: function () {
return {
srcFallback: false,
avatarFallback: false
};
},
getDefaultProps: function () {
return {
href: '#',
title: 'Project',
src: '',
srcDefault: 'https://uploads.scratch.mit.edu/projects/thumbnails/default.png',
avatar: '',
avatarDefault: 'https://uploads.scratch.mit.edu/users/avatars/default.png',
type: 'project',
showLoves: false,
showFavorites: false,
showRemixes: false,
showViews: false,
showAvatar: false,
linkTitle: true,
alt: ''
};
},
handleSrcError: function () {
this.setState({srcFallback: true});
},
handleAvatarError: function () {
this.setState({avatarFallback: true});
},
render: function () {
var classes = classNames(
'thumbnail',
this.props.type,
this.props.className
);
var extra = [];
var info = [];
if (this.props.loves && this.props.showLoves) { if (props.loves && props.showLoves) {
extra.push( extra.push(
<div <div
key="loves" className="thumbnail-loves"
className="thumbnail-loves" key="loves"
title={this.props.loves + ' loves'}> title={`${props.loves} loves`}
{this.props.loves} >
</div> {props.loves}
);
}
if (this.props.favorites && this.props.showFavorites) {
extra.push(
<div
key="favorites"
className="thumbnail-favorites"
title={this.favorites + ' favorites'}>
{this.props.favorites}
</div>
);
}
if (this.props.remixes && this.props.showRemixes) {
extra.push(
<div
key="remixes"
className="thumbnail-remixes"
title={this.props.remixes + ' remixes'}
>
{this.props.remixes}
</div>
);
}
if (this.props.views && this.props.showViews) {
extra.push(
<div
key="views"
className="thumbnail-views"
title={this.props.views + ' views'}
>
{this.props.views}
</div>
);
}
var imgElement, titleElement, avatarElement;
if (this.props.linkTitle) {
if (this.state.srcFallback) {
imgElement = (
<a
className="thumbnail-image"
href={this.props.href}
key="imgElement"
>
<img
alt={this.props.alt}
src={this.props.srcDefault}
/>
</a>
);
} else {
imgElement = (
<a
className="thumbnail-image"
href={this.props.href}
key="imgElement"
>
<img
alt={this.props.alt}
src={this.props.src}
onError={this.handleSrcError}
/>
</a>
);
}
titleElement = (
<a href={this.props.href} key="titleElement">
{this.props.title}
</a>
);
} else {
imgElement = <img src={this.props.src} />;
titleElement = this.props.title;
}
info.push(titleElement);
if (this.props.creator) {
info.push(
<div key="creator" className="thumbnail-creator">
<a href={'/users/' + this.props.creator + '/'}>{this.props.creator}</a>
</div>
);
}
if (this.props.avatar && this.props.showAvatar) {
if (this.state.avatarFallback) {
avatarElement = (
<a
className="creator-image"
href={'/users/' + this.props.creator + '/'}
>
<img
alt={this.props.creator}
src={this.props.avatarDefault}
/>
</a>
);
} else {
avatarElement = (
<a
className="creator-image"
href={'/users/' + this.props.creator + '/'}
>
<img
alt={this.props.creator}
src={this.props.avatar}
onError={this.handleAvatarError}
/>
</a>
);
}
}
return (
<div className={classes} >
{imgElement}
<div className="thumbnail-info">
{avatarElement}
<div className="thumbnail-title">
{info}
</div>
</div>
{extra}
</div> </div>
); );
} }
}); if (props.favorites && props.showFavorites) {
extra.push(
<div
className="thumbnail-favorites"
key="favorites"
title={`${props.favorites} favorites`}
>
{props.favorites}
</div>
);
}
if (props.remixes && props.showRemixes) {
extra.push(
<div
className="thumbnail-remixes"
key="remixes"
title={`${props.remixes} remixes`}
>
{props.remixes}
</div>
);
}
if (props.views && props.showViews) {
extra.push(
<div
className="thumbnail-views"
key="views"
title={`${props.views} views`}
>
{props.views}
</div>
);
}
let imgElement;
let titleElement;
let avatarElement;
if (props.linkTitle) {
imgElement = (
<a
className="thumbnail-image"
href={props.href}
key="imgElement"
>
<img
alt={props.alt}
src={props.src}
/>
</a>
);
titleElement = (
<a
href={props.href}
key="titleElement"
>
{props.title}
</a>
);
} else {
imgElement = <img src={props.src} />;
titleElement = props.title;
}
info.push(titleElement);
if (props.creator) {
info.push(
<div
className="thumbnail-creator"
key="creator"
>
<a href={`/users/${props.creator}/`}>{props.creator}</a>
</div>
);
}
if (props.avatar && props.showAvatar) {
avatarElement = (
<a
className="creator-image"
href={`/users/${props.creator}/`}
>
<img
alt={props.creator}
src={props.avatar}
/>
</a>
);
}
return (
<div
className={classNames(
'thumbnail',
props.type,
props.className
)}
>
{imgElement}
<div className="thumbnail-info">
{avatarElement}
<div className="thumbnail-title">
{info}
</div>
</div>
{extra}
</div>
);
};
Thumbnail.propTypes = {
alt: PropTypes.string,
avatar: PropTypes.string,
className: PropTypes.string,
creator: PropTypes.string,
favorites: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
href: PropTypes.string,
linkTitle: PropTypes.bool,
loves: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
remixes: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
showAvatar: PropTypes.bool,
showFavorites: PropTypes.bool,
showLoves: PropTypes.bool,
showRemixes: PropTypes.bool,
showViews: PropTypes.bool,
src: PropTypes.string,
title: PropTypes.string,
type: PropTypes.string,
views: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
};
Thumbnail.defaultProps = {
alt: '',
avatar: '',
href: '#',
linkTitle: true,
showAvatar: false,
showFavorites: false,
showLoves: false,
showRemixes: false,
showViews: false,
src: '',
title: 'Project',
type: 'project'
};
module.exports = Thumbnail; module.exports = Thumbnail;

View file

@ -1,21 +1,18 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./title-banner.scss'); require('./title-banner.scss');
var TitleBanner = React.createClass({ const TitleBanner = props => (
type: 'TitleBanner', <div className={classNames('title-banner', props.className)}>
render: function () { {props.children}
var classes = classNames( </div>
'title-banner', );
this.props.className
); TitleBanner.propTypes = {
return ( children: PropTypes.node,
<div className={classes}> className: PropTypes.string
{this.props.children} };
</div>
);
}
});
module.exports = TitleBanner; module.exports = TitleBanner;

View file

@ -1,33 +1,39 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./tooltip.scss'); require('./tooltip.scss');
var Tooltip = React.createClass({ const Tooltip = props => (
type: 'Tooltip', <span
getDefaultProps: function () { className={classNames(
return {
title: '',
tipContent: ''
};
},
render: function () {
var classes = classNames(
'tooltip', 'tooltip',
this.props.className, props.className,
{overmax: (this.props.currentCharacters > this.props.maxCharacters)} {overmax: (props.currentCharacters > props.maxCharacters)}
); )}
return ( >
<span className={classes}> <span className="tip">
<span className="tip"> <img
<img src="/svgs/tooltip/info.svg" alt="info icon" /> alt="info icon"
</span> src="/svgs/tooltip/info.svg"
<span className="expand"> />
{this.props.tipContent} </span>
</span> <span className="expand">
</span> {props.tipContent}
); </span>
} </span>
}); );
Tooltip.propTypes = {
className: PropTypes.string,
currentCharacters: PropTypes.number,
maxCharacters: PropTypes.number,
tipContent: PropTypes.node
};
Tooltip.defaultProps = {
title: '',
tipContent: ''
};
module.exports = Tooltip; module.exports = Tooltip;

View file

@ -1,58 +1,68 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const FormattedMessage = require('react-intl').FormattedMessage;
var FormattedMessage = require('react-intl').FormattedMessage; const PropTypes = require('prop-types');
const React = require('react');
require('../forms/button.scss'); require('../forms/button.scss');
require('./ttt-tile.scss'); require('./ttt-tile.scss');
var TTTTile = React.createClass({ const TTTTile = props => (
type: 'TTTTile', <div className={classNames('ttt-tile', props.className)}>
propTypes: { <a href={props.tutorialLoc}>
title: React.PropTypes.string.isRequired, <div className="ttt-tile-tutorial">
description: React.PropTypes.string, <div className="ttt-tile-image">
thumbUrl: React.PropTypes.string.isRequired, <img
tutorialLoc: React.PropTypes.string.isRequired, alt=""
onGuideClick: React.PropTypes.func className="ttt-tile-image-img"
}, src={props.thumbUrl}
render: function () { />
var classes = classNames( <div className="ttt-tile-image-try">
'ttt-tile', <div className="button mod-ttt-try-button">
this.props.className <FormattedMessage id="tile.tryIt" />
);
return (
<div className={classes} >
<a href={this.props.tutorialLoc}>
<div className="ttt-tile-tutorial">
<div className="ttt-tile-image">
<img className="ttt-tile-image-img" src={this.props.thumbUrl} alt="" />
<div className="ttt-tile-image-try">
<div className="button mod-ttt-try-button">
<FormattedMessage id="tile.tryIt" />
</div>
</div>
</div>
<div className="ttt-tile-info">
<div className="ttt-tile-tag">
<FormattedMessage id='ttt.tutorial' defaultMessage='Tutorial'/>
</div>
<h4 className="ttt-tile-title">{this.props.title}</h4>
<p className="ttt-tile-description">
{this.props.description}
</p>
</div> </div>
</div> </div>
</div>
<div className="ttt-tile-info">
</a> <div className="ttt-tile-tag">
{this.props.onGuideClick && ( <FormattedMessage
<div className="ttt-tile-guides" onClick={this.props.onGuideClick}> defaultMessage="Tutorial"
<FormattedMessage id='tile.guides' defaultMessage='See Cards and Guides'/> id="ttt.tutorial"
<img className="ttt-tile-open-modal" src="/svgs/modal/open-blue.svg" /> />
</div> </div>
)} <h4 className="ttt-tile-title">{props.title}</h4>
<p className="ttt-tile-description">
{props.description}
</p>
</div>
</div> </div>
);
} </a>
}); {props.onGuideClick && (
<div
className="ttt-tile-guides"
onClick={props.onGuideClick}
>
<FormattedMessage
defaultMessage="See Cards and Guides"
id="tile.guides"
/>
<img
className="ttt-tile-open-modal"
src="/svgs/modal/open-blue.svg"
/>
</div>
)}
</div>
);
TTTTile.propTypes = {
className: PropTypes.string,
description: PropTypes.string,
onGuideClick: PropTypes.func,
thumbUrl: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
tutorialLoc: PropTypes.string.isRequired
};
module.exports = TTTTile; module.exports = TTTTile;

View file

@ -1,69 +1,81 @@
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var Box = require('../box/box.jsx'); const Box = require('../box/box.jsx');
require('./welcome.scss'); require('./welcome.scss');
var Welcome = React.createClass({ const Welcome = props => (
type: 'Welcome', <Box
propTypes: { className="welcome"
onDismiss: React.PropTypes.func moreHref="#"
}, moreProps={{
getDefaultProps: function () { className: 'close',
return { title: 'Dismiss',
messages: { onClick: props.onDismiss
'welcome.welcomeToScratch': 'Welcome to Scratch!', }}
'welcome.learn': 'Learn how to make a project in Scratch', moreTitle="x"
'welcome.tryOut': 'Try out starter projects', title={props.messages['welcome.welcomeToScratch']}
'welcome.connect': 'Connect with other Scratchers' >
} <div className="welcome-col blue">
}; <h4>
}, <a href="/projects/editor/?tip_bar=getStarted">
render: function () { {props.messages['welcome.learn']}
return ( </a>
<Box title={this.props.messages['welcome.welcomeToScratch']} </h4>
className="welcome" <a href="/projects/editor/?tip_bar=getStarted">
moreTitle="x" <img
moreHref="#" alt="Get Started"
moreProps={{ src="/images/welcome-learn.png"
className: 'close', />
title: 'Dismiss', </a>
onClick: this.props.onDismiss </div>
}}> <div className="welcome-col green">
<h4>
<a href="/starter_projects/">
{props.messages['welcome.tryOut']}
</a>
</h4>
<a href="/starter_projects/">
<img
alt="Starter Projects"
src="/images/welcome-try.png"
/>
</a>
</div>
<div className="welcome-col pink">
<h4>
<a href="/studios/146521/">
{props.messages['welcome.connect']}
</a>
</h4>
<a href="/studios/146521/">
<img
alt="Connect"
src="/images/welcome-connect.png"
/>
</a>
</div>
</Box>
);
<div className="welcome-col blue"> Welcome.propTypes = {
<h4> messages: PropTypes.shape({
<a href="/projects/editor/?tip_bar=getStarted"> 'welcome.welcomeToScratch': PropTypes.string,
{this.props.messages['welcome.learn']} 'welcome.learn': PropTypes.string,
</a> 'welcome.tryOut': PropTypes.string,
</h4> 'welcome.connect': PropTypes.string
<a href="/projects/editor/?tip_bar=getStarted"> }),
<img src="/images/welcome-learn.png" alt="Get Started" /> onDismiss: PropTypes.func
</a> };
</div>
<div className="welcome-col green"> Welcome.defaultProps = {
<h4> messages: {
<a href="/starter_projects/"> 'welcome.welcomeToScratch': 'Welcome to Scratch!',
{this.props.messages['welcome.tryOut']} 'welcome.learn': 'Learn how to make a project in Scratch',
</a> 'welcome.tryOut': 'Try out starter projects',
</h4> 'welcome.connect': 'Connect with other Scratchers'
<a href="/starter_projects/">
<img src="/images/welcome-try.png" alt="Starter Projects" />
</a>
</div>
<div className="welcome-col pink">
<h4>
<a href="/studios/146521/">
{this.props.messages['welcome.connect']}
</a>
</h4>
<a href="/studios/146521/">
<img src="/images/welcome-connect.png" alt="Connect" />
</a>
</div>
</Box>
);
} }
}); };
module.exports = Welcome; module.exports = Welcome;

View file

@ -1,12 +1,12 @@
var jar = require('./lib/jar'); const jar = require('./lib/jar');
var Raven = require('raven-js'); const Raven = require('raven-js');
/** /**
* ----------------------------------------------------------------------------- * -----------------------------------------------------------------------------
* Error handling * Error handling
* ----------------------------------------------------------------------------- * -----------------------------------------------------------------------------
*/ */
(function () { (() => {
if (process.env.SENTRY_DSN !== '') { if (process.env.SENTRY_DSN !== '') {
Raven.config(process.env.SENTRY_DSN).install(); Raven.config(process.env.SENTRY_DSN).install();
} }
@ -17,22 +17,22 @@ var Raven = require('raven-js');
* L10N * L10N
* ----------------------------------------------------------------------------- * -----------------------------------------------------------------------------
*/ */
(function () { (() => {
/** /*
* Bind locale code from cookie if available. Uses navigator language API as a fallback. * Bind locale code from cookie if available. Uses navigator language API as a fallback.
* *
* @return {string} * @return {string}
*/ */
function updateLocale () { const updateLocale = () => {
var obj = jar.get('scratchlanguage'); let obj = jar.get('scratchlanguage');
if (typeof obj === 'undefined') { if (typeof obj === 'undefined') {
obj = window.navigator.userLanguage || window.navigator.language; obj = window.navigator.userLanguage || window.navigator.language;
if (['pt','pt-pt','PT','PT-PT'].indexOf(obj) !== -1) { if (['pt', 'pt-pt', 'PT', 'PT-PT'].indexOf(obj) !== -1) {
obj = 'pt-br'; // default Portuguese users to Brazilian Portuguese due to our user base. Added in 2.2.5. obj = 'pt-br'; // default Portuguese users to Brazilian Portuguese due to our user base. Added in 2.2.5.
} }
} }
return obj; return obj;
} };
window._locale = updateLocale(); window._locale = updateLocale();
})(); })();

View file

@ -47,6 +47,9 @@
"general.myClass": "My Class", "general.myClass": "My Class",
"general.myClasses": "My Classes", "general.myClasses": "My Classes",
"general.myStuff": "My Stuff", "general.myStuff": "My Stuff",
"general.noDeletionTitle": "Your Account Will Not Be Deleted",
"general.noDeletionDescription": "Your account was scheduled for deletion but you logged in. Your account has been reactivated. If you didnt request for your account to be deleted, you should {resetLink} to make sure your account is secure.",
"general.noDeletionLink": "change your password",
"general.notRequired": "Not Required", "general.notRequired": "Not Required",
"general.other": "Other", "general.other": "Other",
"general.offlineEditor": "Offline Editor", "general.offlineEditor": "Offline Editor",

View file

@ -1,9 +1,9 @@
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var xhr = require('xhr'); const xhr = require('xhr');
var jar = require('./jar'); const jar = require('./jar');
var log = require('./log'); const log = require('./log');
var urlParams = require('./url-params'); const urlParams = require('./url-params');
/** /**
* Helper method that constructs requests to the scratch api. * Helper method that constructs requests to the scratch api.
@ -12,9 +12,11 @@ var urlParams = require('./url-params');
* CSRF forgeries (see: https://www.squarefree.com/securitytips/web-developers.html#CSRF) * CSRF forgeries (see: https://www.squarefree.com/securitytips/web-developers.html#CSRF)
* *
* It also takes in other arguments specified in the xhr library spec. * It also takes in other arguments specified in the xhr library spec.
*
* @param {object} opts optional xhr args (see above)
* @param {Function} callback [description]
*/ */
module.exports = (opts, callback) => {
module.exports = function (opts, callback) {
defaults(opts, { defaults(opts, {
host: process.env.API_HOST, host: process.env.API_HOST,
headers: {}, headers: {},
@ -40,26 +42,29 @@ module.exports = function (opts, callback) {
opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; opts.headers['Content-Type'] = 'application/x-www-form-urlencoded';
} }
var apiRequest = function (opts) { const apiRequest = options => {
if (opts.host !== '') { if (options.host !== '') {
if ('withCredentials' in new XMLHttpRequest()) { if ('withCredentials' in new XMLHttpRequest()) {
opts.useXDR = false; options.useXDR = false;
} else { } else {
// For IE < 10, we must use XDR for cross-domain requests. XDR does not support // For IE < 10, we must use XDR for cross-domain requests. XDR does not support
// custom headers. // custom headers.
opts.useXDR = true; options.useXDR = true;
delete opts.headers; delete options.headers;
if (opts.authentication) { if (options.authentication) {
var authenticationParams = ['x-token=' + opts.authentication]; const authenticationParams = [`x-token=${options.authentication}`];
var parts = opts.uri.split('?'); const parts = options.uri.split('?');
var qs = (parts[1] || '').split('&').concat(authenticationParams).join('&'); const qs = (parts[1] || '')
opts.uri = parts[0] + '?' + qs; .split('&')
.concat(authenticationParams)
.join('&');
options.uri = `${parts[0]}?${qs}`;
} }
} }
} }
xhr(opts, function (err, res, body) { xhr(options, (err, res, body) => {
if (err) log.error(err); if (err) log.error(err);
if (opts.responseType === 'json' && typeof body === 'string') { if (options.responseType === 'json' && typeof body === 'string') {
// IE doesn't parse responses as JSON without the json attribute, // IE doesn't parse responses as JSON without the json attribute,
// even with responseType: 'json'. // even with responseType: 'json'.
// See https://github.com/Raynos/xhr/issues/123 // See https://github.com/Raynos/xhr/issues/123
@ -73,25 +78,25 @@ module.exports = function (opts, callback) {
// [{success: true, redirect: "/location/to/redirect"}] // [{success: true, redirect: "/location/to/redirect"}]
try { try {
if ('redirect' in body[0]) window.location = body[0].redirect; if ('redirect' in body[0]) window.location = body[0].redirect;
} catch (err) { } catch (e) {
// do nothing // do nothing
} }
callback(err, body, res); callback(err, body, res);
}); });
}.bind(this); };
if (typeof jar.get('scratchlanguage') !== 'undefined') { if (typeof jar.get('scratchlanguage') !== 'undefined') {
opts.headers['Accept-Language'] = jar.get('scratchlanguage') + ', en;q=0.8'; opts.headers['Accept-Language'] = `${jar.get('scratchlanguage')}, en;q=0.8`;
} }
if (opts.authentication) { if (opts.authentication) {
opts.headers['X-Token'] = opts.authentication; opts.headers['X-Token'] = opts.authentication;
} }
if (opts.useCsrf) { if (opts.useCsrf) {
jar.use('scratchcsrftoken', '/csrf_token/', function (err, csrftoken) { jar.use('scratchcsrftoken', '/csrf_token/', (err, csrftoken) => {
if (err) return log.error('Error while retrieving CSRF token', err); if (err) return log.error('Error while retrieving CSRF token', err);
opts.headers['X-CSRFToken'] = csrftoken; opts.headers['X-CSRFToken'] = csrftoken;
apiRequest(opts); apiRequest(opts);
}.bind(this)); });
} else { } else {
apiRequest(opts); apiRequest(opts);
} }

View file

@ -1,22 +1,28 @@
module.exports = {}; module.exports = {};
var countries = module.exports.data = require('iso-3166-2').data; const countries = module.exports.data = require('iso-3166-2').data;
module.exports.countryOptions = Object.keys(countries).map(function (code) { module.exports.countryOptions = Object.keys(countries).map(code => ({
return {value: code.toLowerCase(), label: countries[code].name}; value: code.toLowerCase(),
}).sort(function (a, b) { label: countries[code].name
return a.label < b.label ? -1 : 1; }))
}); .sort((a, b) => {
if (a.label < b.label) {
module.exports.subdivisionOptions = return -1;
Object.keys(countries).reduce(function (subByCountry, code) { }
subByCountry[code.toLowerCase()] = Object.keys(countries[code].sub).map(function (subCode) { return 1;
return {
value: subCode.toLowerCase(),
label: countries[code].sub[subCode].name,
type: countries[code].sub[subCode].type
};
}).sort(function (a, b) {
return a.label < b.label ? -1 : 1;
}); });
module.exports.subdivisionOptions = Object.keys(countries).reduce((subByCountry, code) => {
subByCountry[code.toLowerCase()] = Object.keys(countries[code].sub).map(subCode => ({
value: subCode.toLowerCase(),
label: countries[code].sub[subCode].name,
type: countries[code].sub[subCode].type
}))
.sort((a, b) => {
if (a.label < b.label) {
return -1;
}
return 1;
});
return subByCountry; return subByCountry;
}, {}); }, {});

View file

@ -1,34 +0,0 @@
/**
* Takes an ISO time and returns a string representing how long ago the date
* represents. For example, "2015-01-01T00:00:00" becomes "1 minute ago".
*
* Based on "JavaScript Pretty Date"
* Copyright (c) 2011 John Resig (ejohn.org)
* Licensed under the MIT and GPL licenses.
*/
var Format = {
date: function (stamp) {
stamp = (stamp || '').replace(/-/g,'/').replace(/[TZ]/g,' ');
var date = new Date(stamp);
var diff = (((new Date()).getTime() - date.getTime()) / 1000);
var day_diff = Math.floor(diff / 86400);
if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
return 'A while ago';
}
return day_diff == 0 && (
diff < 60 && 'Just now' ||
diff < 120 && '1 minute ago' ||
diff < 3600 && Math.floor( diff / 60 ) + ' minutes ago' ||
diff < 7200 && '1 hour ago' ||
diff < 86400 && Math.floor( diff / 3600 ) + ' hours ago') ||
day_diff == 1 && 'Yesterday' ||
day_diff < 7 && day_diff + ' days ago' ||
day_diff < 31 && Math.ceil( day_diff / 7 ) + ' weeks ago';
}
};
module.exports = Format;

View file

@ -1,9 +1,10 @@
/* This file contains breakpoints from _frameless.scss, to be used in MediaQuery elements. /* This file contains breakpoints from _frameless.scss, to be used in MediaQuery elements.
* All units are in px, as per _frameless.scss and the MediaQuery default arguments. * All units are in px, as per _frameless.scss and the MediaQuery default arguments.
*/ */
const frameless = {
module.exports = {
desktop: 942, desktop: 942,
tablet: 640, tablet: 640,
mobile: 480 mobile: 480
}; };
module.exports = frameless;

View file

@ -1,20 +1,20 @@
var requireAll = require('./require-all'); const requireAll = require('./require-all');
var ReactIntl = require('react-intl'); const ReactIntl = require('react-intl');
var allLocaleData = requireAll(require.context('react-intl/locale-data', true, /^\.\/.*\.js$/)); const allLocaleData = requireAll(require.context('react-intl/locale-data', true, /^\.\/.*\.js$/));
var customLocaleData = require('../../custom-locales.json'); const customLocaleData = require('../../custom-locales.json');
/** /**
* Add all locales * Add all locales
*/ */
for (var locale in allLocaleData) { for (const locale in allLocaleData) {
ReactIntl.addLocaleData(allLocaleData[locale]); ReactIntl.addLocaleData(allLocaleData[locale]);
} }
/** /**
* Add custom locales to react-intl if it doesn't have them. * Add custom locales to react-intl if it doesn't have them.
*/ */
for (var customLocale in customLocaleData) { for (const customLocale in customLocaleData) {
ReactIntl.addLocaleData(customLocaleData[customLocale]); ReactIntl.addLocaleData(customLocaleData[customLocale]);
} }

View file

@ -1,7 +1,7 @@
var cookie = require('cookie'); const cookie = require('cookie');
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var xhr = require('xhr'); const xhr = require('xhr');
var pako = require('pako'); const pako = require('pako');
/** /**
* Module that handles coookie interactions. * Module that handles coookie interactions.
@ -11,16 +11,16 @@ var pako = require('pako');
* set(name, value) synchronously sets the cookie * set(name, value) synchronously sets the cookie
* use(name, uri, callback) can by sync or async, gets cookie from the uri if not there. * use(name, uri, callback) can by sync or async, gets cookie from the uri if not there.
*/ */
var Jar = { const Jar = {
unsign: function (value, callback) { unsign: (value, callback) => {
// Return the usable content portion of a signed, compressed cookie generated by // Return the usable content portion of a signed, compressed cookie generated by
// Django's signing module // Django's signing module
// https://github.com/django/django/blob/stable/1.8.x/django/core/signing.py // https://github.com/django/django/blob/stable/1.8.x/django/core/signing.py
if (typeof value === 'undefined') return callback(null, value); if (typeof value === 'undefined') return callback(null, value);
try { try {
var b64Data = value.split(':')[0]; let b64Data = value.split(':')[0];
var decompress = false; let decompress = false;
if (b64Data[0] === '.') { if (b64Data[0] === '.') {
decompress = true; decompress = true;
b64Data = b64Data.substring(1); b64Data = b64Data.substring(1);
@ -29,24 +29,29 @@ var Jar = {
// Django makes its base64 strings url safe by replacing + and / with - and _ respectively // Django makes its base64 strings url safe by replacing + and / with - and _ respectively
// using base64.urlsafe_b64encode // using base64.urlsafe_b64encode
// https://docs.python.org/2/library/base64.html#base64.b64encode // https://docs.python.org/2/library/base64.html#base64.b64encode
b64Data = b64Data.replace(/[-_]/g, function (c) {return {'-':'+', '_':'/'}[c]; }); b64Data = b64Data.replace(
var strData = atob(b64Data); /[-_]/g,
c => ({
'-': '+',
'_': '/'
}[c])
);
let strData = atob(b64Data);
if (decompress) { if (decompress) {
var charData = strData.split('').map(function (c) { return c.charCodeAt(0); }); const charData = strData.split('').map(c => (c.charCodeAt(0)));
var binData = new Uint8Array(charData); const binData = new Uint8Array(charData);
var data = pako.inflate(binData); const data = pako.inflate(binData);
strData = String.fromCharCode.apply(null, new Uint16Array(data)); strData = String.fromCharCode.apply(null, new Uint16Array(data));
} }
return callback(null, strData); return callback(null, strData);
} catch (e) { } catch (e) {
return callback(e); return callback(e);
} }
}, },
get: function (name, callback) { get: (name, callback) => {
// Get cookie by name // Get cookie by name
var obj = cookie.parse(document.cookie) || {}; const obj = cookie.parse(document.cookie) || {};
// Handle optional callback // Handle optional callback
if (typeof callback === 'function') { if (typeof callback === 'function') {
@ -56,44 +61,44 @@ var Jar = {
return obj[name]; return obj[name];
}, },
use: function (name, uri, callback) { use: (name, uri, callback) => {
// Attempt to get cookie // Attempt to get cookie
Jar.get(name, function (err, obj) { Jar.get(name, (err, obj) => {
if (typeof obj !== 'undefined') return callback(null, obj); if (typeof obj !== 'undefined') return callback(null, obj);
// Make XHR request to cookie setter uri // Make XHR request to cookie setter uri
xhr({ xhr({
uri: uri uri: uri
}, function (err) { }, e => {
if (err) return callback(err); if (e) return callback(e);
Jar.get(name, callback); Jar.get(name, callback);
}); });
}); });
}, },
set: function (name, value, opts) { set: (name, value, opts) => {
opts = opts || {}; opts = opts || {};
defaults(opts, { defaults(opts, {
expires: new Date(new Date().setYear(new Date().getFullYear() + 1)) expires: new Date(new Date().setYear(new Date().getFullYear() + 1))
}); });
opts.path = '/'; opts.path = '/';
var obj = cookie.serialize(name, value, opts); const obj = cookie.serialize(name, value, opts);
document.cookie = obj; document.cookie = obj;
}, },
getUnsignedValue: function (cookieName, signedValue, callback) { getUnsignedValue: (cookieName, signedValue, callback) => {
// Get a value from a signed object // Get a value from a signed object
Jar.get(cookieName, function (err, value) { Jar.get(cookieName, (err, value) => {
if (err) return callback(err); if (err) return callback(err);
if (typeof value === 'undefined') return callback(null, value); if (typeof value === 'undefined') return callback(null, value);
Jar.unsign(value, function (err, contents) { Jar.unsign(value, (e, contents) => {
if (err) return callback(err); if (e) return callback(e);
try { try {
var data = JSON.parse(contents); const data = JSON.parse(contents);
} catch (err) { return callback(null, data[signedValue]);
return callback(err); } catch (error) {
return callback(error);
} }
return callback(null, data[signedValue]);
}); });
}); });
} }

View file

@ -1,4 +1,4 @@
var minilog = require('minilog'); const minilog = require('minilog');
minilog.enable(); minilog.enable();
module.exports = minilog('www'); module.exports = minilog('www');

View file

@ -1,21 +1,27 @@
var redux = require('redux'); const redux = require('redux');
var thunk = require('redux-thunk').default; const thunk = require('redux-thunk').default;
// JSX syntax transforms to React.createElement // JSX syntax transforms to React.createElement
var React = require('react'); // eslint-disable-line const React = require('react'); // eslint-disable-line
var ReactDOM = require('react-dom'); const ReactDOM = require('react-dom');
var StoreProvider = require('react-redux').Provider; const StoreProvider = require('react-redux').Provider;
var IntlProvider = require('./intl.jsx').IntlProvider; const IntlProvider = require('./intl.jsx').IntlProvider;
var permissionsActions = require('../redux/permissions.js'); const permissionsActions = require('../redux/permissions.js');
var sessionActions = require('../redux/session.js'); const sessionActions = require('../redux/session.js');
var reducer = require('../redux/reducer.js'); const reducer = require('../redux/reducer.js');
require('../main.scss'); require('../main.scss');
var render = function (jsx, element, reducers) { /**
* Function to render views into a full page
* @param {object} jsx jsx component of the view
* @param {object} element html element to render to on the template
* @param {array} reducers list of view-specific reducers
*/
const render = (jsx, element, reducers) => {
// Get locale and messages from global namespace (see "init.js") // Get locale and messages from global namespace (see "init.js")
var locale = window._locale || 'en'; let locale = window._locale || 'en';
var messages = {}; let messages = {};
if (typeof window._messages !== 'undefined') { if (typeof window._messages !== 'undefined') {
if (typeof window._messages[locale] === 'undefined') { if (typeof window._messages[locale] === 'undefined') {
// Fall back on the split // Fall back on the split
@ -28,8 +34,8 @@ var render = function (jsx, element, reducers) {
messages = window._messages[locale]; messages = window._messages[locale];
} }
var allReducers = reducer(reducers); const allReducers = reducer(reducers);
var store = redux.createStore( const store = redux.createStore(
allReducers, allReducers,
redux.applyMiddleware(thunk) redux.applyMiddleware(thunk)
); );
@ -37,7 +43,10 @@ var render = function (jsx, element, reducers) {
// Render view component // Render view component
ReactDOM.render( ReactDOM.render(
<StoreProvider store={store}> <StoreProvider store={store}>
<IntlProvider locale={locale} messages={messages}> <IntlProvider
locale={locale}
messages={messages}
>
{jsx} {jsx}
</IntlProvider> </IntlProvider>
</StoreProvider>, </StoreProvider>,

View file

@ -1,5 +1,5 @@
var requireAll = function (requireContext) { const requireAll = requireContext => (
return requireContext.keys().map(requireContext); requireContext.keys().map(requireContext)
}; );
module.exports = requireAll; module.exports = requireAll;

View file

@ -1,21 +1,19 @@
/* /*
* Function that shuffles an array using a Fisher-Yates shuffle. * Function that shuffles an array using a Fisher-Yates shuffle.
*/ */
module.exports.shuffle = arr => {
module.exports.shuffle = function (arr) { let i = 0;
var i, j = 0; let j = 0;
var temp = null; let temp = null;
if (arr) { if (arr) {
var tempArray = arr.slice(0); const tempArray = arr.slice(0);
} else { for (i = arr.length - 1; i > 0; i -= 1) {
return arr; j = Math.floor(Math.random() * (i + 1));
temp = tempArray[i];
tempArray[i] = tempArray[j];
tempArray[j] = temp;
}
return tempArray;
} }
return arr;
for (i = arr.length - 1; i > 0; i -= 1) {
j = Math.floor(Math.random() * (i + 1));
temp = tempArray[i];
tempArray[i] = tempArray[j];
tempArray[j] = temp;
}
return tempArray;
}; };

View file

@ -2,21 +2,18 @@
* urlParams({a: 1, b: 2, c: 3}) * urlParams({a: 1, b: 2, c: 3})
* // a=1&b=2&c=3 * // a=1&b=2&c=3
*/ */
module.exports = function urlParams (values) { const params = values => (
return Object Object.keys(values).map(key => {
.keys(values) const value = typeof values[key] === 'undefined' ? '' : values[key];
.map(function (key) { const encodeKeyValuePair = val => (
var value = typeof values[key] === 'undefined' ? '' : values[key]; [key, val].map(encodeURIComponent).join('=')
function encodeKeyValuePair (value) { );
return [key, value] if (Array.isArray(value)) {
.map(encodeURIComponent) return value.map(encodeKeyValuePair).join('&');
.join('='); }
} return encodeKeyValuePair(value);
if (Array.isArray(value)) { })
return value.map(encodeKeyValuePair).join('&'); .join('&')
} else { );
return encodeKeyValuePair(value);
} module.exports = params;
})
.join('&');
};

View file

@ -1,13 +1,13 @@
var keyMirror = require('keymirror'); const keyMirror = require('keymirror');
var api = require('../lib/api'); const api = require('../lib/api');
var Types = keyMirror({ const Types = keyMirror({
SET_DETAILS: null, SET_DETAILS: null,
SET_DETAILS_FETCHING: null, SET_DETAILS_FETCHING: null,
SET_DETAILS_ERROR: null SET_DETAILS_ERROR: null
}); });
module.exports.detailsReducer = function (state, action) { module.exports.detailsReducer = (state, action) => {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = {}; state = {};
} }
@ -23,61 +23,50 @@ module.exports.detailsReducer = function (state, action) {
} }
}; };
module.exports.setDetailsError = function (error) { module.exports.setDetailsError = error => ({
return { type: Types.SET_DETAILS_ERROR,
type: Types.SET_DETAILS_ERROR, error: error
error: error });
};
};
module.exports.setDetails = function (details) { module.exports.setDetails = details => ({
return { type: Types.SET_DETAILS,
type: Types.SET_DETAILS, details: details
details: details });
};
};
module.exports.setDetailsFetching = function () { module.exports.setDetailsFetching = () => ({
return { type: Types.SET_DETAILS_FETCHING,
type: Types.SET_DETAILS_FETCHING, fetching: true
fetching: true });
};
};
module.exports.startGetDetails = function (id) { module.exports.getDetails = id => (dispatch => {
return function (dispatch) { api({
dispatch(module.exports.setDetailsFetching()); uri: `/conference/${id}/details`
dispatch(module.exports.getDetails(id)); }, (err, body) => {
}; if (err) {
}; dispatch(module.exports.setDetailsError(err));
return;
}
module.exports.getDetails = function (id) { if (typeof body !== 'undefined') {
return function (dispatch) { const columns = body.columns;
api({ if (body.rows) {
uri: '/conference/' + id + '/details' const details = body.rows[0];
}, function (err, body) { const detailsObject = details.reduce((prev, cur, index) => {
if (err) { prev[columns[index]] = cur;
dispatch(module.exports.setDetailsError(err)); return prev;
return; }, {});
} dispatch(module.exports.setDetails(detailsObject));
if (typeof body !== 'undefined') {
var columns = body.columns;
if (body.rows) {
var details = body.rows[0];
var detailsObject = details.reduce(function (prev, cur, index) {
prev[columns[index]] = cur;
return prev;
}, {});
dispatch(module.exports.setDetails(detailsObject));
} else {
dispatch(module.exports.setDetailsError('Not Found'));
}
return;
} else { } else {
dispatch(module.exports.setDetailsError('An unexpected error occurred')); dispatch(module.exports.setDetailsError('Not Found'));
return;
} }
}); return;
}; }
}; dispatch(module.exports.setDetailsError('An unexpected error occurred'));
return;
});
});
module.exports.startGetDetails = id => (dispatch => {
dispatch(module.exports.setDetailsFetching());
dispatch(module.exports.getDetails(id));
});

View file

@ -1,13 +1,13 @@
var keyMirror = require('keymirror'); const keyMirror = require('keymirror');
var api = require('../lib/api'); const api = require('../lib/api');
var Types = keyMirror({ const Types = keyMirror({
SET_SCHEDULE: null, SET_SCHEDULE: null,
SET_SCHEDULE_FETCHING: null, SET_SCHEDULE_FETCHING: null,
SET_SCHEDULE_ERROR: null SET_SCHEDULE_ERROR: null
}); });
module.exports.scheduleReducer = function (state, action) { module.exports.scheduleReducer = (state, action) => {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = { state = {
timeSlots: [], timeSlots: [],
@ -26,40 +26,27 @@ module.exports.scheduleReducer = function (state, action) {
} }
}; };
module.exports.setSchedule = function (schedule) { module.exports.setSchedule = schedule => ({
return { type: Types.SET_SCHEDULE,
type: Types.SET_SCHEDULE, schedule: schedule
schedule: schedule });
};
};
module.exports.setScheduleFetching = function () { module.exports.setScheduleFetching = () => ({
return { type: Types.SET_SCHEDULE_FETCHING,
type: Types.SET_SCHEDULE_FETCHING, fetching: true
fetching: true });
};
};
module.exports.setScheduleError = function (error) { module.exports.setScheduleError = error => ({
return { type: Types.SET_SCHEDULE_ERROR,
type: Types.SET_SCHEDULE_ERROR, error: error
error: error });
};
};
module.exports.startGetSchedule = function (day) {
return function (dispatch) {
dispatch(module.exports.setScheduleFetching());
dispatch(module.exports.getDaySchedule(day));
};
};
// group periods of time by start time // group periods of time by start time
module.exports.sortTimeSlots = function (timeSlot1, timeSlot2) { module.exports.sortTimeSlots = (timeSlot1, timeSlot2) => {
var timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a') ? true : false; const timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a');
var timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a') ? true : false; const timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a');
var timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1)); let timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1), 10);
var timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1)); let timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1), 10);
// convert to 24-hour for sorting // convert to 24-hour for sorting
if (timeSlot1Time !== 12 && !timeSlot1Am) { if (timeSlot1Time !== 12 && !timeSlot1Am) {
@ -71,68 +58,67 @@ module.exports.sortTimeSlots = function (timeSlot1, timeSlot2) {
if (timeSlot1Time < timeSlot2Time) { if (timeSlot1Time < timeSlot2Time) {
return -1; return -1;
} else {
return 1;
} }
return 1;
}; };
/** /**
* Gets the schedule for the given day from the api * Gets the schedule for the given day from the api
* @param {String} day Day of the conference (Thursday, Friday or Satrurday) * @param {string} day Day of the conference (Thursday, Friday or Satrurday)
* *
* @return {Object} Schedule for the day, broken into chunks * @return {object} Schedule for the day, broken into chunks
*/ */
module.exports.getDaySchedule = function (day) { module.exports.getDaySchedule = day => (dispatch => {
return function (dispatch) { api({
api({ uri: `/conference/schedule/${day}`
uri: '/conference/schedule/' + day }, (err, body) => {
}, function (err, body) { if (err) {
if (err) { dispatch(module.exports.setScheduleError(err));
dispatch(module.exports.setScheduleError(err)); return;
return; }
}
if (typeof body !== 'undefined') { if (typeof body !== 'undefined') {
var columns = body.columns; const columns = body.columns;
var rows = body.rows || []; const rows = body.rows || [];
// Group events by the time period in which they occur (for presentation) // Group events by the time period in which they occur (for presentation)
var scheduleByTimeSlot = rows.reduce(function (prev, cur) { const scheduleByTimeSlot = rows.reduce((prev, cur) => {
var cleanedRow = {}; const cleanedRow = {};
for (var i = 0; i < columns.length; i++) { for (let i = 0; i < columns.length; i++) {
if (cur[i].length > 0) { if (cur[i].length > 0) {
cleanedRow[columns[i]] = cur[i]; cleanedRow[columns[i]] = cur[i];
}
} }
cleanedRow['uri'] = '/conference/2016/' + cleanedRow.rowid + '/details'; }
var timeSlot = cleanedRow.Chunk + cleanedRow.Start; cleanedRow.uri = `/conference/2016/${cleanedRow.rowid}/details`;
if (typeof prev.timeSlots[timeSlot] === 'undefined') { const timeSlot = cleanedRow.Chunk + cleanedRow.Start;
prev.timeSlots[timeSlot] = [cleanedRow]; if (typeof prev.timeSlots[timeSlot] === 'undefined') {
prev.info.push({ prev.timeSlots[timeSlot] = [cleanedRow];
name: cleanedRow.Chunk, prev.info.push({
time: cleanedRow.Start name: cleanedRow.Chunk,
}); time: cleanedRow.Start
} else { });
prev.timeSlots[timeSlot].push(cleanedRow); } else {
} prev.timeSlots[timeSlot].push(cleanedRow);
return prev; }
}, {timeSlots: [], info: []}); return prev;
}, {timeSlots: [], info: []});
scheduleByTimeSlot.info.sort(module.exports.sortTimeSlots); scheduleByTimeSlot.info.sort(module.exports.sortTimeSlots);
var schedule = scheduleByTimeSlot.info.map(function (timeSlot) { const schedule = scheduleByTimeSlot.info.map(timeSlot => ({
return { info: timeSlot,
info: timeSlot, items: scheduleByTimeSlot.timeSlots[timeSlot.name + timeSlot.time]
items: scheduleByTimeSlot.timeSlots[timeSlot.name + timeSlot.time] }));
}; dispatch(module.exports.setSchedule({
}); timeSlots: schedule,
dispatch(module.exports.setSchedule({ day: day
timeSlots: schedule, }));
day: day return;
})); }
return; dispatch(module.exports.setScheduleError('An unexpected error occurred'));
} else { return;
dispatch(module.exports.setScheduleError('An unexpected error occurred')); });
return; });
}
}); module.exports.startGetSchedule = day => (dispatch => {
}; dispatch(module.exports.setScheduleFetching());
}; dispatch(module.exports.getDaySchedule(day));
});

View file

@ -1,22 +1,22 @@
var keyMirror = require('keymirror'); const keyMirror = require('keymirror');
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var api = require('../lib/api'); const api = require('../lib/api');
var Types = keyMirror({ const Types = keyMirror({
SET_MESSAGE_COUNT: null, SET_MESSAGE_COUNT: null,
SET_MESSAGE_COUNT_ERROR: null, SET_MESSAGE_COUNT_ERROR: null,
SET_STATUS: null SET_STATUS: null
}); });
module.exports.getInitialState = function (){ const getInitialState = () => ({
return {messageCount: 0}; messageCount: 0
}; });
module.exports.messageCountReducer = function (state, action) { module.exports.messageCountReducer = (state, action) => {
// Reducer for handling changes to session state // Reducer for handling changes to session state
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = module.exports.getInitialState(); state = getInitialState();
} }
switch (action.type) { switch (action.type) {
case Types.SET_MESSAGE_COUNT: case Types.SET_MESSAGE_COUNT:
@ -31,40 +31,32 @@ module.exports.messageCountReducer = function (state, action) {
} }
}; };
module.exports.setSessionError = function (error) { module.exports.setSessionError = error => ({
return { type: Types.SET_MESSAGE_COUNT_ERROR,
type: Types.SET_MESSAGE_COUNT_ERROR, error: error
error: error });
};
};
module.exports.setCount = function (count) { module.exports.setCount = count => ({
return { type: Types.SET_MESSAGE_COUNT,
type: Types.SET_MESSAGE_COUNT, count: count
count: count });
};
};
module.exports.setStatus = function (status){ module.exports.setStatus = status => ({
return { type: Types.SET_STATUS,
type: Types.SET_STATUS, status: status
status: status });
};
};
module.exports.getCount = function (username) { module.exports.getCount = username => (dispatch => {
return function (dispatch) { api({
api({ method: 'get',
method: 'get', uri: `/users/${username}/messages/count`
uri: '/users/' + username + '/messages/count' }, (err, body) => {
}, function (err, body) { if (err) {
if (err) { dispatch(module.exports.setCount(0));
dispatch(module.exports.setCount(0)); dispatch(module.exports.setSessionError(err));
dispatch(module.exports.setSessionError(err)); return;
return; }
} const count = parseInt(body.count, 10);
var count = parseInt(body.count, 10); dispatch(module.exports.setCount(count));
dispatch(module.exports.setCount(count)); });
}); });
};
};

View file

@ -1,10 +1,10 @@
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var defaultsDeep = require('lodash.defaultsdeep'); const defaultsDeep = require('lodash.defaultsdeep');
var keyMirror = require('keymirror'); const keyMirror = require('keymirror');
var api = require('../lib/api'); const api = require('../lib/api');
var log = require('../lib/log'); const log = require('../lib/log');
var messageCountActions = require('./message-count.js'); const messageCountActions = require('./message-count.js');
module.exports.Status = keyMirror({ module.exports.Status = keyMirror({
FETCHED: null, FETCHED: null,
@ -17,23 +17,21 @@ module.exports.Status = keyMirror({
DELETE_ERROR: null DELETE_ERROR: null
}); });
module.exports.getInitialState = function () { module.exports.getInitialState = () => ({
return { status: {
status: { admin: module.exports.Status.NOT_FETCHED,
admin: module.exports.Status.NOT_FETCHED, message: module.exports.Status.NOT_FETCHED,
message: module.exports.Status.NOT_FETCHED, clear: module.exports.Status.NOT_FETCHED,
clear: module.exports.Status.NOT_FETCHED, delete: module.exports.Status.NOT_FETCHED
delete: module.exports.Status.NOT_FETCHED },
}, messages: {
messages: { admin: [],
admin: [], social: [],
social: [], invite: {}
invite: {} }
} });
};
};
module.exports.messagesReducer = function (state, action) { module.exports.messagesReducer = (state, action) => {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = module.exports.getInitialState(); state = module.exports.getInitialState();
} }
@ -68,75 +66,61 @@ module.exports.messagesReducer = function (state, action) {
} }
}; };
module.exports.setMessagesError = function (error) { module.exports.setMessagesError = error => ({
return { type: 'ERROR',
type: 'ERROR', error: error
error: error });
};
};
module.exports.setMessages = function (messages) { module.exports.setMessages = messages => ({
return { type: 'SET_MESSAGES',
type: 'SET_MESSAGES', messages: messages
messages: messages });
};
};
module.exports.setMessagesOffset = function (offset) { module.exports.setMessagesOffset = offset => ({
return { type: 'SET_MESSAGES_OFFSET',
type: 'SET_MESSAGES_OFFSET', offset: offset
offset: offset });
};
};
module.exports.setAdminMessages = function (messages) { module.exports.setAdminMessages = messages => ({
return { type: 'SET_ADMIN_MESSAGES',
type: 'SET_ADMIN_MESSAGES', messages: messages
messages: messages });
};
};
module.exports.setScratcherInvite = function (invite) { module.exports.setScratcherInvite = invite => ({
return { type: 'SET_SCRATCHER_INVITE',
type: 'SET_SCRATCHER_INVITE', invite: invite
invite: invite });
};
};
module.exports.setStatus = function (type, status){ module.exports.setStatus = (type, status) => ({
return { type: type,
type: type, status: status
status: status });
};
};
/** /**
* Sends a request to mark one's unread messages count as cleared. * Sends a request to mark one's unread messages count as cleared.
* @return {null} returns nothing * @return {null} returns nothing
*/ */
module.exports.clearMessageCount = function () { module.exports.clearMessageCount = () => (dispatch => {
return function (dispatch) { dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING));
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING)); api({
api({ host: '',
host: '', uri: '/site-api/messages/messages-clear/',
uri: '/site-api/messages/messages-clear/', method: 'POST',
method: 'POST', useCsrf: true
useCsrf: true }, (err, body) => {
}, function (err, body) { if (err) {
if (err) { dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR));
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR)); dispatch(module.exports.setMessagesError(err));
dispatch(module.exports.setMessagesError(err)); return;
return; }
} if (typeof body !== 'undefined' && !body.success) {
if (typeof body !== 'undefined' && !body.success) { dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR));
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR)); dispatch(module.exports.setMessagesError('messages not cleared'));
dispatch(module.exports.setMessagesError('messages not cleared')); return;
return; }
} dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHED));
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHED)); });
}); });
};
};
/** /**
* Marks an admin message as read, dismissing it from the page * Marks an admin message as read, dismissing it from the page
@ -146,8 +130,8 @@ module.exports.clearMessageCount = function () {
* @param {object[]} adminMessages current list of admin messages retrieved * @param {object[]} adminMessages current list of admin messages retrieved
* @return {null} returns nothing * @return {null} returns nothing
*/ */
module.exports.clearAdminMessage = function (messageType, messageId, messageCount, adminMessages) { module.exports.clearAdminMessage = (messageType, messageId, messageCount, adminMessages) => (
return function (dispatch) { dispatch => {
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING)); dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING));
api({ api({
host: '', host: '',
@ -158,7 +142,7 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
alertType: messageType, alertType: messageType,
alertId: messageId alertId: messageId
} }
}, function (err, body) { }, (err, body) => {
if (err) { if (err) {
dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.DELETE_ERROR)); dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.DELETE_ERROR));
dispatch(module.exports.setMessagesError(err)); dispatch(module.exports.setMessagesError(err));
@ -175,9 +159,9 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
dispatch(module.exports.setScratcherInvite({})); dispatch(module.exports.setScratcherInvite({}));
} else { } else {
// find the admin message and remove it // find the admin message and remove it
var toRemove = -1; let toRemove = -1;
for (var i in adminMessages) { for (const i of adminMessages) {
if (adminMessages[i].id === messageId) { if (i.id === messageId) {
toRemove = i; toRemove = i;
break; break;
} }
@ -188,8 +172,8 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
dispatch(messageCountActions.setCount(messageCount - 1)); dispatch(messageCountActions.setCount(messageCount - 1));
dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.FETCHED)); dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.FETCHED));
}); });
}; }
}; );
/** /**
* Gets a user's messages to be displayed on the /messages page * Gets a user's messages to be displayed on the /messages page
@ -201,7 +185,7 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
* @param {string} [opts.filter] type of messages to return * @param {string} [opts.filter] type of messages to return
* @return {null} returns nothing * @return {null} returns nothing
*/ */
module.exports.getMessages = function (username, token, opts) { module.exports.getMessages = (username, token, opts) => {
opts = defaults(opts, { opts = defaults(opts, {
messages: [], messages: [],
offset: 0, offset: 0,
@ -209,17 +193,17 @@ module.exports.getMessages = function (username, token, opts) {
clearCount: true clearCount: true
}); });
var filterArg = ''; let filterArg = '';
if (opts.filter.length > 0) { if (opts.filter.length > 0) {
filterArg = '&filter=' + opts.filter; filterArg = `&filter=${opts.filter}`;
} }
return function (dispatch) { return dispatch => {
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.FETCHING)); dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.FETCHING));
api({ api({
uri: '/users/' + username + '/messages?limit=40&offset=' + opts.offset + filterArg, uri: `/users/${username}/messages?limit=40&offset=${opts.offset}${filterArg}`,
authentication: token authentication: token
}, function (err, body) { }, (err, body) => {
if (err) { if (err) {
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.MESSAGES_ERROR)); dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.MESSAGES_ERROR));
dispatch(module.exports.setMessagesError(err)); dispatch(module.exports.setMessagesError(err));
@ -246,30 +230,28 @@ module.exports.getMessages = function (username, token, opts) {
* @param {string} token the user's unique token for auth * @param {string} token the user's unique token for auth
* @return {null} returns nothing * @return {null} returns nothing
*/ */
module.exports.getAdminMessages = function (username, token) { module.exports.getAdminMessages = (username, token) => (dispatch => {
return function (dispatch) { dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHING));
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHING)); api({
api({ uri: `/users/${username}/messages/admin`,
uri: '/users/' + username + '/messages/admin', authentication: token
authentication: token }, (err, body) => {
}, function (err, body) { if (err) {
if (err) { dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR));
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR)); dispatch(module.exports.setMessagesError(err));
dispatch(module.exports.setMessagesError(err)); dispatch(module.exports.setAdminMessages([]));
dispatch(module.exports.setAdminMessages([])); return;
return; }
} if (typeof body === 'undefined') {
if (typeof body === 'undefined') { dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR));
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR)); dispatch(module.exports.setMessagesError('No session content'));
dispatch(module.exports.setMessagesError('No session content')); dispatch(module.exports.setAdminMessages([]));
dispatch(module.exports.setAdminMessages([])); return;
return; }
} dispatch(module.exports.setAdminMessages(body));
dispatch(module.exports.setAdminMessages(body)); dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHED));
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHED)); });
}); });
};
};
/** /**
* Gets the invitation to become a Scratcher for a user, if one exists * Gets the invitation to become a Scratcher for a user, if one exists
@ -277,20 +259,18 @@ module.exports.getAdminMessages = function (username, token) {
* @param {string} token the user's unique token for auth * @param {string} token the user's unique token for auth
* @return {null} returns nothing * @return {null} returns nothing
*/ */
module.exports.getScratcherInvite = function (username, token) { module.exports.getScratcherInvite = (username, token) => (dispatch => {
return function (dispatch) { api({
api({ uri: `/users/${username}/invites`,
uri: '/users/' + username + '/invites', authentication: token
authentication: token }, (err, body) => {
}, function (err, body) { if (err) {
if (err) { dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.INVITE_ERROR));
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.INVITE_ERROR)); dispatch(module.exports.setMessagesError(err));
dispatch(module.exports.setMessagesError(err)); dispatch(module.exports.setScratcherInvite({}));
dispatch(module.exports.setScratcherInvite({})); return;
return; }
} if (typeof body === 'undefined') return dispatch(module.exports.setMessagesError('No session content'));
if (typeof body === 'undefined') return dispatch(module.exports.setMessagesError('No session content')); dispatch(module.exports.setScratcherInvite(body));
dispatch(module.exports.setScratcherInvite(body)); });
}); });
};
};

View file

@ -1,11 +1,11 @@
var keyMirror = require('keymirror'); const keyMirror = require('keymirror');
var Types = keyMirror({ const Types = keyMirror({
SET_SEARCH_TERM: null SET_SEARCH_TERM: null
}); });
module.exports.navigationReducer = function (state, action) { module.exports.navigationReducer = (state, action) => {
if(typeof state === 'undefined') { if (typeof state === 'undefined') {
state = ''; state = '';
} }
switch (action.type) { switch (action.type) {
@ -16,9 +16,7 @@ module.exports.navigationReducer = function (state, action) {
} }
}; };
module.exports.setSearchTerm = function (searchTerm) { module.exports.setSearchTerm = searchTerm => ({
return { type: Types.SET_SEARCH_TERM,
type: Types.SET_SEARCH_TERM, searchTerm: searchTerm
searchTerm: searchTerm });
};
};

View file

@ -1,14 +1,14 @@
var keyMirror = require('keymirror'); const keyMirror = require('keymirror');
var jar = require('../lib/jar.js'); const jar = require('../lib/jar.js');
var Types = keyMirror({ const Types = keyMirror({
SET_PERMISSIONS: null, SET_PERMISSIONS: null,
SET_PERMISSIONS_ERROR: null SET_PERMISSIONS_ERROR: null
}); });
module.exports.permissionsReducer = function (state, action) { module.exports.permissionsReducer = (state, action) => {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = ''; state = {};
} }
switch (action.type) { switch (action.type) {
case Types.SET_PERMISSIONS: case Types.SET_PERMISSIONS:
@ -20,43 +20,37 @@ module.exports.permissionsReducer = function (state, action) {
} }
}; };
module.exports.storePermissions = function (permissions) { module.exports.storePermissions = permissions => {
permissions = permissions || {}; permissions = permissions || {};
return function (dispatch) { return dispatch => {
jar.set('permissions', permissions, { jar.set('permissions', permissions, {
encode: function (value) { encode: value => (
return encodeURIComponent(JSON.stringify(value)); encodeURIComponent(JSON.stringify(value))
} )
}); });
return dispatch(module.exports.setPermissions(permissions)); return dispatch(module.exports.setPermissions(permissions));
}; };
}; };
module.exports.getPermissions = function () { module.exports.getPermissions = () => (dispatch => {
return function (dispatch) { jar.get('permissions', (err, value) => {
jar.get('permissions', function (err, value) { if (err) return dispatch(module.exports.setPermissionsError(err));
if (err) return dispatch(module.exports.setPermissionsError(err));
try { try {
value = JSON.parse(decodeURIComponent(value)) || {}; value = JSON.parse(decodeURIComponent(value)) || {};
} catch (e) { } catch (e) {
value = {}; value = {};
} }
return dispatch(module.exports.setPermissions(value)); return dispatch(module.exports.setPermissions(value));
}); });
}; });
};
module.exports.setPermissions = function (permissions) { module.exports.setPermissions = permissions => ({
return { type: Types.SET_PERMISSIONS,
type: Types.SET_PERMISSIONS, permissions: permissions
permissions: permissions });
};
};
module.exports.setPermissionsError = function (error) { module.exports.setPermissionsError = error => ({
return { type: Types.SET_PERMISSIONS_ERROR,
type: Types.SET_PERMISSIONS_ERROR, error: error
error: error });
};
};

View file

@ -1,9 +1,9 @@
var combineReducers = require('redux').combineReducers; const combineReducers = require('redux').combineReducers;
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var messageCountReducer = require('./message-count.js').messageCountReducer; const messageCountReducer = require('./message-count.js').messageCountReducer;
var permissionsReducer = require('./permissions.js').permissionsReducer; const permissionsReducer = require('./permissions.js').permissionsReducer;
var sessionReducer = require('./session.js').sessionReducer; const sessionReducer = require('./session.js').sessionReducer;
/** /**
* Returns a combined reducer to be used for a page in `render.jsx`. * Returns a combined reducer to be used for a page in `render.jsx`.
@ -11,11 +11,11 @@ var sessionReducer = require('./session.js').sessionReducer;
* - and any reducers specific to the page should be passed into * - and any reducers specific to the page should be passed into
* `render()` as an object (which will then be passed to the function * `render()` as an object (which will then be passed to the function
* below). * below).
* @param {Object} opts key/value where the key is the name of the * @param {object} opts key/value where the key is the name of the
* redux state, value is the reducer function. * redux state, value is the reducer function.
* @return {Object} combined reducer to be used in the redux store * @return {object} combined reducer to be used in the redux store
*/ */
module.exports = function (opts) { module.exports = opts => {
opts = opts || {}; opts = opts || {};
return combineReducers(defaults(opts, { return combineReducers(defaults(opts, {
session: sessionReducer, session: sessionReducer,

Some files were not shown because too many files have changed in this diff Show more