Revert "[Develop] Upgrade to ES6"

This commit is contained in:
Ray Schamp 2018-01-30 09:54:45 -05:00 committed by GitHub
parent a144bab0e6
commit 46511e1253
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
185 changed files with 11594 additions and 13650 deletions
.babelrc.eslintrc.eslintrc.js
.tx
Makefile
bin
dev-server
package.json
src
.eslintrc.js
components
init.jsl10n.json
lib
redux

View file

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

29
.eslintrc Normal file
View file

@ -0,0 +1,29 @@
{
"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"
}

View file

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

View file

@ -133,9 +133,3 @@ 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

@ -55,7 +55,7 @@ test:
@make tap @make tap
lint: lint:
$(ESLINT) . --ext .js,.jsx $(ESLINT) . --ext .js,.jsx,.json
$(SASSLINT) ./src/*.scss $(SASSLINT) ./src/*.scss
$(SASSLINT) ./src/**/*.scss $(SASSLINT) ./src/**/*.scss

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 routeJson = require('../src/routes.json'); var route_json = 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 = routeJson.map(function (route) { var routes = route_json.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 (e, resp) { fastly.cloneVersion(response.number, function (err, response) {
if (e) return cb('Failed to clone latest version: ' + e); if (err) return cb('Failed to clone latest version: ' + err);
cb(null, resp.number); cb(null, response.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,19 +171,20 @@ 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 (e, resp) { fastly.activateVersion(results.version, function (err, response) {
if (err) throw new Error(e); if (err) throw new Error(err);
process.stdout.write('Successfully configured and activated version ' + resp.number + '\n'); process.stdout.write('Successfully configured and activated version ' + response.number + '\n');
if (process.env.FASTLY_PURGE_ALL) { if (process.env.FASTLY_PURGE_ALL) {
fastly.purgeAll(FASTLY_SERVICE_ID, function (error) { fastly.purgeAll(FASTLY_SERVICE_ID, function (err) {
if (error) throw new Error(error); if (err) throw new Error(err);
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 p = route.routeAlias || route.pattern; var path = route.routeAlias || route.pattern;
if (paths.indexOf(p) === -1) { if (paths.indexOf(path) === -1) {
paths.push(p); paths.push(path);
} }
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 (servId, version) { fastly.getFastlyAPIPrefix = function (serviceId, version) {
return '/service/' + encodeURIComponent(servId) + '/version/' + version; return '/service/' + encodeURIComponent(serviceId) + '/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 (lateVersion, version) { var latestVersion = versions.reduce(function (latestVersion, version) {
if (!lateVersion) return version; if (!latestVersion) return version;
if (version.number > lateVersion.number) return version; if (version.number > latestVersion.number) return version;
return lateVersion; return latestVersion;
}); });
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 (e, resp) { this.request('POST', postUrl, condition, function (err, response) {
if (e) { if (err) {
return cb('Failed while inserting condition "' + condition.statement + '": ' + e); return cb('Failed while inserting condition \"' + condition.statement + '\": ' + err);
} }
return cb(null, resp); return cb(null, response);
}); });
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 (e, resp) { this.request('POST', postUrl, header, function (err, response) {
if (e) { if (err) {
return cb('Failed to insert header: ' + e); return cb('Failed to insert header: ' + err);
} }
return cb(null, resp); return cb(null, response);
}); });
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 (e, resp) { this.request('POST', postUrl, responseObj, function (err, response) {
if (e) { if (err) {
return cb('Failed to insert response object: ' + e); return cb('Failed to insert response object: ' + err);
} }
return cb(null, resp); return cb(null, response);
}); });
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 (e, resp) { this.request('POST', postUrl, content, function (err, response) {
if (e) { if (err) {
return cb('Failed while adding custom vcl "' + name + '": ' + e); return cb('Failed while adding custom vcl \"' + name + '\": ' + err);
} }
return cb(null, resp); return cb(null, response);
}); });
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,12 +1,10 @@
/* /**
* Constructor * Constructor
*/ */
const Handler = function (route) { function Handler (route) {
// Handle redirects // Handle redirects
if (route.redirect) { if (route.redirect) {
return (req, res) => { return (req, res) => { res.redirect(route.redirect); };
res.redirect(route.redirect);
};
} }
var url = '/' + route.name + '.html'; var url = '/' + route.name + '.html';
@ -14,9 +12,9 @@ const Handler = function (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

@ -33,27 +33,24 @@
"devDependencies": { "devDependencies": {
"async": "1.5.2", "async": "1.5.2",
"autoprefixer": "6.3.6", "autoprefixer": "6.3.6",
"babel-cli": "6.26.0", "babel-core": "6.10.4",
"babel-core": "6.23.1", "babel-eslint": "5.0.4",
"babel-eslint": "8.0.2", "babel-loader": "6.2.4",
"babel-loader": "7.1.0", "babel-preset-es2015": "6.9.0",
"babel-preset-es2015": "6.22.0", "babel-preset-react": "6.11.1",
"babel-preset-react": "6.22.0",
"cheerio": "1.0.0-rc.2", "cheerio": "1.0.0-rc.2",
"classnames": "2.2.5", "classnames": "2.1.3",
"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": "4.7.1", "eslint": "1.3.1",
"eslint-config-scratch": "5.0.0",
"eslint-plugin-json": "1.2.0", "eslint-plugin-json": "1.2.0",
"eslint-plugin-react": "7.4.0", "eslint-plugin-react": "3.3.1",
"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.19.5", "formsy-react": "0.18.0",
"formsy-react-components": "0.11.1", "formsy-react-components": "0.7.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",
@ -62,7 +59,6 @@
"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",
@ -74,22 +70,21 @@
"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": "2.0.10", "postcss-loader": "0.8.2",
"prop-types": "15.6.0",
"raven-js": "3.0.4", "raven-js": "3.0.4",
"react": "15.5.4", "react": "15.1.0",
"react-dom": "15.5.4", "react-dom": "15.0.1",
"react-intl": "2.1.2", "react-intl": "2.1.2",
"react-modal": "3.1.11", "react-modal": "1.5.2",
"react-onclickoutside": "6.7.1", "react-onclickoutside": "4.1.1",
"react-redux": "4.4.5", "react-redux": "4.4.5",
"react-responsive": "3.0.0", "react-responsive": "1.1.4",
"react-slick": "0.12.2", "react-slick": "0.12.2",
"react-telephone-input": "3.8.6", "react-telephone-input": "3.4.5",
"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": "6.0.6", "sass-loader": "2.0.1",
"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",
@ -97,8 +92,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": "2.7.0", "webpack": "1.12.14",
"webpack-dev-middleware": "2.0.4", "webpack-dev-middleware": "1.2.0",
"xhr": "2.2.0" "xhr": "2.2.0"
}, },
"nyc": { "nyc": {

View file

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

View file

@ -1,35 +1,33 @@
const bindAll = require('lodash.bindall'); var classNames = require('classnames');
const classNames = require('classnames'); var React = require('react');
const PropTypes = require('prop-types');
const React = require('react');
require('./accordion.scss'); require('./accordion.scss');
class Accordion extends React.Component { var Accordion = React.createClass({
constructor (props) { type: 'Accordion',
super(props); getDefaultProps: function () {
bindAll(this, [ return {
'handleClick' titleAs: 'div',
]); contentAs: 'div'
this.state = { };
},
getInitialState: function () {
return {
isOpen: false isOpen: false
}; };
} },
handleClick (e) { toggleContent: function () {
e.preventDefault();
this.setState({isOpen: !this.state.isOpen}); this.setState({isOpen: !this.state.isOpen});
} },
render () { render: function () {
const classes = classNames({ var 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 <this.props.titleAs className="title"
className="title" onClick={this.toggleContent}>
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}>
@ -38,16 +36,6 @@ class Accordion extends React.Component {
</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,39 +1,37 @@
const bindAll = require('lodash.bindall'); var React = require('react');
const connect = require('react-redux').connect; var connect = require('react-redux').connect;
const PropTypes = require('prop-types');
const React = require('react');
const Button = require('../forms/button.jsx'); var Button = require('../forms/button.jsx');
require('./adminpanel.scss'); require('./adminpanel.scss');
class AdminPanel extends React.Component { var AdminPanel = React.createClass({
constructor (props) { type: 'AdminPanel',
super(props); getInitialState: function () {
bindAll(this, [ return {
'handleToggleVisibility'
]);
this.state = {
showPanel: false showPanel: false
}; };
} },
handleToggleVisibility (e) { handleToggleVisibility: function (e) {
e.preventDefault(); e.preventDefault();
this.setState({showPanel: !this.state.showPanel}); this.setState({showPanel: !this.state.showPanel});
} },
render () { render: function () {
if (!this.props.isAdmin) return false; // make sure user is present before checking if they're an admin. Don't show anything if user not an admin.
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 <div id="admin-panel" className="visible">
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">
@ -46,15 +44,8 @@ class AdminPanel extends React.Component {
<dd> <dd>
<ul className="cache-list"> <ul className="cache-list">
<li> <li>
<form <form method="post" action="/scratch_admin/page/clear-anon-cache/">
action="/scratch_admin/page/clear-anon-cache/" <input type="hidden" name="path" value="/" />
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">
@ -62,39 +53,34 @@ class AdminPanel extends React.Component {
</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>
);
} }
}
AdminPanel.propTypes = {
children: PropTypes.node,
isAdmin: PropTypes.bool
};
const mapStateToProps = state => ({
isAdmin: state.permissions.admin
}); });
const ConnectedAdminPanel = connect(mapStateToProps)(AdminPanel); var mapStateToProps = function (state) {
return {
session: state.session
};
};
var ConnectedAdminPanel = connect(mapStateToProps)(AdminPanel);
module.exports = ConnectedAdminPanel; module.exports = ConnectedAdminPanel;

View file

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

View file

@ -1,38 +1,40 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./box.scss'); require('./box.scss');
const Box = props => ( var Box = React.createClass({
<div className={classNames('box', props.className)}> type: 'Box',
<div className="box-header"> propTypes: {
<h4>{props.title}</h4> title: React.PropTypes.string.isRequired,
<h5>{props.subtitle}</h5> subtitle: React.PropTypes.string,
<p> moreTitle: React.PropTypes.string,
<a moreHref: React.PropTypes.string,
href={props.moreHref} moreProps: React.PropTypes.object
{...props.moreProps} },
> render: function () {
{props.moreTitle} var classes = classNames(
</a> 'box',
</p> this.props.className
</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">
{props.children} {this.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,18 +1,17 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./card.scss'); require('./card.scss');
const Card = props => ( var Card = React.createClass({
<div className={classNames(['card', props.className])}> displayName: 'Card',
{props.children} render: function () {
</div> return (
); <div className={classNames(['card', this.props.className])}>
{this.props.children}
Card.propTypes = { </div>
children: PropTypes.node, );
className: PropTypes.string }
}; });
module.exports = Card; module.exports = Card;

View file

@ -1,115 +1,95 @@
const classNames = require('classnames'); var classNames = require('classnames');
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react'); var Slider = require('react-slick');
const Slider = require('react-slick');
const Thumbnail = require('../thumbnail/thumbnail.jsx'); var Thumbnail = require('../thumbnail/thumbnail.jsx');
const frameless = require('../../lib/frameless.js'); var 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 => { /**
defaults(props.settings, { * Displays content in horizontal scrolling box. Example usage: splash page rows.
centerMode: false, */
dots: false, var Carousel = React.createClass({
infinite: false, type: 'Carousel',
lazyLoad: true, propTypes: {
slidesToShow: 5, items: React.PropTypes.array
slidesToScroll: 5, },
variableWidth: true, getDefaultProps: function () {
responsive: [ return {
{ items: require('./carousel.json'),
breakpoint: frameless.mobile, showRemixes: false,
settings: { showLoves: false,
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;
const arrows = props.items.length > props.settings.slidesToShow; var classes = classNames(
return ( 'carousel',
<Slider this.props.className
arrows={arrows} );
className={classNames('carousel', props.className)} return (
{... props.settings} <Slider className={classes} arrows={arrows} {... settings}>
> {this.props.items.map(function (item) {
{props.items.map(item => { var href = '';
let href = ''; switch (this.props.type) {
switch (props.type) { case 'gallery':
case 'gallery': href = '/studios/' + item.id + '/';
href = `/studios/${item.id}/`; break;
break; case 'project':
case 'project': href = '/projects/' + item.id + '/';
href = `/projects/${item.id}/`; break;
break; default:
default: href = '/' + item.type + '/' + item.id + '/';
href = `/${item.type}/${item.id}/`; }
}
return ( return (
<Thumbnail <Thumbnail key={[this.key, item.id].join('.')}
creator={item.author.username} showLoves={this.props.showLoves}
href={href} showRemixes={this.props.showRemixes}
key={[props.type, item.id].join('.')} type={this.props.type}
loves={item.stats.loves} href={href}
remixes={item.stats.remixes} title={item.title}
showLoves={props.showLoves} src={item.image}
showRemixes={props.showRemixes} creator={item.author.username}
src={item.image} remixes={item.stats.remixes}
title={item.title} loves={item.stats.loves} />
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,118 +1,97 @@
// 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.
const classNames = require('classnames'); var classNames = require('classnames');
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react'); var Slider = require('react-slick');
const Slider = require('react-slick');
const Thumbnail = require('../thumbnail/thumbnail.jsx'); var Thumbnail = require('../thumbnail/thumbnail.jsx');
const frameless = require('../../lib/frameless.js'); var 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 => { /**
defaults(props.settings, { * Displays content in horizontal scrolling box. Example usage: splash page rows.
centerMode: false, */
dots: false, var LegacyCarousel = React.createClass({
infinite: false, type: 'LegacyCarousel',
lazyLoad: true, propTypes: {
slidesToShow: 5, items: React.PropTypes.array
slidesToScroll: 5, },
variableWidth: true, getDefaultProps: function () {
responsive: [ return {
{ items: require('./carousel.json'),
breakpoint: frameless.mobile, showRemixes: false,
settings: { showLoves: false
};
},
render: function () {
var settings = this.props.settings || {};
defaults(settings, {
centerMode: false,
dots: false,
infinite: false,
lazyLoad: true,
slidesToShow: 5,
slidesToScroll: 5,
variableWidth: true,
responsive: [
{breakpoint: frameless.mobile, settings: {
arrows: true, 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;
const arrows = props.items.length > props.settings.slidesToShow; var classes = classNames(
return ( 'carousel',
<Slider this.props.className
arrows={arrows} );
className={classNames('carousel', props.className)} return (
{... props.settings} <Slider className={classes} arrows={arrows} {... settings}>
> {this.props.items.map(function (item) {
{props.items.map(item => { var href = '';
let href = ''; switch (item.type) {
switch (item.type) { case 'gallery':
case 'gallery': href = '/studios/' + item.id + '/';
href = `/studios/${item.id}/`; break;
break; case 'project':
case 'project': href = '/projects/' + item.id + '/';
href = `/projects/${item.id}/`; break;
break; default:
default: href = '/' + item.type + '/' + item.id + '/';
href = `/${item.type}/${item.id}/`; }
}
return ( return (
<Thumbnail <Thumbnail key={[this.key, item.id].join('.')}
creator={item.creator} showLoves={this.props.showLoves}
href={href} showRemixes={this.props.showRemixes}
key={[props.type, item.id].join('.')} type={item.type}
loves={item.love_count} href={href}
remixes={item.remixers_count} title={item.title}
showLoves={props.showLoves} src={item.thumbnail_url}
showRemixes={props.showRemixes} creator={item.creator}
src={item.thumbnail_url} remixes={item.remixers_count}
title={item.title} loves={item.love_count} />
type={item.type} );
/> }.bind(this))}
); </Slider>
})} );
</Slider> }
); });
};
Carousel.propTypes = { module.exports = LegacyCarousel;
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,33 +1,34 @@
const classNames = require('classnames'); var classNames = require('classnames');
const FormattedRelative = require('react-intl').FormattedRelative; var FormattedRelative = require('react-intl').FormattedRelative;
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const EmojiText = require('../emoji-text/emoji-text.jsx'); var EmojiText = require('../emoji-text/emoji-text.jsx');
require('./comment.scss'); require('./comment.scss');
const CommentText = props => ( var CommentText = React.createClass({
<div className={classNames('comment-text', props.className)}> type: 'CommentText',
<EmojiText propTypes: {
className="mod-comment" comment: React.PropTypes.string.isRequired,
text={props.comment} datetimeCreated: React.PropTypes.string,
/> className: React.PropTypes.string
{typeof props.datetimeCreated === 'undefined' ? [] : [ },
<p render: function () {
className="comment-text-timestamp" var classes = classNames(
key="comment-text-timestamp" 'comment-text',
> this.props.class
<FormattedRelative value={new Date(props.datetimeCreated)} /> );
</p> return (
]} <div className={classes}>
</div> <EmojiText className="mod-comment" text={this.props.comment} />
); {typeof this.props.datetimeCreated !== 'undefined' ? [
<p className="comment-text-timestamp">
CommentText.propTypes = { <FormattedRelative value={new Date(this.props.datetimeCreated)} />
className: PropTypes.string, </p>
comment: PropTypes.string.isRequired, ] : []}
datetimeCreated: PropTypes.string </div>
}; );
}
});
module.exports = CommentText; module.exports = CommentText;

View file

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

View file

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

View file

@ -1,46 +1,40 @@
const bindAll = require('lodash.bindall'); var React = require('react');
const classNames = require('classnames'); var classNames = require('classnames');
const onClickOutside = require('react-onclickoutside').default;
const PropTypes = require('prop-types');
const React = require('react');
require('./dropdown.scss'); require('./dropdown.scss');
class Dropdown extends React.Component { var Dropdown = React.createClass({
constructor (props) { type: 'Dropdown',
super(props); mixins: [
bindAll(this, [ require('react-onclickoutside')
'handleClickOutside' ],
]); propTypes: {
} onRequestClose: React.PropTypes.func,
handleClickOutside () { isOpen: React.PropTypes.bool
},
getDefaultProps: function () {
return {
as: 'div',
isOpen: false
};
},
handleClickOutside: function () {
if (this.props.isOpen) { if (this.props.isOpen) {
this.props.onRequestClose(); this.props.onRequestClose();
} }
} },
render () { render: function () {
var classes = classNames(
'dropdown',
this.props.className,
{open: this.props.isOpen}
);
return ( return (
<this.props.as <this.props.as className={classes}>
className={classNames('dropdown', this.props.className, {
open: this.props.isOpen
})}
>
{this.props.children} {this.props.children}
</this.props.as> </this.props.as>
); );
} }
} });
Dropdown.propTypes = { module.exports = Dropdown;
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,12 +24,8 @@
} }
a { a {
&:link, background-color: transparent;
&:visited, color: $type-white;
&:active {
background-color: transparent;
color: $type-white;
}
} }
input { input {

View file

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

View file

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

View file

@ -1,152 +1,125 @@
const React = require('react'); var React = require('react');
const FlexRow = require('../../../flex-row/flex-row.jsx'); var FlexRow = require('../../../flex-row/flex-row.jsx');
const FooterBox = require('../../container/footer.jsx'); var FooterBox = require('../../container/footer.jsx');
require('../footer.scss'); require('../footer.scss');
const ConferenceFooter = () => ( var ConferenceFooter = React.createClass({
<FooterBox> type: 'ConferenceFooter',
<div className="collaborators"> render: function () {
<h4>Sponsors</h4> return (
<FlexRow as="ul"> <FooterBox>
<li className="odl"> <div className="collaborators">
<a href="https://odl.mit.edu/"> <h4>Sponsors</h4>
<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> <li className="odl">
<a href="//www.twitter.com/scratch"> <a href="https://odl.mit.edu/">
<img <img src="/images/conference/footer/mit-odl.png"
alt="scratch twitter" alt="MIT Office of Digital Learning" />
src="/images/conference/footer/twitter.png"
/>
</a> </a>
</li> </li>
<li> <li className="intel">
<a href="//www.facebook.com/scratchteam"> <a href="http://www.intel.com/content/www/us/en/homepage.html">
<img <img src="/images/conference/footer/intel.png"
alt="scratch facebook" alt="Intel" />
src="/images/conference/footer/facebook.png"
/>
</a> </a>
</li> </li>
<li> <li className="lego">
<a href="http://medium.com/scratchfoundation-blog"> <a href="http://www.legofoundation.com/">
<img <img src="/images/conference/footer/lego-foundation.png"
alt="scratch foundation blog" alt="LEGO Foundation" />
src="/images/conference/footer/medium.png" </a>
/> </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>
</div> <FlexRow className="scratch-links">
</FlexRow> <div className="family">
</FooterBox> <h4>Scratch Family</h4>
); <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,100 +1,84 @@
const injectIntl = require('react-intl').injectIntl; var React = require('react');
const intlShape = require('react-intl').intlShape; var ReactIntl = require('react-intl');
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
const FlexRow = require('../../../flex-row/flex-row.jsx'); var injectIntl = ReactIntl.injectIntl;
const FooterBox = require('../../container/footer.jsx'); var FormattedMessage = ReactIntl.FormattedMessage;
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');
const ConferenceFooter = props => ( var ConferenceFooter = React.createClass({
<FooterBox> type: 'ConferenceFooter',
<FlexRow className="scratch-links"> render: function () {
<div className="family"> return (
<h4><FormattedMessage id="footer.scratchFamily" /></h4> <FooterBox>
<FlexRow> <FlexRow className="scratch-links">
<FlexRow <div className="family">
as="ul" <h4><FormattedMessage id='footer.scratchFamily' /></h4>
className="column" <FlexRow>
> <FlexRow as="ul" className="column">
<li> <li>
<a href="https://scratch.mit.edu">Scratch</a> <a href="https://scratch.mit.edu" target="_blank">Scratch</a>
</li> </li>
<li> <li>
<a href="http://www.scratchjr.org/">ScratchJr</a> <a href="http://www.scratchjr.org/" target="_blank">ScratchJr</a>
</li> </li>
</FlexRow> </FlexRow>
<FlexRow <FlexRow as="ul" className="column">
as="ul" <li>
className="column" <a href="http://www.scratchfoundation.org/" target="_blank">Scratch Foundation</a>
> </li>
<li> <li>
<a href="http://www.scratchfoundation.org/">Scratch Foundation</a> <a href="http://scratched.gse.harvard.edu/" target="_blank">ScratchEd</a>
</li> </li>
<li> </FlexRow>
<a href="http://scratched.gse.harvard.edu/">ScratchEd</a> <FlexRow as="ul" className="column">
</li> <li>
</FlexRow> <a href="http://day.scratch.mit.edu" target="_blank">Scratch Day</a>
<FlexRow </li>
as="ul" </FlexRow>
className="column" </FlexRow>
> <p className="legal">
<li> <FormattedMessage id='general.copyright' />
<a href="http://day.scratch.mit.edu">Scratch Day</a> </p>
</li> </div>
</FlexRow> <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> </FlexRow>
<p className="legal"> <LanguageChooser locale={this.props.intl.locale} />
<FormattedMessage id="general.copyright" /> </FooterBox>
</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,146 +1,84 @@
const FormattedMessage = require('react-intl').FormattedMessage; var React = require('react');
const injectIntl = require('react-intl').injectIntl; var ReactIntl = require('react-intl');
const intlShape = require('react-intl').intlShape;
const React = require('react');
const FlexRow = require('../../../flex-row/flex-row.jsx'); var injectIntl = ReactIntl.injectIntl;
const FooterBox = require('../../container/footer.jsx'); var FormattedMessage = ReactIntl.FormattedMessage;
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');
const ConferenceFooter = props => ( var ConferenceFooter = React.createClass({
<FooterBox> type: 'ConferenceFooter',
<FlexRow className="scratch-links"> render: function () {
<div className="family"> return (
<h4><FormattedMessage id="footer.scratchFamily" /></h4> <FooterBox>
<FlexRow> <FlexRow className="scratch-links">
<FlexRow <div className="family">
as="ul" <h4><FormattedMessage id='footer.scratchFamily' /></h4>
className="column" <FlexRow>
> <FlexRow as="ul" className="column">
<li> <li>
<a <a href="https://scratch.mit.edu" target="_blank">Scratch</a>
href="https://scratch.mit.edu" </li>
rel="noopener noreferrer" <li>
target="_blank" <a href="http://www.scratchjr.org/" target="_blank">ScratchJr</a>
> </li>
Scratch </FlexRow>
</a> <FlexRow as="ul" className="column">
</li> <li>
<li> <a href="http://www.scratchfoundation.org/" target="_blank">Scratch Foundation</a>
<a </li>
href="http://www.scratchjr.org/" <li>
rel="noopener noreferrer" <a href="http://scratched.gse.harvard.edu/" target="_blank">ScratchEd</a>
target="_blank" </li>
> </FlexRow>
ScratchJr <FlexRow as="ul" className="column">
</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 </div>
href="http://www.scratchfoundation.org/" <div className="media">
rel="noopener noreferrer" <div className="contact-us">
target="_blank" <h4>Contact</h4>
> <p>
Scratch Foundation <a href="mailto:conference@scratch.mit.edu" target="_blank">
</a> Email Us
</li> </a>
<li> </p>
<a </div>
href="http://scratched.gse.harvard.edu/" <div className="social">
rel="noopener noreferrer" <FlexRow as="ul">
target="_blank" <li>
> <a href="//www.twitter.com/scratch" target="_blank">
ScratchEd <img src="/images/conference/footer/twitter.png" alt="scratch twitter" />
</a> </a>
</li> </li>
</FlexRow> <li>
<FlexRow <a href="//www.facebook.com/scratchteam" target="_blank">
as="ul" <img src="/images/conference/footer/facebook.png" alt="scratch facebook" />
className="column" </a>
> </li>
<li> <li>
<a <a href="http://medium.com/scratchfoundation-blog" target="_blank">
href="http://day.scratch.mit.edu" <img src="/images/conference/footer/medium.png" alt="scratch foundation blog" />
rel="noopener noreferrer" </a>
target="_blank" </li>
> </FlexRow>
Scratch Day </div>
</a> </div>
</li>
</FlexRow>
</FlexRow> </FlexRow>
<p className="legal"> <LanguageChooser locale={this.props.intl.locale} />
<FormattedMessage id="general.copyright" /> </FooterBox>
</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 @@
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./footer.scss'); require('./footer.scss');
const FooterBox = props => ( var FooterBox = React.createClass({
<div className="inner"> type: 'FooterBox',
{props.children} render: function () {
</div> return (
); <div className="inner">
{this.props.children}
FooterBox.propTypes = { </div>
children: PropTypes.node );
}; }
});
module.exports = FooterBox; module.exports = FooterBox;

View file

@ -1,225 +1,226 @@
const FormattedMessage = require('react-intl').FormattedMessage; var React = require('react');
const injectIntl = require('react-intl').injectIntl; var ReactIntl = require('react-intl');
const intlShape = require('react-intl').intlShape; var FormattedMessage = ReactIntl.FormattedMessage;
const MediaQuery = require('react-responsive').default; var injectIntl = ReactIntl.injectIntl;
const React = require('react');
const FooterBox = require('../container/footer.jsx'); var FooterBox = require('../container/footer.jsx');
const LanguageChooser = require('../../languagechooser/languagechooser.jsx'); var LanguageChooser = require('../../languagechooser/languagechooser.jsx');
const frameless = require('../../../lib/frameless'); var MediaQuery = require('react-responsive');
var frameless = require('../../../lib/frameless');
require('./footer.scss'); require('./footer.scss');
const Footer = props => ( var Footer = React.createClass({
<FooterBox> type: 'Footer',
<MediaQuery maxWidth={frameless.tablet - 1}> render: function () {
<div className="lists"> return (
<dl> <FooterBox>
<dd> <MediaQuery maxWidth={frameless.tablet - 1}>
<a href="/about"> <div className="lists">
<FormattedMessage id="general.aboutScratch" /> <dl>
</a> <dd>
</dd> <a href="/about">
<dd> <FormattedMessage id='general.aboutScratch' />
<a href="/jobs"> </a>
<FormattedMessage id="general.jobs" /> </dd>
</a> <dd>
</dd> <a href="/jobs">
<dd> <FormattedMessage id='general.jobs' />
<a href="/contact-us/"> </a>
<FormattedMessage id="general.contactUs" /> </dd>
</a> <dd>
</dd> <a href="/contact-us/">
</dl> <FormattedMessage id='general.contactUs' />
<dl> </a>
<dd> </dd>
<a href="/terms_of_use"> </dl>
<FormattedMessage id="general.termsOfUse" /> <dl>
</a> <dd>
</dd> <a href="/terms_of_use">
<dd> <FormattedMessage id='general.termsOfUse' />
<a href="/privacy_policy"> </a>
<FormattedMessage id="general.privacyPolicy" /> </dd>
</a> <dd>
</dd> <a href="/privacy_policy">
<dd> <FormattedMessage id='general.privacyPolicy' />
<a href="/community_guidelines"> </a>
<FormattedMessage id="general.guidelines" /> </dd>
</a> <dd>
</dd> <a href="/community_guidelines">
</dl> <FormattedMessage id='general.guidelines' />
</div> </a>
</MediaQuery> </dd>
<MediaQuery minWidth={frameless.tablet}> </dl>
<div className="lists"> </div>
<dl> </MediaQuery>
<dt> <MediaQuery minWidth={frameless.tablet}>
<FormattedMessage id="general.about" /> <div className="lists">
</dt> <dl>
<dd> <dt>
<a href="/about"> <FormattedMessage id='general.about' />
<FormattedMessage id="general.aboutScratch" /> </dt>
</a> <dd>
</dd> <a href="/about">
<dd> <FormattedMessage id='general.aboutScratch' />
<a href="/parents/"> </a>
<FormattedMessage id="general.forParents" /> </dd>
</a> <dd>
</dd> <a href="/parents/">
<dd> <FormattedMessage id='general.forParents' />
<a href="/educators"> </a>
<FormattedMessage id="general.forEducators" /> </dd>
</a> <dd>
</dd> <a href="/educators">
<dd> <FormattedMessage id='general.forEducators' />
<a href="/developers"> </a>
<FormattedMessage id="general.forDevelopers" /> </dd>
</a> <dd>
</dd> <a href="/developers">
<dd> <FormattedMessage id='general.forDevelopers' />
<a href="/info/credits"> </a>
<FormattedMessage id="general.credits" /> </dd>
</a> <dd>
</dd> <a href="/info/credits">
<dd> <FormattedMessage id='general.credits' />
<a href="/jobs"> </a>
<FormattedMessage id="general.jobs" /> </dd>
</a> <dd>
</dd> <a href="/jobs">
<dd> <FormattedMessage id='general.jobs' />
<a href="http://wiki.scratch.mit.edu/wiki/Scratch_Press"> </a>
<FormattedMessage id="general.press" /> </dd>
</a> <dd>
</dd> <a href="http://wiki.scratch.mit.edu/wiki/Scratch_Press">
</dl> <FormattedMessage id='general.press' />
<dl> </a>
<dt> </dd>
<FormattedMessage id="general.community" /> </dl>
</dt> <dl>
<dd> <dt>
<a href="/community_guidelines"> <FormattedMessage id='general.community' />
<FormattedMessage id="general.guidelines" /> </dt>
</a> <dd>
</dd> <a href="/community_guidelines">
<dd> <FormattedMessage id='general.guidelines' />
<a href="/discuss/"> </a>
<FormattedMessage id="footer.discuss" /> </dd>
</a> <dd>
</dd> <a href="/discuss/">
<dd> <FormattedMessage id='footer.discuss' />
<a href="https://wiki.scratch.mit.edu/"> </a>
<FormattedMessage id="general.wiki" /> </dd>
</a> <dd>
</dd> <a href="https://wiki.scratch.mit.edu/">
<dd> <FormattedMessage id='general.wiki' />
<a href="/statistics/"> </a>
<FormattedMessage id="general.statistics" /> </dd>
</a> <dd>
</dd> <a href="/statistics/">
</dl> <FormattedMessage id='general.statistics' />
</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={props.intl.locale} /> <LanguageChooser locale={this.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,26 +1,22 @@
const classNames = require('classnames'); var React = require('react');
const omit = require('lodash.omit'); var classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./button.scss'); require('./button.scss');
const Button = props => { var Button = React.createClass({
const classes = classNames('button', props.className); type: 'Button',
propTypes: {
return (
<button },
className={classes} render: function () {
{...omit(props, ['className', 'children'])} var classes = classNames(
> 'button',
{props.children} this.props.className
</button> );
); return (
}; <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 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./charcount.scss'); require('./charcount.scss');
const CharCount = props => ( var CharCount = React.createClass({
<p type: 'CharCount',
className={classNames('char-count', props.className, { getDefaultProps: function () {
overmax: (props.currentCharacters > props.maxCharacters) return {
})} maxCharacters: 0,
> currentCharacters: 0
{props.currentCharacters}/{props.maxCharacters} };
</p> },
); render: function () {
var classes = classNames(
CharCount.propTypes = { 'char-count',
className: PropTypes.string, this.props.className,
currentCharacters: PropTypes.number, {overmax: (this.props.currentCharacters > this.props.maxCharacters)}
maxCharacters: PropTypes.number );
}; return (
<p className={classes}>
CharCount.defaultProps = { {this.props.currentCharacters}/{this.props.maxCharacters}
currentCharacters: 0, </p>
maxCharacters: 0 );
}; }
});
module.exports = CharCount; module.exports = CharCount;

View file

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

View file

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

View file

@ -1,61 +1,47 @@
const bindAll = require('lodash.bindall'); var classNames = require('classnames');
const classNames = require('classnames'); var Formsy = require('formsy-react');
const Formsy = require('formsy-react'); var omit = require('lodash.omit');
const omit = require('lodash.omit'); var React = require('react');
const PropTypes = require('prop-types'); var validations = require('./validations.jsx').validations;
const React = require('react');
const validations = require('./validations.jsx').validations; for (var validation in validations) {
for (const validation in validations) {
Formsy.addValidationRule(validation, validations[validation]); Formsy.addValidationRule(validation, validations[validation]);
} }
class Form extends React.Component { var Form = React.createClass({
constructor (props) { getDefaultProps: function () {
super(props); return {
bindAll(this, [ noValidate: true,
'handleChange' onChange: function () {}
]); };
this.state = { },
getInitialState: function () {
return {
allValues: {} allValues: {}
}; };
} },
handleChange (currentValues, isChanged) { onChange: function (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 () { render: function () {
var classes = classNames(
'form',
this.props.className
);
return ( return (
<Formsy.Form <Formsy.Form {... this.props} className={classes} ref="formsy" onChange={this.onChange}>
className={classNames('form', this.props.className)} {React.Children.map(this.props.children, function (child) {
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;
} }
return child; }.bind(this))}
})}
</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,6 +1,5 @@
const Formsy = require('formsy-react'); var Formsy = require('formsy-react');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./general-error.scss'); require('./general-error.scss');
@ -11,18 +10,13 @@ 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.
*/ */
const GeneralError = props => { module.exports = Formsy.HOC(React.createClass({
if (!props.showError()) return null; render: function () {
return ( if (!this.props.showError()) return null;
<p className="general-error"> return (
{props.getErrorMessage()} <p className="general-error">
</p> {this.props.getErrorMessage()}
); </p>
}; );
}
GeneralError.propTypes = { }));
getErrorMessage: PropTypes.func,
showError: PropTypes.func
};
module.exports = Formsy.HOC(GeneralError);

View file

@ -1,32 +1,20 @@
const omit = require('lodash.omit'); var React = require('react');
const PropTypes = require('prop-types');
const React = require('react');
/** module.exports = function InputComponentMixin (Component) {
* Higher-order component for building an input field var InputComponent = React.createClass({
* @param {React.Component} Component an input component getDefaultProps: function () {
* @return {React.Component} a wrapped input component return {
*/ messages: {
module.exports = Component => { 'general.notRequired': 'Not Required'
const InputComponent = props => ( }
<Component };
help={props.required ? null : props.messages['general.notRequired']} },
{...omit(props, ['messages'])} render: function () {
/> 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,57 +1,46 @@
const bindAll = require('lodash.bindall'); var classNames = require('classnames');
const classNames = require('classnames'); var FRCInput = require('formsy-react-components').Input;
const FRCInput = require('formsy-react-components').Input; 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('./input.scss'); require('./input.scss');
require('./row.scss'); require('./row.scss');
class Input extends React.Component { var Input = React.createClass({
constructor (props) { type: 'Input',
super(props); getDefaultProps: function () {
bindAll(this, [ return {};
'handleInvalid', },
'handleValid' getInitialState: function () {
]); return {
this.state = {
status: '' status: ''
}; };
} },
handleValid () { onValid: function () {
this.setState({ this.setState({
status: 'pass' status: 'pass'
}); });
} },
handleInvalid () { onInvalid: function () {
this.setState({ this.setState({
status: 'fail' status: 'fail'
}); });
} },
render () { render: function () {
var classes = classNames(
this.state.status,
this.props.className,
{'no-label': (typeof this.props.label === 'undefined')}
);
return ( return (
<FRCInput <FRCInput {... this.props}
className="input" className="input"
rowClassName={classNames( rowClassName={classes}
this.state.status, onValid={this.onValid}
this.props.className, onInvalid={this.onInvalid} />
{'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,33 +1,23 @@
const allCountries = require('react-telephone-input/lib/country_data').allCountries; var allCountries = require('react-telephone-input/lib/country_data').allCountries;
const classNames = require('classnames'); var classNames = require('classnames');
const ComponentMixin = require('formsy-react-components').ComponentMixin; var ComponentMixin = require('formsy-react-components').ComponentMixin;
const createReactClass = require('create-react-class'); var FormsyMixin = require('formsy-react').Mixin;
const FormsyMixin = require('formsy-react').Mixin; var React = require('react');
const omit = require('lodash.omit'); var ReactPhoneInput = require('react-telephone-input/lib/withStyles');
const PropTypes = require('prop-types'); var Row = require('formsy-react-components').Row;
const React = require('react');
const ReactPhoneInput = require('react-telephone-input/lib/withStyles').default;
const Row = require('formsy-react-components').Row;
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx'); var inputHOC = require('./input-hoc.jsx');
const intl = require('../../lib/intl.jsx'); var intl = require('../../lib/intl.jsx');
const validationHOCFactory = require('./validations.jsx').validationHOCFactory; var validationHOCFactory = require('./validations.jsx').validationHOCFactory;
const allIso2 = allCountries.map(country => (country.iso2)); var allIso2 = allCountries.map(function (country) {return country.iso2;});
require('./row.scss'); require('./row.scss');
require('./phone-input.scss'); require('./phone-input.scss');
const PhoneInput = createReactClass({ // eslint-disable-line react/prefer-es6-class var PhoneInput = React.createClass({
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
@ -41,34 +31,29 @@ const PhoneInput = createReactClass({ // eslint-disable-line react/prefer-es6-cl
defaultCountry: 'us' defaultCountry: 'us'
}; };
}, },
handleChangeInput: function (number, country) { onChangeInput: function (number, country) {
const value = { var value = {national_number: number, country_code: country};
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 () {
let defaultCountry = PhoneInput.getDefaultProps().defaultCountry; var 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 <Row {... this.getRowProperties()}
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 <ReactPhoneInput className="form-control"
className="form-control" {... this.props}
defaultCountry={defaultCountry} defaultCountry={defaultCountry}
disabled={this.isFormDisabled() || this.props.disabled} onChange={this.onChangeInput}
id={this.getId()} id={this.getId()}
label={null} label={null}
onChange={this.handleChangeInput} disabled={this.isFormDisabled() || this.props.disabled}
{...omit(this.props, ['className', 'disabled', 'onChange'])}
/> />
{this.renderHelp()} {this.renderHelp()}
{this.renderErrorMessage()} {this.renderErrorMessage()}
@ -78,7 +63,7 @@ const PhoneInput = createReactClass({ // eslint-disable-line react/prefer-es6-cl
} }
}); });
const phoneValidationHOC = validationHOCFactory({ var phoneValidationHOC = validationHOCFactory({
isPhone: <intl.FormattedMessage id="teacherRegistration.validationPhoneNumber" /> isPhone: <intl.FormattedMessage id="teacherRegistration.validationPhoneNumber" />
}); });

View file

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

View file

@ -1,31 +1,33 @@
const classNames = require('classnames'); var classNames = require('classnames');
const defaults = require('lodash.defaultsdeep'); var defaults = require('lodash.defaultsdeep');
const FRCSelect = require('formsy-react-components').Select; var FRCSelect = require('formsy-react-components').Select;
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react'); var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
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');
const Select = props => { var Select = React.createClass({
if (props.required && !props.value) { type: 'Select',
props = defaults({}, props, {value: props.options[0].value}); propTypes: {
},
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 @@
const classNames = require('classnames'); var classNames = require('classnames');
const FRCTextarea = require('formsy-react-components').Textarea; var FRCTextarea = require('formsy-react-components').Textarea;
const omit = require('lodash.omit'); 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('./textarea.scss'); require('./textarea.scss');
const TextArea = props => ( var TextArea = React.createClass({
<FRCTextarea type: 'TextArea',
className="textarea" render: function () {
rowClassName={classNames('textarea-row', props.className)} var classes = classNames(
{...omit(props, ['className'])} 'textarea-row',
/> this.props.className
); );
return (
TextArea.propTypes = { <FRCTextarea {... this.props}
className: PropTypes.string className="textarea"
}; rowClassName={classes} />
);
}
});
module.exports = inputHOC(defaultValidationHOC(TextArea)); module.exports = inputHOC(defaultValidationHOC(TextArea));

View file

@ -1,46 +1,48 @@
const defaults = require('lodash.defaultsdeep'); var defaults = require('lodash.defaultsdeep');
const intl = require('../../lib/intl.jsx'); var intl = require('../../lib/intl.jsx');
const libphonenumber = require('google-libphonenumber'); var libphonenumber = require('google-libphonenumber');
const omit = require('lodash.omit'); var phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance();
const phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance(); var React = require('react');
const PropTypes = require('prop-types');
const React = require('react'); module.exports = {};
module.exports.validations = { module.exports.validations = {
notEquals: (values, value, neq) => (value !== neq), notEquals: function (values, value, neq) {
notEqualsField: (values, value, field) => (value !== values[field]), return value !== neq;
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 {
const parsed = phoneNumberUtil.parse(value.national_number, value.country_code.iso2); var 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 = defaultValidationErrors => (Component => { module.exports.validationHOCFactory = function (defaultValidationErrors) {
const ValidatedComponent = props => ( return function (Component) {
<Component var ValidatedComponent = React.createClass({
validationErrors={defaults( render: function () {
{}, var validationErrors = defaults(
defaultValidationErrors, {},
props.validationErrors defaultValidationErrors,
)} 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,68 +1,79 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const Thumbnail = require('../thumbnail/thumbnail.jsx'); var Thumbnail = require('../thumbnail/thumbnail.jsx');
const FlexRow = require('../flex-row/flex-row.jsx'); var FlexRow = require('../flex-row/flex-row.jsx');
require('./grid.scss'); require('./grid.scss');
const Grid = props => ( var Grid = React.createClass({
<div className={classNames('grid', props.className)}> type: 'Grid',
<FlexRow> getDefaultProps: function () {
{props.items.map((item, key) => { return {
const href = `/${props.itemType}/${item.id}/`; items: require('./grid.json'),
if (props.itemType === 'projects') { itemType: 'projects',
return ( showLoves: false,
<Thumbnail showFavorites: false,
avatar={`https://cdn2.scratch.mit.edu/get_image/user/${item.author.id}_32x32.png`} showRemixes: false,
creator={item.author.username} showViews: false,
favorites={item.stats.favorites} showAvatar: false
href={href} };
key={key} },
loves={item.stats.loves} render: function () {
remixes={item.stats.remixes} var classes = classNames(
showAvatar={props.showAvatar} 'grid',
showFavorites={props.showFavorites} this.props.className
showLoves={props.showLoves} );
showRemixes={props.showRemixes} return (
showViews={props.showViews} <div className={classes}>
src={item.image} <FlexRow>
title={item.title} {this.props.items.map(function (item, key) {
type={'project'} var href = '/' + this.props.itemType + '/' + item.id + '/';
views={item.stats.views}
/>
);
}
return (
<Thumbnail
href={href}
key={key}
owner={item.owner}
src={item.image}
title={item.title}
type={'gallery'}
/>
);
})}
</FlexRow>
</div>
);
Grid.propTypes = { if (this.props.itemType == 'projects') {
className: PropTypes.string, return (
itemType: PropTypes.string, <Thumbnail
items: PropTypes.arrayOf(PropTypes.object) key={key}
}; showLoves={this.props.showLoves}
showFavorites={this.props.showFavorites}
Grid.defaultProps = { showRemixes={this.props.showRemixes}
items: require('./grid.json'), showViews={this.props.showViews}
itemType: 'projects', showAvatar={this.props.showAvatar}
showLoves: false, type={'project'}
showFavorites: false, href={href}
showRemixes: false, title={item.title}
showViews: false, src={item.image}
showAvatar: false avatar={
}; '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,34 +1,39 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react'); var TitleBanner = require('../../components/title-banner/title-banner.jsx');
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
*/ */
const InformationPage = props => ( var InformationPage = React.createClass({
<div className="information-page"> type: 'InformationPage',
<TitleBanner className="masthead"> propTypes: {
<div className="inner"> title: React.PropTypes.string.isRequired
<h1 className="title-banner-h1"> },
{props.title} render: function () {
</h1> var classes = classNames(
'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,128 +1,111 @@
const bindAll = require('lodash.bindall'); 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');
const sessionActions = require('../../redux/session.js'); var sessionActions = require('../../redux/session.js');
const IframeModal = require('../modal/iframe/modal.jsx'); var IframeModal = require('../modal/iframe/modal.jsx');
const Registration = require('../registration/registration.jsx'); var Registration = require('../registration/registration.jsx');
require('./intro.scss'); require('./intro.scss');
class Intro extends React.Component { var Intro = React.createClass({
constructor (props) { type: 'Intro',
super(props); getDefaultProps: function () {
bindAll(this, [ return {
'handleShowVideo', messages: {
'handleCloseVideo', 'intro.aboutScratch': 'ABOUT SCRATCH',
'handleJoinClick', 'intro.forEducators': 'FOR EDUCATORS',
'handleCloseRegistration', 'intro.forParents': 'FOR PARENTS',
'handleCompleteRegistration' 'intro.itsFree': 'it\'s free!',
]); 'intro.joinScratch': 'JOIN SCRATCH',
this.state = { '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: {}
};
},
getInitialState: function () {
return {
videoOpen: false videoOpen: false
}; };
} },
handleShowVideo () { showVideo: function () {
this.setState({videoOpen: true}); this.setState({videoOpen: true});
} },
handleCloseVideo () { closeVideo: function () {
this.setState({videoOpen: false}); this.setState({videoOpen: false});
} },
handleJoinClick (e) { handleJoinClick: function (e) {
e.preventDefault(); e.preventDefault();
this.setState({registrationOpen: true}); this.setState({'registrationOpen': true});
} },
handleCloseRegistration () { closeRegistration: function () {
this.setState({registrationOpen: false}); this.setState({'registrationOpen': false});
} },
handleCompleteRegistration () { completeRegistration: function () {
this.props.dispatch(sessionActions.refreshSession()); this.props.dispatch(sessionActions.refreshSession());
this.closeRegistration(); this.closeRegistration();
} },
render () { render: function () {
return ( return (
<div className="intro"> <div className="intro">
<div className="content"> <div className="content">
<h1 <h1 dangerouslySetInnerHTML={{__html: this.props.messages['intro.tagLine']}}>
dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger </h1>
__html: this.props.messages['intro.tagLine']
}}
/>
<div className="sprites"> <div className="sprites">
<a <a className="sprite sprite-1" href="/projects/editor/?tip_bar=getStarted">
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 className="circle"></div>
<div className="text"> <div className="text">
{this.props.messages['intro.tryItOut']} {this.props.messages['intro.tryItOut']}
</div> </div>
</a> </a>
<a <a className="sprite sprite-2" href="/starter_projects/">
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 className="circle"></div>
<div className="text"> <div className="text">
{this.props.messages['intro.seeExamples']} {this.props.messages['intro.seeExamples']}
</div> </div>
</a> </a>
<a <a className="sprite sprite-3" href="#" onClick={this.handleJoinClick}>
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 className="circle"></div>
<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 <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}
/>
</div> </div>
<div <div className="description"
className="description" dangerouslySetInnerHTML={{__html: this.props.messages['intro.description']}}></div>
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']}
@ -130,70 +113,33 @@ class Intro extends React.Component {
<a href="/educators/"> <a href="/educators/">
{this.props.messages['intro.forEducators']} {this.props.messages['intro.forEducators']}
</a> </a>
<a <a className="last" href="/parents/">
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 <div className="play-button" onClick={this.showVideo}></div>
className="play-button" <img src="//cdn.scratch.mit.edu/scratchr2/static/images/hp-video-screenshot.png"
onClick={this.handleShowVideo} alt="Intro Video" />
/>
<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>
); );
} }
}
Intro.propTypes = {
dispatch: PropTypes.func.isRequired,
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
})
};
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); var mapStateToProps = function (state) {
return {
session: state.session
};
};
var ConnectedIntro = connect(mapStateToProps)(Intro);
module.exports = ConnectedIntro; module.exports = ConnectedIntro;

View file

@ -1,57 +1,46 @@
const bindAll = require('lodash.bindall'); var classNames = require('classnames');
const classNames = require('classnames'); var React = require('react');
const PropTypes = require('prop-types');
const React = require('react');
const jar = require('../../lib/jar.js'); var jar = require('../../lib/jar.js');
const languages = require('../../../languages.json'); var languages = require('../../../languages.json');
const Form = require('../forms/form.jsx'); var Form = require('../forms/form.jsx');
const Select = require('../forms/select.jsx'); var 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.
*/ */
class LanguageChooser extends React.Component { var LanguageChooser = React.createClass({
constructor (props) { type: 'LanguageChooser',
super(props); getDefaultProps: function () {
bindAll(this, [ return {
'handleSetLanguage' languages: languages,
]); locale: 'en'
} };
handleSetLanguage (name, value) { },
onSetLanguage: function (name, value) {
jar.set('scratchlanguage', value); jar.set('scratchlanguage', value);
window.location.reload(); window.location.reload();
} },
render () { render: function () {
const languageOptions = Object.keys(this.props.languages).map(value => ({ var classes = classNames(
value: value, 'language-chooser',
label: this.props.languages[value] this.props.className
})); );
var languageOptions = Object.keys(this.props.languages).map(function (value) {
return {value: value, label: this.props.languages[value]};
}.bind(this));
return ( return (
<Form className={classNames('language-chooser', this.props.className)}> <Form className={classes}>
<Select <Select name="language"
required options={languageOptions}
name="language" value={this.props.locale}
options={languageOptions} onChange={this.onSetLanguage}
value={this.props.locale} required />
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,108 +1,66 @@
const bindAll = require('lodash.bindall'); 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');
const log = require('../../lib/log.js'); var log = require('../../lib/log.js');
const Form = require('../forms/form.jsx'); var Form = require('../forms/form.jsx');
const Input = require('../forms/input.jsx'); var Input = require('../forms/input.jsx');
const Button = require('../forms/button.jsx'); var Button = require('../forms/button.jsx');
const Spinner = require('../spinner/spinner.jsx'); var Spinner = require('../spinner/spinner.jsx');
require('./login.scss'); require('./login.scss');
class Login extends React.Component { var Login = React.createClass({
constructor (props) { type: 'Login',
super(props); propTypes: {
bindAll(this, [ onLogIn: React.PropTypes.func,
'handleSubmit' error: React.PropTypes.string
]); },
this.state = { getInitialState: function () {
return {
waiting: false waiting: false
}; };
} },
handleSubmit (formData) { handleSubmit: function (formData) {
this.setState({waiting: true}); this.setState({waiting: true});
this.props.onLogIn(formData, err => { this.props.onLogIn(formData, function (err) {
if (err) log.error(err); if (err) log.error(err);
this.setState({waiting: false}); this.setState({waiting: false});
}); }.bind(this));
} },
render () { render: function () {
let error; var 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 <label htmlFor="username" key="usernameLabel">
htmlFor="username" <FormattedMessage id='general.username' />
key="usernameLabel"
>
<FormattedMessage id="general.username" />
</label> </label>
<Input <Input type="text" ref="username" name="username" maxLength="30" key="usernameInput" required />
required <label htmlFor="password" key="passwordLabel">
key="usernameInput" <FormattedMessage id='general.password' />
maxLength="30"
name="username"
ref={input => {
this.username = input;
}}
type="text"
/>
<label
htmlFor="password"
key="passwordLabel"
>
<FormattedMessage id="general.password" />
</label> </label>
<Input <Input type="password" ref="password" name="password" key="passwordInput" required />
required
key="passwordInput"
name="password"
ref={input => {
this.password = input;
}}
type="password"
/>
{this.state.waiting ? [ {this.state.waiting ? [
<Button <Button className="submit-button white" type="submit" disabled="disabled" key="submitButton">
className="submit-button white"
disabled="disabled"
key="submitButton"
type="submit"
>
<Spinner /> <Spinner />
</Button> </Button>
] : [ ] : [
<Button <Button className="submit-button white" type="submit" key="submitButton">
className="submit-button white" <FormattedMessage id='general.signIn' />
key="submitButton"
type="submit"
>
<FormattedMessage id="general.signIn" />
</Button> </Button>
]} ]}
<a <a className="right" href="/accounts/password_reset/" key="passwordResetLink">
className="right" <FormattedMessage id='login.needHelp' />
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,68 +1,60 @@
const bindAll = require('lodash.bindall'); var classNames = require('classnames');
const classNames = require('classnames'); var React = require('react');
const MediaQuery = require('react-responsive').default; var MediaQuery = require('react-responsive');
const PropTypes = require('prop-types'); var frameless = require('../../lib/frameless');
const React = require('react');
const frameless = require('../../lib/frameless');
require('./masonrygrid.scss'); require('./masonrygrid.scss');
class MasonryGrid extends React.Component { var MasonryGrid = React.createClass({
constructor (props) { type: 'MasonryGrid',
super(props); getDefaultProps: function () {
bindAll(this, [ return {
'reorderColumns' as: 'div'
]); };
} },
reorderColumns (items, cols) { reorderColumns: function (items, cols) {
const a1 = []; var a1 = [];
const a2 = []; var a2 = [];
const a3 = []; var a3 = [];
let i = 0; var 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++){
const col = (i + cols) % cols; var 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 () {
render () { var classes = classNames(
'masonry',
this.props.className
);
return ( return (
<this.props.as className={classNames('masonry', this.props.className)}> <this.props.as className={classes}>
<MediaQuery maxWidth={frameless.tablet - 1}> <MediaQuery maxWidth={frameless.tablet - 1} >
{this.props.children} {this.props.children}
</MediaQuery> </MediaQuery>
<MediaQuery <MediaQuery minWidth={frameless.tablet} maxWidth={frameless.desktop - 1} >
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,53 +1,39 @@
const bindAll = require('lodash.bindall'); var React = require('react');
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');
class Microworld extends React.Component { var Box = require('../box/box.jsx');
constructor (props) { var LegacyCarousel = require('../carousel/legacy-carousel.jsx');
super(props); var IframeModal = require('../modal/iframe/modal.jsx');
bindAll(this, [ var NestedCarousel = require('../nestedcarousel/nestedcarousel.jsx');
'markVideoOpen',
'markVideoClosed', var Microworld = React.createClass({
'renderVideos', type: 'Microworld',
'renderVideo', propTypes: {
'renderEditorWindow', microworldData: React.PropTypes.node.isRequired
'renderTips', },
'renderStarterProject', markVideoOpen: function (key) {
'renderProjectIdeasBox', {/* When a video is clicked, mark it as an open video, so the video Modal will open.
'renderForum', Key is the number of the video, so distinguish between different videos on the page */}
'renderDesignStudio'
]); var videoOpenArr = this.state.videoOpen;
this.state = {
videoOpen: {}
};
}
markVideoOpen (key) {
/*
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; videoOpenArr[key] = true;
this.setState({videoOpen: videoOpenArr}); this.setState({videoOpen: videoOpenArr});
} },
markVideoClosed (key) { markVideoClosed: function (key) {
/* {/* When a video's x is clicked, mark it as closed, so the video Modal will disappear.
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 */}
Key is the number of the video, so distinguish between different videos on the page var videoOpenArr = this.state.videoOpen;
*/
const videoOpenArr = this.state.videoOpen;
videoOpenArr[key] = false; videoOpenArr[key] = false;
this.setState({videoOpen: videoOpenArr}); this.setState({videoOpen: videoOpenArr});
} },
renderVideos () { getInitialState: function () {
const videos = this.props.microworldData.videos; return {
videoOpen: {}
};
},
renderVideos: function () {
var videos = this.props.microworldData.videos;
if (!videos || videos.length <= 0) { if (!videos || videos.length <= 0) {
return null; return null;
} }
@ -62,32 +48,26 @@ class Microworld extends React.Component {
</div> </div>
</div> </div>
); );
} },
renderVideo (video, key) { renderVideo: function (video, key) {
return ( return (
<div> <div>
<div className="video"> <div className="video">
<div <div className="play-button" onClick={this.markVideoOpen.bind(this, key)}>
className="play-button" </div>
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 () { renderEditorWindow: function () {
const projectId = this.props.microworldData.microworld_project_id; var projectId = this.props.microworldData.microworld_project_id;
if (!projectId) { if (!projectId) {
return null; return null;
@ -95,37 +75,30 @@ class Microworld extends React.Component {
return ( return (
<div className="editor section"> <div className="editor section">
<h1 className="sectionheader">Start Creating!</h1> <h1 className="sectionheader">Start Creating!</h1>
<iframe <iframe src={'//scratch.mit.edu/projects/embed-editor/' + projectId + '/?isMicroworld=true'}
frameBorder="0" frameBorder="0"> </iframe>
src={`//scratch.mit.edu/projects/embed-editor/${projectId}/?isMicroworld=true`}
/>
{this.renderTips()} {this.renderTips()}
</div> </div>
); );
} },
renderTips () { renderTips: function () {
const tips = this.props.microworldData.tips; var 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 <NestedCarousel items={tips} settings={{slidesToShow:1,slidesToScroll:1}}/>
items={tips}
settings={{
slidesToShow: 1,
slidesToScroll: 1
}}
/>
</div> </div>
</div> </div>
); );
} },
renderStarterProject () { renderStarterProject: function () {
const starterProjects = this.props.microworldData.starter_projects; var starterProjects = this.props.microworldData.starter_projects;
if (!starterProjects || starterProjects.length <= 0){ if (!starterProjects || starterProjects.length <= 0){
return null; return null;
} }
@ -134,42 +107,39 @@ class Microworld extends React.Component {
<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 () { renderProjectIdeasBox: function () {
const communityProjects = this.props.microworldData.community_projects; var communityProjects = this.props.microworldData.community_projects;
if (!communityProjects || communityProjects.size <= 0) { if (!communityProjects || communityProjects.size <= 0) {
return null; return null;
} }
const featured = communityProjects.featured_projects; var featured = communityProjects.featured_projects;
const all = communityProjects.newest_projects; var all = communityProjects.newest_projects;
const rows = []; var 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
key="community_all_projects" title="All Community Projects"
title="All Community Projects" key="community_all_projects">
> <LegacyCarousel items={all} />
<LegacyCarousel items={all} /> </Box>
</Box>
); );
} }
if (rows.length <= 0) { if (rows.length <= 0) {
@ -181,88 +151,67 @@ class Microworld extends React.Component {
{rows} {rows}
</div> </div>
); );
} },
renderForum () { renderForum: function () {
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 () { renderDesignStudio: function () {
const designChallenge = this.props.microworldData.design_challenge; var designChallenge = this.props.microworldData.design_challenge;
if (!designChallenge) { if (!designChallenge) {
return null; return null;
} }
let studioHref = '';
if (designChallenge.studio_id) { if (designChallenge.studio_id) {
studioHref = `https://scratch.mit.edu//studios/${designChallenge.studio_id}/`; var 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 <iframe src={'https://scratch.mit.edu/projects/' + designChallenge.project_id +
frameBorder="0" '/#fullscreen'} frameBorder="0"> </iframe>
src={`https://scratch.mit.edu/projects/${designChallenge.project_id}/#fullscreen`}
/>
</div> </div>
<div className="design-studio-projects"> <div className="design-studio-projects">
<Box <Box title="Examples"
key="scratch_design_studio" key="scratch_design_studio"
moreHref={studioHref ? studioHref : null} moreTitle={studioHref ? 'Visit the studio' : null}
moreTitle={studioHref ? 'Visit the studio' : null} moreHref={studioHref ? studioHref : 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 <LegacyCarousel settings={{slidesToShow:2,slidesToScroll:2}}
items={this.props.microworldData.design_challenge.studio1} items={this.props.microworldData.design_challenge.studio1} />
settings={{ <LegacyCarousel settings={{slidesToShow:2,slidesToScroll:2}}
slidesToShow: 2, items={this.props.microworldData.design_challenge.studio2} />
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 ( },
<div className="section"> render: function () {
<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">
@ -282,10 +231,6 @@ class Microworld extends React.Component {
); );
} }
} });
Microworld.propTypes = {
microworldData: PropTypes.node.isRequired
};
module.exports = Microworld; module.exports = Microworld;

View file

@ -1,9 +1,7 @@
const bindAll = require('lodash.bindall'); 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'); var ReactModal = require('react-modal');
const React = require('react');
const ReactModal = require('react-modal');
require('./modal.scss'); require('./modal.scss');
@ -12,55 +10,47 @@ ReactModal.setAppElement(document.getElementById('view'));
/** /**
* Container for pop up windows (See: registration window) * Container for pop up windows (See: registration window)
*/ */
class Modal extends React.Component { var Modal = React.createClass({
constructor (props) { type: 'Modal',
super(props); propTypes: {
bindAll(this, [ className: React.PropTypes.string,
'handleRequestClose' overlayClassName: React.PropTypes.string
]); },
} requestClose: function () {
handleRequestClose () {
return this.modal.portal.requestClose(); return this.modal.portal.requestClose();
} },
render () { render: function () {
var modalClasses = classNames(
'modal-content',
this.props.className
);
var overlayClasses = classNames(
'modal-overlay',
this.props.overlayClassName
);
return ( return (
<ReactModal <ReactModal
appElement={document.getElementById('view')} ref={
className={{ function (component) {
base: classNames('modal-content', this.props.className), this.modal = component;
afterOpen: classNames('modal-content', this.props.className), }.bind(this)
beforeClose: classNames('modal-content', this.props.className) }
}} className={modalClasses}
overlayClassName={{ overlayClassName={overlayClasses}
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 <div className="modal-content-close" onClick={this.requestClose}>
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);
} }
.modal-content:focus { .ReactModal__Content:focus {
outline: none; outline: none;
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -1,27 +1,29 @@
const React = require('react'); var React = require('react');
const NavigationBox = require('../../base/navigation.jsx'); var NavigationBox = require('../../base/navigation.jsx');
require('./navigation.scss'); require('./navigation.scss');
const Navigation = () => ( var Navigation = React.createClass({
<NavigationBox> type: 'Navigation',
<ul className="ul mod-2017"> render: function () {
<li className="li-left mod-logo mod-2017"> return (
<a <NavigationBox>
className="logo-a" <ul className="ul mod-2017">
href="/conference" <li className="li-left mod-logo mod-2017">
> <a href="/conference" className="logo-a">
<img <img
alt="Scratch Logo" src="/images/logo_sm.png"
className="logo-a-image" alt="Scratch Logo"
src="/images/logo_sm.png" className="logo-a-image"
/> />
<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,27 +1,29 @@
const React = require('react'); var React = require('react');
const NavigationBox = require('../../base/navigation.jsx'); var NavigationBox = require('../../base/navigation.jsx');
require('./navigation.scss'); require('./navigation.scss');
const Navigation = () => ( var Navigation = React.createClass({
<NavigationBox> type: 'Navigation',
<ul className="ul mod-2018"> render: function () {
<li className="li-left mod-logo mod-2018"> return (
<a <NavigationBox>
className="logo-a" <ul className="ul mod-2018">
href="/" <li className="li-left mod-logo mod-2018">
> <a href="/" className="logo-a">
<img <img
alt="Scratch Logo" src="/images/logo_sm.png"
className="logo-a-image" alt="Scratch Logo"
src="/images/logo_sm.png" className="logo-a-image"
/> />
<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,48 +1,31 @@
const bindAll = require('lodash.bindall'); 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 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 PropTypes = require('prop-types');
const React = require('react');
const messageCountActions = require('../../../redux/message-count.js'); var messageCountActions = require('../../../redux/message-count.js');
const sessionActions = require('../../../redux/session.js'); var sessionActions = require('../../../redux/session.js');
const api = require('../../../lib/api'); var api = require('../../../lib/api');
const Avatar = require('../../avatar/avatar.jsx'); var Avatar = require('../../avatar/avatar.jsx');
const Button = require('../../forms/button.jsx'); var Button = require('../../forms/button.jsx');
const Dropdown = require('../../dropdown/dropdown.jsx'); var Dropdown = require('../../dropdown/dropdown.jsx');
const Form = require('../../forms/form.jsx'); var Form = require('../../forms/form.jsx');
const Input = require('../../forms/input.jsx'); var Input = require('../../forms/input.jsx');
const log = require('../../../lib/log.js'); var log = require('../../../lib/log.js');
const Login = require('../../login/login.jsx'); var Login = require('../../login/login.jsx');
const Modal = require('../../modal/base/modal.jsx'); var Modal = require('../../modal/base/modal.jsx');
const NavigationBox = require('../base/navigation.jsx'); var NavigationBox = require('../base/navigation.jsx');
const Registration = require('../../registration/registration.jsx'); var Registration = require('../../registration/registration.jsx');
require('./navigation.scss'); require('./navigation.scss');
class Navigation extends React.Component { var Navigation = React.createClass({
constructor (props) { type: 'Navigation',
super(props); getInitialState: function () {
bindAll(this, [ return {
'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,
@ -50,153 +33,155 @@ class Navigation extends React.Component {
registrationOpen: false, registrationOpen: false,
messageCountIntervalId: -1 // javascript method interval id for getting messsage count. messageCountIntervalId: -1 // javascript method interval id for getting messsage count.
}; };
} },
componentDidMount () { getDefaultProps: function () {
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) {
const intervalId = setInterval(() => { var intervalId = setInterval(function () {
this.props.dispatch( this.props.dispatch(messageCountActions.getCount(this.props.session.session.user.username));
messageCountActions.getCount(this.props.session.session.user.username) }.bind(this), 120000); // check for new messages every 2 mins.
); 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 (prevProps) { componentDidUpdate: function (prevProps) {
if (prevProps.session.session.user !== this.props.session.session.user) { if (prevProps.session.session.user != this.props.session.session.user) {
this.setState({ // eslint-disable-line react/no-did-update-set-state this.setState({
loginOpen: false, 'loginOpen': false,
accountNavOpen: false 'accountNavOpen': false
}); });
if (this.props.session.session.user) { if (this.props.session.session.user) {
const intervalId = setInterval(() => { var intervalId = setInterval(function () {
this.props.dispatch( this.props.dispatch(messageCountActions.getCount(this.props.session.session.user.username));
messageCountActions.getCount(this.props.session.session.user.username) }.bind(this), 120000); // check for new messages every 2 mins.
); 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({ // eslint-disable-line react/no-did-update-set-state this.setState({
messageCountIntervalId: -1 'messageCountIntervalId': -1
}); });
} }
} }
} },
componentWillUnmount () { componentWillUnmount: function () {
// 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 () { getProfileUrl: function () {
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 (e) { handleJoinClick: function (e) {
e.preventDefault(); e.preventDefault();
this.setState({registrationOpen: true}); this.setState({'registrationOpen': true});
} },
handleLoginClick (e) { handleLoginClick: function (e) {
e.preventDefault(); e.preventDefault();
this.setState({loginOpen: !this.state.loginOpen}); this.setState({'loginOpen': !this.state.loginOpen});
} },
handleCloseLogin () { closeLogin: function () {
this.setState({loginOpen: false}); this.setState({'loginOpen': false});
} },
handleLogIn (formData, callback) { handleLogIn: function (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
}, (err, body) => { }, function (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 (e) { handleLogOut: function (e) {
e.preventDefault(); e.preventDefault();
api({ api({
host: '', host: '',
method: 'post', method: 'post',
uri: '/accounts/logout/', uri: '/accounts/logout/',
useCsrf: true useCsrf: true
}, err => { }, function (err) {
if (err) log.error(err); if (err) log.error(err);
this.handleCloseLogin(); this.closeLogin();
window.location = '/'; window.location = '/';
}); }.bind(this));
} },
handleAccountNavClick (e) { handleAccountNavClick: function (e) {
e.preventDefault(); e.preventDefault();
this.setState({accountNavOpen: true}); this.setState({'accountNavOpen': true});
} },
handleCloseAccountNav () { closeAccountNav: function () {
this.setState({accountNavOpen: false}); this.setState({'accountNavOpen': false});
} },
showCanceledDeletion () { showCanceledDeletion: function () {
this.setState({canceledDeletionOpen: true}); this.setState({'canceledDeletionOpen': true});
} },
handleCloseCanceledDeletion () { closeCanceledDeletion: function () {
this.setState({canceledDeletionOpen: false}); this.setState({'canceledDeletionOpen': false});
} },
handleCloseRegistration () { closeRegistration: function () {
this.setState({registrationOpen: false}); this.setState({'registrationOpen': false});
} },
handleCompleteRegistration () { completeRegistration: function () {
this.props.dispatch(sessionActions.refreshSession()); this.props.dispatch(sessionActions.refreshSession());
this.handleCloseRegistration(); this.closeRegistration();
} },
handleSearchSubmit (formData) { onSearchSubmit: function (formData) {
window.location.href = `/search/projects?q=${encodeURIComponent(formData.q)}`; window.location.href = '/search/projects?q=' + encodeURIComponent(formData.q);
} },
render () { render: function () {
const createLink = this.props.session.session.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home'; var classes = classNames({
'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 <NavigationBox className={classes}>
className={classNames({
'logged-in': this.props.session.session.user
})}
>
<ul> <ul>
<li className="logo"> <li className="logo"><a href="/" aria-label="Scratch"></a></li>
<a
aria-label="Scratch"
href="/"
/>
</li>
<li className="link create"> <li className="link create">
<a href={createLink}> <a href={createLink}>
@ -220,76 +205,47 @@ class Navigation extends React.Component {
</li> </li>
<li className="search"> <li className="search">
<Form onSubmit={this.handleSearchSubmit}> <Form onSubmit={this.onSearchSubmit}>
<Button <Button type="submit" className="btn-search" />
className="btn-search" <Input type="text"
type="submit" value={this.props.searchTerm}
/> aria-label={formatMessage({id: 'general.search'})}
<Input placeholder={formatMessage({id: 'general.search'})}
aria-label={this.props.intl.formatMessage({id: 'general.search'})} name="q" />
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 <li className="link right messages" key="messages">
className="link right messages"
key="messages"
>
<a <a
href="/messages/" href="/messages/"
title={this.props.intl.formatMessage({id: 'general.messages'})} title={formatMessage({id: 'general.messages'})}>
>
<span <span className={messageClasses}>{this.props.unreadMessageCount}</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 <li className="link right mystuff" key="mystuff">
className="link right mystuff"
key="mystuff"
>
<a <a
href="/mystuff/" href="/mystuff/"
title={this.props.intl.formatMessage({id: 'general.myStuff'})} title={formatMessage({id: 'general.myStuff'})}>
>
<FormattedMessage id="general.myStuff" /> <FormattedMessage id="general.myStuff" />
</a> </a>
</li>, </li>,
<li <li className="link right account-nav" key="account-nav">
className="link right account-nav" <a className={dropdownClasses}
key="account-nav" href="#" onClick={this.handleAccountNavClick}>
> <Avatar src={this.props.session.session.user.thumbnailUrl} alt="" />
<a <span className='profile-name'>
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"
className={process.env.SCRATCH_ENV} isOpen={this.state.accountNavOpen}
isOpen={this.state.accountNavOpen} onRequestClose={this.closeAccountNav}
onRequestClose={this.handleCloseAccountNav} className={process.env.SCRATCH_ENV}>
>
<li> <li>
<a href={this.getProfileUrl()}> <a href={this.getProfileUrl()}>
<FormattedMessage id="general.profile" /> <FormattedMessage id="general.profile" />
@ -308,8 +264,8 @@ class Navigation extends React.Component {
</li> </li>
] : []} ] : []}
{this.props.permissions.student ? [ {this.props.permissions.student ? [
<li key="my-class-li"> <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>
@ -320,123 +276,69 @@ class Navigation extends React.Component {
</a> </a>
</li> </li>
<li className="divider"> <li className="divider">
<a <a href="#" onClick={this.handleLogOut}>
href="#"
onClick={this.handleLogOut}
>
<FormattedMessage id="navigation.signOut" /> <FormattedMessage id="navigation.signOut" />
</a> </a>
</li> </li>
</Dropdown> </Dropdown>
</li> </li>
] : [ ] : [
<li <li className="link right join" key="join">
className="link right join" <a href="#" onClick={this.handleJoinClick}>
key="join"
>
<a
href="#"
onClick={this.handleJoinClick}
>
<FormattedMessage id="general.joinScratch" /> <FormattedMessage id="general.joinScratch" />
</a> </a>
</li>, </li>,
<Registration <Registration
isOpen={this.state.registrationOpen} key="registration"
key="registration" isOpen={this.state.registrationOpen}
onRegistrationDone={this.handleCompleteRegistration} onRequestClose={this.closeRegistration}
onRequestClose={this.handleCloseRegistration} onRegistrationDone={this.completeRegistration} />,
/>, <li className="link right login-item" key="login">
<li
className="link right login-item"
key="login"
>
<a <a
className="ignore-react-onclickoutside"
href="#" href="#"
key="login-link"
onClick={this.handleLoginClick} onClick={this.handleLoginClick}
> className="ignore-react-onclickoutside"
<FormattedMessage id="general.signIn" /> key="login-link">
</a> <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}
key="login-dropdown" onRequestClose={this.closeLogin}
onRequestClose={this.handleCloseLogin} key="login-dropdown">
>
<Login <Login
error={this.state.loginError}
onLogIn={this.handleLogIn} onLogIn={this.handleLogIn}
/> error={this.state.loginError} />
</Dropdown> </Dropdown>
</li> </li>
]) : []} ]) : [
]}
</ul> </ul>
<Modal <Modal isOpen={this.state.canceledDeletionOpen}
isOpen={this.state.canceledDeletionOpen} onRequestClose={this.closeCanceledDeletion}
style={{ style={{content:{padding: 15}}}>
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>
<FormattedMessage Your account was scheduled for deletion but you logged in. Your account has been reactivated.
id="general.noDeletionDescription" If you didnt request for your account to be deleted, you should
values={{ {' '}<a href="/accounts/password_reset/">change your password</a>{' '}
resetLink: <a href="/accounts/password_reset/"> to make sure your account is secure.
{this.props.intl.formatMessage({id: 'general.noDeletionLink'})}
</a>
}}
/>
</p> </p>
</Modal> </Modal>
</NavigationBox> </NavigationBox>
); );
} }
}
Navigation.propTypes = {
dispatch: PropTypes.func,
intl: intlShape,
permissions: PropTypes.shape({
admin: PropTypes.bool,
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])
};
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); var mapStateToProps = function (state) {
return {
session: state.session,
permissions: state.permissions,
unreadMessageCount: state.messageCount.messageCount,
searchTerm: state.navigation
};
};
var ConnectedNavigation = connect(mapStateToProps)(Navigation);
module.exports = injectIntl(ConnectedNavigation); module.exports = injectIntl(ConnectedNavigation);

View file

@ -1,84 +1,75 @@
const classNames = require('classnames'); var classNames = require('classnames');
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react'); var Slider = require('react-slick');
const Slider = require('react-slick');
const Thumbnail = require('../thumbnail/thumbnail.jsx'); var 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.
*/ */}
const NestedCarousel = props => { var NestedCarousel = React.createClass({
defaults(props.settings, { type: 'NestedCarousel',
dots: true, propTypes: {
infinite: false, items: React.PropTypes.array
lazyLoad: true, },
slidesToShow: 1, getDefaultProps: function () {
slidesToScroll: 1, return {
variableWidth: false items: require('./nestedcarousel.json')
}); };
},
render: function () {
var settings = this.props.settings || {};
defaults(settings, {
dots: true,
infinite: false,
lazyLoad: true,
slidesToShow: 1,
slidesToScroll: 1,
variableWidth: false
});
const arrows = props.items.length > props.settings.slidesToShow; var arrows = this.props.items.length > 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}
stages.push( src={item.thumbnailUrl}
<div key={`outer_${i}`}> linkTitle = {false} />);
<h3>{props.items[i].title}</h3> }
stages.push(
<div key={'outer_' + i}>
<h3>{this.props.items[i].title}</h3>
{thumbnails} {thumbnails}
</div> </div>);
}
return (
<Slider className={classes} arrows={arrows} {... settings}>
{stages}
</Slider>
); );
} }
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,53 +1,54 @@
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const Box = require('../box/box.jsx'); var Box = require('../box/box.jsx');
require('./news.scss'); require('./news.scss');
const News = props => ( var News = React.createClass({
<Box type: 'News',
className="news" propTypes: {
moreHref="/discuss/5/" items: React.PropTypes.array
moreTitle={props.messages['general.viewAll']} },
title={props.messages['news.scratchNews']} getDefaultProps: function () {
> return {
<ul> items: require('./news.json'),
{props.items.map(item => ( messages: {
<li key={item.id}> 'general.viewAll': 'View All',
<a href={item.url}> 'news.scratchNews': 'Scratch News'
<img }
alt="" };
className="news-image" },
height="53" render: function () {
src={item.image} return (
width="53" <Box
/> className="news"
<div className="news-description"> title={this.props.messages['news.scratchNews']}
<h4>{item.headline}</h4> moreTitle={this.props.messages['general.viewAll']}
<p>{item.copy}</p> moreHref="/discuss/5/">
</div>
</a>
</li>
))}
</ul>
</Box>
);
News.propTypes = { <ul>
items: PropTypes.arrayOf(PropTypes.object), {this.props.items.map(function (item) {
messages: PropTypes.shape({ return (
'general.viewAll': PropTypes.string, <li key={item.id}>
'news.scratchNews': PropTypes.string <a href={item.url}>
}) <img src={item.image}
}; className="news-image"
width="53"
News.defaultProps = { height="53"
items: require('./news.json'), alt=""
messages: { />
'general.viewAll': 'View All', <div className="news-description">
'news.scratchNews': 'Scratch News' <h4>{item.headline}</h4>
<p>{item.copy}</p>
</div>
</a>
</li>
);
})}
</ul>
</Box>
);
} }
}; });
module.exports = News; module.exports = News;

View file

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

View file

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

View file

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

View file

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

View file

@ -1,45 +1,42 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const Progression = props => { module.exports = React.createClass({
const childProps = { displayName: 'Progression',
activeStep: props.step, propTypes: {
totalSteps: React.Children.count(props.children) step: function (props, propName, componentName) {
}; var stepValidator = function (props, propName) {
return ( if (props[propName] > -1 && props[propName] < props.children.length) {
<div return null;
className={classNames('progression', props.className)} } else {
{...props} return new Error('Prop `step` out of range');
>
{React.Children.map(props.children, (child, id) => {
if (id === props.step) {
return React.cloneElement(child, childProps);
} }
})} };
</div> return (
); React.PropTypes.number.isRequired(props, propName, componentName) ||
}; stepValidator(props, propName, componentName)
);
Progression.propTypes = { }
children: PropTypes.node, },
className: PropTypes.string, getDefaultProps: function () {
step: function (props, propName, componentName) { return {
const stepValidator = (propz, name) => { step: 0
if (propz[name] > -1 && propz[name] < propz.children.length) { };
return null; },
} render: function () {
return new Error('Prop `step` out of range'); var childProps = {
activeStep: this.props.step,
totalSteps: React.Children.count(this.props.children)
}; };
return ( return (
(typeof props[propName] === 'number' ? null : new Error('Not a number')) || <div {... this.props}
stepValidator(props, propName, componentName) className={classNames('progression', this.props.className)}>
{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,62 +1,53 @@
const bindAll = require('lodash.bindall'); var React = require('react');
const PropTypes = require('prop-types'); var IframeModal = require('../modal/iframe/modal.jsx');
const React = require('react');
const IframeModal = require('../modal/iframe/modal.jsx');
require('./registration.scss'); require('./registration.scss');
class Registration extends React.Component { var Registration = React.createClass({
constructor (props) { propTypes: {
super(props); isOpen: React.PropTypes.bool,
bindAll(this, [ onRegistrationDone: React.PropTypes.func,
'handleMessage', onRequestClose: React.PropTypes.func
'toggleMessageListener' },
]); onMessage: function (e) {
} if (e.origin != window.location.origin) return;
componentDidMount () { if (e.source != this.registrationIframe.contentWindow) return;
if (this.props.isOpen) this.toggleMessageListener(true); if (e.data == 'registration-done') this.props.onRegistrationDone();
} 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 (present) { toggleMessageListener: function (present) {
if (present) { if (present) {
window.addEventListener('message', this.handleMessage); window.addEventListener('message', this.onMessage);
} else { } else {
window.removeEventListener('message', this.handleMessage); window.removeEventListener('message', this.onMessage);
} }
} },
render () { componentDidMount: function () {
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
className="mod-registration"
componentRef={iframe => { // eslint-disable-line react/jsx-no-bind
this.registrationIframe = iframe;
}}
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
src="/accounts/standalone-registration/"
onRequestClose={this.props.onRequestClose} onRequestClose={this.props.onRequestClose}
className="mod-registration"
componentRef={
function (iframe) {
this.registrationIframe = iframe;
}.bind(this)
}
src="/accounts/standalone-registration/"
/> />
); );
} }
} });
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,18 +1,17 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./slide.scss'); require('./slide.scss');
const Slide = props => ( var Slide = React.createClass({
<div className={classNames(['slide', props.className])}> displayName: 'Slide',
{props.children} render: function () {
</div> return (
); <div className={classNames(['slide', this.props.className])}>
{this.props.children}
Slide.propTypes = { </div>
children: PropTypes.node, );
className: PropTypes.string }
}; });
module.exports = Slide; module.exports = Slide;

View file

@ -1,47 +1,57 @@
const classNames = require('classnames'); var classNames = require('classnames');
const FormattedRelative = require('react-intl').FormattedRelative; var FormattedRelative = require('react-intl').FormattedRelative;
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const FlexRow = require('../flex-row/flex-row.jsx'); var FlexRow = require('../flex-row/flex-row.jsx');
require('./social-message.scss'); require('./social-message.scss');
const SocialMessage = props => ( var SocialMessage = React.createClass({
<props.as className={classNames('social-message', props.className)}> type: 'SocialMessage',
<FlexRow className="mod-social-message"> propTypes: {
<div className="social-message-content"> as: React.PropTypes.string,
{typeof props.iconSrc === 'undefined' ? [] : [ datetime: React.PropTypes.string.isRequired,
<img iconSrc: React.PropTypes.string,
alt={props.iconAlt} iconAlt: React.PropTypes.string,
className={classNames('social-message-icon', props.imgClassName)} imgClassName: React.PropTypes.string
key="social-message-icon" },
src={props.iconSrc} getDefaultProps: function () {
/> return {
]} as: 'li'
<div> };
{props.children} },
</div> render: function () {
</div> var classes = classNames(
<span className="social-message-date"> 'social-message',
<FormattedRelative value={new Date(props.datetime)} /> this.props.className
</span> );
</FlexRow> var imgClass = classNames(
</props.as> 'social-message-icon',
); this.props.imgClassName
);
SocialMessage.propTypes = { return (
as: PropTypes.string, // eslint-disable-line react/no-unused-prop-types <this.props.as className={classes}>
children: PropTypes.node, <FlexRow className="mod-social-message">
className: PropTypes.string, <div className="social-message-content">
datetime: PropTypes.string.isRequired, {typeof this.props.iconSrc !== 'undefined' ? [
iconAlt: PropTypes.string, <img
iconSrc: PropTypes.string, key="social-message-icon"
imgClassName: PropTypes.string className={imgClass}
}; 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,18 +1,20 @@
const range = require('lodash.range'); var range = require('lodash.range');
const React = require('react'); var React = require('react');
require('./spinner.scss'); require('./spinner.scss');
// Adapted from http://tobiasahlin.com/spinkit/ var Spinner = React.createClass({
const Spinner = () => ( // Adapted from http://tobiasahlin.com/spinkit/
<div className="spinner"> type: 'Spinner',
{range(1, 13).map(id => ( render: function () {
<div return (
className={`circle${id} circle`} <div className="spinner">
key={`circle${id}`} {range(1,13).map(function (id) {
/> return <div className={'circle' + id + ' circle'}></div>;
))} })}
</div> </div>
); );
}
});
module.exports = Spinner; module.exports = Spinner;

View file

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

View file

@ -1,38 +1,36 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
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.
*/ */
const SubNavigation = props => ( var SubNavigation = React.createClass({
<div type: 'SubNavigation',
className={classNames( getDefaultProps: function () {
return {
align: 'middle'
};
},
render: function () {
var classes = classNames(
[ [
'sub-nav', 'sub-nav',
props.className this.props.className
], ],
{ {
'sub-nav-align-left': props.align === 'left', 'sub-nav-align-left': this.props.align === 'left',
'sub-nav-align-right': props.align === 'right' 'sub-nav-align-right': this.props.align === 'right'
} }
)} );
> return (
{props.children} <div className={classes}>
</div> {this.props.children}
); </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,26 +1,28 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
const React = require('react'); var 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.
*/ */
const Tabs = props => ( var Tabs = React.createClass({
<div className="tab-background"> type: 'Tabs',
<SubNavigation className={classNames('tabs', props.className)}> render: function () {
{props.children} var classes = classNames(
</SubNavigation> 'tabs',
</div> this.props.className
); );
return (
Tabs.propTypes = { <div className='tab-background'>
children: PropTypes.node, <SubNavigation className={classes}>
className: PropTypes.string {this.props.children}
}; </SubNavigation>
</div>
);
}
});
module.exports = Tabs; module.exports = Tabs;

View file

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

View file

@ -1,168 +1,186 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./thumbnail.scss'); require('./thumbnail.scss');
const Thumbnail = props => { var Thumbnail = React.createClass({
const extra = []; type: 'Thumbnail',
const info = []; propTypes: {
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 (props.loves && props.showLoves) { if (this.props.loves && this.props.showLoves) {
extra.push( extra.push(
<div <div
className="thumbnail-loves" key="loves"
key="loves" className="thumbnail-loves"
title={`${props.loves} loves`} title={this.props.loves + ' loves'}>
> {this.props.loves}
{props.loves}
</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>
);
}
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>
{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,18 +1,21 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./title-banner.scss'); require('./title-banner.scss');
const TitleBanner = props => ( var TitleBanner = React.createClass({
<div className={classNames('title-banner', props.className)}> type: 'TitleBanner',
{props.children} render: function () {
</div> var classes = classNames(
); 'title-banner',
this.props.className
TitleBanner.propTypes = { );
children: PropTypes.node, return (
className: PropTypes.string <div className={classes}>
}; {this.props.children}
</div>
);
}
});
module.exports = TitleBanner; module.exports = TitleBanner;

View file

@ -1,39 +1,33 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./tooltip.scss'); require('./tooltip.scss');
const Tooltip = props => ( var Tooltip = React.createClass({
<span type: 'Tooltip',
className={classNames( getDefaultProps: function () {
return {
title: '',
tipContent: ''
};
},
render: function () {
var classes = classNames(
'tooltip', 'tooltip',
props.className, this.props.className,
{overmax: (props.currentCharacters > props.maxCharacters)} {overmax: (this.props.currentCharacters > this.props.maxCharacters)}
)} );
> return (
<span className="tip"> <span className={classes}>
<img <span className="tip">
alt="info icon" <img src="/svgs/tooltip/info.svg" alt="info icon" />
src="/svgs/tooltip/info.svg" </span>
/> <span className="expand">
</span> {this.props.tipContent}
<span className="expand"> </span>
{props.tipContent} </span>
</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,68 +1,58 @@
const classNames = require('classnames'); var classNames = require('classnames');
const FormattedMessage = require('react-intl').FormattedMessage; var React = require('react');
const PropTypes = require('prop-types'); var FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
require('../forms/button.scss'); require('../forms/button.scss');
require('./ttt-tile.scss'); require('./ttt-tile.scss');
const TTTTile = props => ( var TTTTile = React.createClass({
<div className={classNames('ttt-tile', props.className)}> type: 'TTTTile',
<a href={props.tutorialLoc}> propTypes: {
<div className="ttt-tile-tutorial"> title: React.PropTypes.string.isRequired,
<div className="ttt-tile-image"> description: React.PropTypes.string,
<img thumbUrl: React.PropTypes.string.isRequired,
alt="" tutorialLoc: React.PropTypes.string.isRequired,
className="ttt-tile-image-img" onGuideClick: React.PropTypes.func
src={props.thumbUrl} },
/> render: function () {
<div className="ttt-tile-image-try"> var classes = classNames(
<div className="button mod-ttt-try-button"> 'ttt-tile',
<FormattedMessage id="tile.tryIt" /> this.props.className
);
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">
<div className="ttt-tile-tag"> </a>
<FormattedMessage {this.props.onGuideClick && (
defaultMessage="Tutorial" <div className="ttt-tile-guides" onClick={this.props.onGuideClick}>
id="ttt.tutorial" <FormattedMessage id='tile.guides' defaultMessage='See Cards and Guides'/>
/> <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,81 +1,69 @@
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const Box = require('../box/box.jsx'); var Box = require('../box/box.jsx');
require('./welcome.scss'); require('./welcome.scss');
const Welcome = props => ( var Welcome = React.createClass({
<Box type: 'Welcome',
className="welcome" propTypes: {
moreHref="#" onDismiss: React.PropTypes.func
moreProps={{ },
className: 'close', getDefaultProps: function () {
title: 'Dismiss', return {
onClick: props.onDismiss messages: {
}} 'welcome.welcomeToScratch': 'Welcome to Scratch!',
moreTitle="x" 'welcome.learn': 'Learn how to make a project in Scratch',
title={props.messages['welcome.welcomeToScratch']} 'welcome.tryOut': 'Try out starter projects',
> 'welcome.connect': 'Connect with other Scratchers'
<div className="welcome-col blue"> }
<h4> };
<a href="/projects/editor/?tip_bar=getStarted"> },
{props.messages['welcome.learn']} render: function () {
</a> return (
</h4> <Box title={this.props.messages['welcome.welcomeToScratch']}
<a href="/projects/editor/?tip_bar=getStarted"> className="welcome"
<img moreTitle="x"
alt="Get Started" moreHref="#"
src="/images/welcome-learn.png" moreProps={{
/> className: 'close',
</a> title: 'Dismiss',
</div> onClick: this.props.onDismiss
<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>
);
Welcome.propTypes = { <div className="welcome-col blue">
messages: PropTypes.shape({ <h4>
'welcome.welcomeToScratch': PropTypes.string, <a href="/projects/editor/?tip_bar=getStarted">
'welcome.learn': PropTypes.string, {this.props.messages['welcome.learn']}
'welcome.tryOut': PropTypes.string, </a>
'welcome.connect': PropTypes.string </h4>
}), <a href="/projects/editor/?tip_bar=getStarted">
onDismiss: PropTypes.func <img src="/images/welcome-learn.png" alt="Get Started" />
}; </a>
</div>
Welcome.defaultProps = { <div className="welcome-col green">
messages: { <h4>
'welcome.welcomeToScratch': 'Welcome to Scratch!', <a href="/starter_projects/">
'welcome.learn': 'Learn how to make a project in Scratch', {this.props.messages['welcome.tryOut']}
'welcome.tryOut': 'Try out starter projects', </a>
'welcome.connect': 'Connect with other Scratchers' </h4>
<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 @@
const jar = require('./lib/jar'); var jar = require('./lib/jar');
const Raven = require('raven-js'); var 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 @@ const 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}
*/ */
const updateLocale = () => { function updateLocale () {
let obj = jar.get('scratchlanguage'); var 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,9 +47,6 @@
"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 @@
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const xhr = require('xhr'); var xhr = require('xhr');
const jar = require('./jar'); var jar = require('./jar');
const log = require('./log'); var log = require('./log');
const urlParams = require('./url-params'); var urlParams = require('./url-params');
/** /**
* Helper method that constructs requests to the scratch api. * Helper method that constructs requests to the scratch api.
@ -12,11 +12,9 @@ const 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: {},
@ -42,29 +40,26 @@ module.exports = (opts, callback) => {
opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; opts.headers['Content-Type'] = 'application/x-www-form-urlencoded';
} }
const apiRequest = options => { var apiRequest = function (opts) {
if (options.host !== '') { if (opts.host !== '') {
if ('withCredentials' in new XMLHttpRequest()) { if ('withCredentials' in new XMLHttpRequest()) {
options.useXDR = false; opts.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.
options.useXDR = true; opts.useXDR = true;
delete options.headers; delete opts.headers;
if (options.authentication) { if (opts.authentication) {
const authenticationParams = [`x-token=${options.authentication}`]; var authenticationParams = ['x-token=' + opts.authentication];
const parts = options.uri.split('?'); var parts = opts.uri.split('?');
const qs = (parts[1] || '') var qs = (parts[1] || '').split('&').concat(authenticationParams).join('&');
.split('&') opts.uri = parts[0] + '?' + qs;
.concat(authenticationParams)
.join('&');
options.uri = `${parts[0]}?${qs}`;
} }
} }
} }
xhr(options, (err, res, body) => { xhr(opts, function (err, res, body) {
if (err) log.error(err); if (err) log.error(err);
if (options.responseType === 'json' && typeof body === 'string') { if (opts.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
@ -78,25 +73,25 @@ module.exports = (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 (e) { } catch (err) {
// 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/', (err, csrftoken) => { jar.use('scratchcsrftoken', '/csrf_token/', function (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,28 +1,22 @@
module.exports = {}; module.exports = {};
const countries = module.exports.data = require('iso-3166-2').data; var countries = module.exports.data = require('iso-3166-2').data;
module.exports.countryOptions = Object.keys(countries).map(code => ({ module.exports.countryOptions = Object.keys(countries).map(function (code) {
value: code.toLowerCase(), return {value: code.toLowerCase(), label: countries[code].name};
label: countries[code].name }).sort(function (a, b) {
})) return a.label < b.label ? -1 : 1;
.sort((a, b) => { });
if (a.label < b.label) {
return -1; module.exports.subdivisionOptions =
} Object.keys(countries).reduce(function (subByCountry, code) {
return 1; subByCountry[code.toLowerCase()] = Object.keys(countries[code].sub).map(function (subCode) {
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;
}, {}); }, {});

34
src/lib/format.js Normal file
View file

@ -0,0 +1,34 @@
/**
* 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,10 +1,9 @@
/* 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 @@
const requireAll = require('./require-all'); var requireAll = require('./require-all');
const ReactIntl = require('react-intl'); var ReactIntl = require('react-intl');
const allLocaleData = requireAll(require.context('react-intl/locale-data', true, /^\.\/.*\.js$/)); var allLocaleData = requireAll(require.context('react-intl/locale-data', true, /^\.\/.*\.js$/));
const customLocaleData = require('../../custom-locales.json'); var customLocaleData = require('../../custom-locales.json');
/** /**
* Add all locales * Add all locales
*/ */
for (const locale in allLocaleData) { for (var 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 (const customLocale in customLocaleData) { for (var customLocale in customLocaleData) {
ReactIntl.addLocaleData(customLocaleData[customLocale]); ReactIntl.addLocaleData(customLocaleData[customLocale]);
} }

View file

@ -1,7 +1,7 @@
const cookie = require('cookie'); var cookie = require('cookie');
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const xhr = require('xhr'); var xhr = require('xhr');
const pako = require('pako'); var pako = require('pako');
/** /**
* Module that handles coookie interactions. * Module that handles coookie interactions.
@ -11,16 +11,16 @@ const 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.
*/ */
const Jar = { var Jar = {
unsign: (value, callback) => { unsign: function (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 {
let b64Data = value.split(':')[0]; var b64Data = value.split(':')[0];
let decompress = false; var decompress = false;
if (b64Data[0] === '.') { if (b64Data[0] === '.') {
decompress = true; decompress = true;
b64Data = b64Data.substring(1); b64Data = b64Data.substring(1);
@ -29,29 +29,24 @@ const 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( b64Data = b64Data.replace(/[-_]/g, function (c) {return {'-':'+', '_':'/'}[c]; });
/[-_]/g, var strData = atob(b64Data);
c => ({
'-': '+',
'_': '/'
}[c])
);
let strData = atob(b64Data);
if (decompress) { if (decompress) {
const charData = strData.split('').map(c => (c.charCodeAt(0))); var charData = strData.split('').map(function (c) { return c.charCodeAt(0); });
const binData = new Uint8Array(charData); var binData = new Uint8Array(charData);
const data = pako.inflate(binData); var 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: (name, callback) => { get: function (name, callback) {
// Get cookie by name // Get cookie by name
const obj = cookie.parse(document.cookie) || {}; var obj = cookie.parse(document.cookie) || {};
// Handle optional callback // Handle optional callback
if (typeof callback === 'function') { if (typeof callback === 'function') {
@ -61,44 +56,44 @@ const Jar = {
return obj[name]; return obj[name];
}, },
use: (name, uri, callback) => { use: function (name, uri, callback) {
// Attempt to get cookie // Attempt to get cookie
Jar.get(name, (err, obj) => { Jar.get(name, function (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
}, e => { }, function (err) {
if (e) return callback(e); if (err) return callback(err);
Jar.get(name, callback); Jar.get(name, callback);
}); });
}); });
}, },
set: (name, value, opts) => { set: function (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 = '/';
const obj = cookie.serialize(name, value, opts); var obj = cookie.serialize(name, value, opts);
document.cookie = obj; document.cookie = obj;
}, },
getUnsignedValue: (cookieName, signedValue, callback) => { getUnsignedValue: function (cookieName, signedValue, callback) {
// Get a value from a signed object // Get a value from a signed object
Jar.get(cookieName, (err, value) => { Jar.get(cookieName, function (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, (e, contents) => { Jar.unsign(value, function (err, contents) {
if (e) return callback(e); if (err) return callback(err);
try { try {
const data = JSON.parse(contents); var data = JSON.parse(contents);
return callback(null, data[signedValue]); } catch (err) {
} catch (error) { return callback(err);
return callback(error);
} }
return callback(null, data[signedValue]);
}); });
}); });
} }

View file

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

View file

@ -1,27 +1,21 @@
const redux = require('redux'); var redux = require('redux');
const thunk = require('redux-thunk').default; var thunk = require('redux-thunk').default;
// JSX syntax transforms to React.createElement // JSX syntax transforms to React.createElement
const React = require('react'); // eslint-disable-line var React = require('react'); // eslint-disable-line
const ReactDOM = require('react-dom'); var ReactDOM = require('react-dom');
const StoreProvider = require('react-redux').Provider; var StoreProvider = require('react-redux').Provider;
const IntlProvider = require('./intl.jsx').IntlProvider; var IntlProvider = require('./intl.jsx').IntlProvider;
const permissionsActions = require('../redux/permissions.js'); var permissionsActions = require('../redux/permissions.js');
const sessionActions = require('../redux/session.js'); var sessionActions = require('../redux/session.js');
const reducer = require('../redux/reducer.js'); var 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")
let locale = window._locale || 'en'; var locale = window._locale || 'en';
let messages = {}; var 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
@ -34,8 +28,8 @@ const render = (jsx, element, reducers) => {
messages = window._messages[locale]; messages = window._messages[locale];
} }
const allReducers = reducer(reducers); var allReducers = reducer(reducers);
const store = redux.createStore( var store = redux.createStore(
allReducers, allReducers,
redux.applyMiddleware(thunk) redux.applyMiddleware(thunk)
); );
@ -43,10 +37,7 @@ const render = (jsx, element, reducers) => {
// Render view component // Render view component
ReactDOM.render( ReactDOM.render(
<StoreProvider store={store}> <StoreProvider store={store}>
<IntlProvider <IntlProvider locale={locale} messages={messages}>
locale={locale}
messages={messages}
>
{jsx} {jsx}
</IntlProvider> </IntlProvider>
</StoreProvider>, </StoreProvider>,

View file

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

View file

@ -1,19 +1,21 @@
/* /*
* Function that shuffles an array using a Fisher-Yates shuffle. * Function that shuffles an array using a Fisher-Yates shuffle.
*/ */
module.exports.shuffle = arr => {
let i = 0; module.exports.shuffle = function (arr) {
let j = 0; var i, j = 0;
let temp = null; var temp = null;
if (arr) { if (arr) {
const tempArray = arr.slice(0); var tempArray = arr.slice(0);
for (i = arr.length - 1; i > 0; i -= 1) { } else {
j = Math.floor(Math.random() * (i + 1)); return arr;
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,18 +2,21 @@
* 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
*/ */
const params = values => ( module.exports = function urlParams (values) {
Object.keys(values).map(key => { return Object
const value = typeof values[key] === 'undefined' ? '' : values[key]; .keys(values)
const encodeKeyValuePair = val => ( .map(function (key) {
[key, val].map(encodeURIComponent).join('=') var value = typeof values[key] === 'undefined' ? '' : values[key];
); function encodeKeyValuePair (value) {
if (Array.isArray(value)) { return [key, value]
return value.map(encodeKeyValuePair).join('&'); .map(encodeURIComponent)
} .join('=');
return encodeKeyValuePair(value); }
}) if (Array.isArray(value)) {
.join('&') return value.map(encodeKeyValuePair).join('&');
); } else {
return encodeKeyValuePair(value);
module.exports = params; }
})
.join('&');
};

View file

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

View file

@ -1,13 +1,13 @@
const keyMirror = require('keymirror'); var keyMirror = require('keymirror');
const api = require('../lib/api'); var api = require('../lib/api');
const Types = keyMirror({ var 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 = (state, action) => { module.exports.scheduleReducer = function (state, action) {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = { state = {
timeSlots: [], timeSlots: [],
@ -26,27 +26,40 @@ module.exports.scheduleReducer = (state, action) => {
} }
}; };
module.exports.setSchedule = schedule => ({ module.exports.setSchedule = function (schedule) {
type: Types.SET_SCHEDULE, return {
schedule: schedule type: Types.SET_SCHEDULE,
}); schedule: schedule
};
};
module.exports.setScheduleFetching = () => ({ module.exports.setScheduleFetching = function () {
type: Types.SET_SCHEDULE_FETCHING, return {
fetching: true type: Types.SET_SCHEDULE_FETCHING,
}); fetching: true
};
};
module.exports.setScheduleError = error => ({ module.exports.setScheduleError = function (error) {
type: Types.SET_SCHEDULE_ERROR, return {
error: error type: Types.SET_SCHEDULE_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 = (timeSlot1, timeSlot2) => { module.exports.sortTimeSlots = function (timeSlot1, timeSlot2) {
const timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a'); var timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a') ? true : false;
const timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a'); var timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a') ? true : false;
let timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1), 10); var timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1));
let timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1), 10); var timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1));
// convert to 24-hour for sorting // convert to 24-hour for sorting
if (timeSlot1Time !== 12 && !timeSlot1Am) { if (timeSlot1Time !== 12 && !timeSlot1Am) {
@ -58,67 +71,68 @@ module.exports.sortTimeSlots = (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 = day => (dispatch => { module.exports.getDaySchedule = function (day) {
api({ return function (dispatch) {
uri: `/conference/schedule/${day}` api({
}, (err, body) => { uri: '/conference/schedule/' + day
if (err) { }, function (err, body) {
dispatch(module.exports.setScheduleError(err)); if (err) {
return; dispatch(module.exports.setScheduleError(err));
} return;
}
if (typeof body !== 'undefined') { if (typeof body !== 'undefined') {
const columns = body.columns; var columns = body.columns;
const rows = body.rows || []; var 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)
const scheduleByTimeSlot = rows.reduce((prev, cur) => { var scheduleByTimeSlot = rows.reduce(function (prev, cur) {
const cleanedRow = {}; var cleanedRow = {};
for (let i = 0; i < columns.length; i++) { for (var 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';
cleanedRow.uri = `/conference/2016/${cleanedRow.rowid}/details`; var timeSlot = cleanedRow.Chunk + cleanedRow.Start;
const timeSlot = cleanedRow.Chunk + cleanedRow.Start; if (typeof prev.timeSlots[timeSlot] === 'undefined') {
if (typeof prev.timeSlots[timeSlot] === 'undefined') { prev.timeSlots[timeSlot] = [cleanedRow];
prev.timeSlots[timeSlot] = [cleanedRow]; prev.info.push({
prev.info.push({ name: cleanedRow.Chunk,
name: cleanedRow.Chunk, time: cleanedRow.Start
time: cleanedRow.Start });
}); } else {
} else { prev.timeSlots[timeSlot].push(cleanedRow);
prev.timeSlots[timeSlot].push(cleanedRow); }
} return prev;
return prev; }, {timeSlots: [], info: []});
}, {timeSlots: [], info: []});
scheduleByTimeSlot.info.sort(module.exports.sortTimeSlots); scheduleByTimeSlot.info.sort(module.exports.sortTimeSlots);
const schedule = scheduleByTimeSlot.info.map(timeSlot => ({ var schedule = scheduleByTimeSlot.info.map(function (timeSlot) {
info: timeSlot, return {
items: scheduleByTimeSlot.timeSlots[timeSlot.name + timeSlot.time] info: timeSlot,
})); items: scheduleByTimeSlot.timeSlots[timeSlot.name + timeSlot.time]
dispatch(module.exports.setSchedule({ };
timeSlots: schedule, });
day: day dispatch(module.exports.setSchedule({
})); timeSlots: schedule,
return; day: day
} }));
dispatch(module.exports.setScheduleError('An unexpected error occurred')); return;
return; } else {
}); 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 @@
const keyMirror = require('keymirror'); var keyMirror = require('keymirror');
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const api = require('../lib/api'); var api = require('../lib/api');
const Types = keyMirror({ var 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
}); });
const getInitialState = () => ({ module.exports.getInitialState = function (){
messageCount: 0 return {messageCount: 0};
}); };
module.exports.messageCountReducer = (state, action) => { module.exports.messageCountReducer = function (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 = getInitialState(); state = module.exports.getInitialState();
} }
switch (action.type) { switch (action.type) {
case Types.SET_MESSAGE_COUNT: case Types.SET_MESSAGE_COUNT:
@ -31,32 +31,40 @@ module.exports.messageCountReducer = (state, action) => {
} }
}; };
module.exports.setSessionError = error => ({ module.exports.setSessionError = function (error) {
type: Types.SET_MESSAGE_COUNT_ERROR, return {
error: error type: Types.SET_MESSAGE_COUNT_ERROR,
}); error: error
};
};
module.exports.setCount = count => ({ module.exports.setCount = function (count) {
type: Types.SET_MESSAGE_COUNT, return {
count: count type: Types.SET_MESSAGE_COUNT,
}); count: count
};
};
module.exports.setStatus = status => ({ module.exports.setStatus = function (status){
type: Types.SET_STATUS, return {
status: status type: Types.SET_STATUS,
}); status: status
};
};
module.exports.getCount = username => (dispatch => { module.exports.getCount = function (username) {
api({ return function (dispatch) {
method: 'get', api({
uri: `/users/${username}/messages/count` method: 'get',
}, (err, body) => { uri: '/users/' + username + '/messages/count'
if (err) { }, function (err, body) {
dispatch(module.exports.setCount(0)); if (err) {
dispatch(module.exports.setSessionError(err)); dispatch(module.exports.setCount(0));
return; dispatch(module.exports.setSessionError(err));
} return;
const count = parseInt(body.count, 10); }
dispatch(module.exports.setCount(count)); var count = parseInt(body.count, 10);
}); dispatch(module.exports.setCount(count));
}); });
};
};

View file

@ -1,10 +1,10 @@
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const defaultsDeep = require('lodash.defaultsdeep'); var defaultsDeep = require('lodash.defaultsdeep');
const keyMirror = require('keymirror'); var keyMirror = require('keymirror');
const api = require('../lib/api'); var api = require('../lib/api');
const log = require('../lib/log'); var log = require('../lib/log');
const messageCountActions = require('./message-count.js'); var messageCountActions = require('./message-count.js');
module.exports.Status = keyMirror({ module.exports.Status = keyMirror({
FETCHED: null, FETCHED: null,
@ -17,21 +17,23 @@ module.exports.Status = keyMirror({
DELETE_ERROR: null DELETE_ERROR: null
}); });
module.exports.getInitialState = () => ({ module.exports.getInitialState = function () {
status: { return {
admin: module.exports.Status.NOT_FETCHED, status: {
message: module.exports.Status.NOT_FETCHED, admin: module.exports.Status.NOT_FETCHED,
clear: module.exports.Status.NOT_FETCHED, message: module.exports.Status.NOT_FETCHED,
delete: module.exports.Status.NOT_FETCHED clear: module.exports.Status.NOT_FETCHED,
}, delete: module.exports.Status.NOT_FETCHED
messages: { },
admin: [], messages: {
social: [], admin: [],
invite: {} social: [],
} invite: {}
}); }
};
};
module.exports.messagesReducer = (state, action) => { module.exports.messagesReducer = function (state, action) {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = module.exports.getInitialState(); state = module.exports.getInitialState();
} }
@ -66,61 +68,75 @@ module.exports.messagesReducer = (state, action) => {
} }
}; };
module.exports.setMessagesError = error => ({ module.exports.setMessagesError = function (error) {
type: 'ERROR', return {
error: error type: 'ERROR',
}); error: error
};
};
module.exports.setMessages = messages => ({ module.exports.setMessages = function (messages) {
type: 'SET_MESSAGES', return {
messages: messages type: 'SET_MESSAGES',
}); messages: messages
};
};
module.exports.setMessagesOffset = offset => ({ module.exports.setMessagesOffset = function (offset) {
type: 'SET_MESSAGES_OFFSET', return {
offset: offset type: 'SET_MESSAGES_OFFSET',
}); offset: offset
};
};
module.exports.setAdminMessages = messages => ({ module.exports.setAdminMessages = function (messages) {
type: 'SET_ADMIN_MESSAGES', return {
messages: messages type: 'SET_ADMIN_MESSAGES',
}); messages: messages
};
};
module.exports.setScratcherInvite = invite => ({ module.exports.setScratcherInvite = function (invite) {
type: 'SET_SCRATCHER_INVITE', return {
invite: invite type: 'SET_SCRATCHER_INVITE',
}); invite: invite
};
};
module.exports.setStatus = (type, status) => ({ module.exports.setStatus = function (type, status){
type: type, return {
status: status type: type,
}); 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 = () => (dispatch => { module.exports.clearMessageCount = function () {
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING)); return function (dispatch) {
api({ dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING));
host: '', api({
uri: '/site-api/messages/messages-clear/', host: '',
method: 'POST', uri: '/site-api/messages/messages-clear/',
useCsrf: true method: 'POST',
}, (err, body) => { useCsrf: true
if (err) { }, function (err, body) {
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR)); if (err) {
dispatch(module.exports.setMessagesError(err)); dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR));
return; dispatch(module.exports.setMessagesError(err));
} return;
if (typeof body !== 'undefined' && !body.success) { }
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR)); if (typeof body !== 'undefined' && !body.success) {
dispatch(module.exports.setMessagesError('messages not cleared')); dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR));
return; dispatch(module.exports.setMessagesError('messages not cleared'));
} 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
@ -130,8 +146,8 @@ module.exports.clearMessageCount = () => (dispatch => {
* @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 = (messageType, messageId, messageCount, adminMessages) => ( module.exports.clearAdminMessage = function (messageType, messageId, messageCount, adminMessages) {
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: '',
@ -142,7 +158,7 @@ module.exports.clearAdminMessage = (messageType, messageId, messageCount, adminM
alertType: messageType, alertType: messageType,
alertId: messageId alertId: messageId
} }
}, (err, body) => { }, function (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));
@ -159,9 +175,9 @@ module.exports.clearAdminMessage = (messageType, messageId, messageCount, adminM
dispatch(module.exports.setScratcherInvite({})); dispatch(module.exports.setScratcherInvite({}));
} else { } else {
// find the admin message and remove it // find the admin message and remove it
let toRemove = -1; var toRemove = -1;
for (const i of adminMessages) { for (var i in adminMessages) {
if (i.id === messageId) { if (adminMessages[i].id === messageId) {
toRemove = i; toRemove = i;
break; break;
} }
@ -172,8 +188,8 @@ module.exports.clearAdminMessage = (messageType, messageId, messageCount, adminM
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
@ -185,7 +201,7 @@ module.exports.clearAdminMessage = (messageType, messageId, messageCount, adminM
* @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 = (username, token, opts) => { module.exports.getMessages = function (username, token, opts) {
opts = defaults(opts, { opts = defaults(opts, {
messages: [], messages: [],
offset: 0, offset: 0,
@ -193,17 +209,17 @@ module.exports.getMessages = (username, token, opts) => {
clearCount: true clearCount: true
}); });
let filterArg = ''; var filterArg = '';
if (opts.filter.length > 0) { if (opts.filter.length > 0) {
filterArg = `&filter=${opts.filter}`; filterArg = '&filter=' + opts.filter;
} }
return dispatch => { return function (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
}, (err, body) => { }, function (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));
@ -230,28 +246,30 @@ module.exports.getMessages = (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 = (username, token) => (dispatch => { module.exports.getAdminMessages = function (username, token) {
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHING)); return function (dispatch) {
api({ dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHING));
uri: `/users/${username}/messages/admin`, api({
authentication: token uri: '/users/' + username + '/messages/admin',
}, (err, body) => { authentication: token
if (err) { }, function (err, body) {
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR)); if (err) {
dispatch(module.exports.setMessagesError(err)); dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR));
dispatch(module.exports.setAdminMessages([])); dispatch(module.exports.setMessagesError(err));
return; dispatch(module.exports.setAdminMessages([]));
} return;
if (typeof body === 'undefined') { }
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR)); if (typeof body === 'undefined') {
dispatch(module.exports.setMessagesError('No session content')); dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR));
dispatch(module.exports.setAdminMessages([])); dispatch(module.exports.setMessagesError('No session content'));
return; dispatch(module.exports.setAdminMessages([]));
} return;
dispatch(module.exports.setAdminMessages(body)); }
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHED)); dispatch(module.exports.setAdminMessages(body));
}); 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
@ -259,18 +277,20 @@ module.exports.getAdminMessages = (username, token) => (dispatch => {
* @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 = (username, token) => (dispatch => { module.exports.getScratcherInvite = function (username, token) {
api({ return function (dispatch) {
uri: `/users/${username}/invites`, api({
authentication: token uri: '/users/' + username + '/invites',
}, (err, body) => { authentication: token
if (err) { }, function (err, body) {
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.INVITE_ERROR)); if (err) {
dispatch(module.exports.setMessagesError(err)); dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.INVITE_ERROR));
dispatch(module.exports.setScratcherInvite({})); dispatch(module.exports.setMessagesError(err));
return; dispatch(module.exports.setScratcherInvite({}));
} return;
if (typeof body === 'undefined') return dispatch(module.exports.setMessagesError('No session content')); }
dispatch(module.exports.setScratcherInvite(body)); if (typeof body === 'undefined') return dispatch(module.exports.setMessagesError('No session content'));
}); dispatch(module.exports.setScratcherInvite(body));
}); });
};
};

View file

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

View file

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

View file

@ -1,9 +1,9 @@
const combineReducers = require('redux').combineReducers; var combineReducers = require('redux').combineReducers;
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const messageCountReducer = require('./message-count.js').messageCountReducer; var messageCountReducer = require('./message-count.js').messageCountReducer;
const permissionsReducer = require('./permissions.js').permissionsReducer; var permissionsReducer = require('./permissions.js').permissionsReducer;
const sessionReducer = require('./session.js').sessionReducer; var 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 @@ const 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 = opts => { module.exports = function (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