mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 07:38:07 -05:00
Merge pull request #1789 from LLK/hotfix/es6-upgrade
[Master] Upgrade to ES6, Take II
This commit is contained in:
commit
51533bbf39
188 changed files with 13697 additions and 11511 deletions
3
.babelrc
Normal file
3
.babelrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": ["es2015", "react"],
|
||||
}
|
29
.eslintrc
29
.eslintrc
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"curly": [2, "multi-line"],
|
||||
"eol-last": [2],
|
||||
"indent": [2, 4],
|
||||
"linebreak-style": [2, "unix"],
|
||||
"max-len": [2, 120, 4, {"ignoreUrls": true}],
|
||||
"no-trailing-spaces": [2, { "skipBlankLines": true }],
|
||||
"no-unused-vars": [2, {"args": "after-used", "varsIgnorePattern": "^_"}],
|
||||
"quotes": [2, "single"],
|
||||
"semi": [2, "always"],
|
||||
"space-before-function-paren": [2, "always"],
|
||||
"strict": [2, "never"]
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"globals": {
|
||||
"formatMessage": true
|
||||
},
|
||||
"plugins": [
|
||||
"react",
|
||||
"json"
|
||||
],
|
||||
"extends": "eslint:recommended"
|
||||
}
|
4
.eslintrc.js
Normal file
4
.eslintrc.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
extends: ['scratch', 'scratch/node'],
|
||||
plugins: ['json']
|
||||
};
|
|
@ -133,3 +133,9 @@ file_filter = localizations/conference-index/<lang>.json
|
|||
source_file = src/views/conference/2018/index/l10n.json
|
||||
source_lang = en
|
||||
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
|
|
@ -3,7 +3,7 @@ var defaults = require('lodash.defaults');
|
|||
var fastlyConfig = require('./lib/fastly-config-methods');
|
||||
const languages = require('../languages.json');
|
||||
|
||||
var route_json = require('../src/routes.json');
|
||||
var routeJson = require('../src/routes.json');
|
||||
|
||||
const FASTLY_SERVICE_ID = process.env.FASTLY_SERVICE_ID || '';
|
||||
const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME || '';
|
||||
|
@ -15,10 +15,10 @@ var extraAppRoutes = [
|
|||
// TODO: Should this be added for every route?
|
||||
'/\\?',
|
||||
// View html
|
||||
'/[^\/]*\.html$'
|
||||
'/[^/]*.html$'
|
||||
];
|
||||
|
||||
var routes = route_json.map(function (route) {
|
||||
var routes = routeJson.map(function (route) {
|
||||
return defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route);
|
||||
});
|
||||
|
||||
|
@ -28,9 +28,9 @@ async.auto({
|
|||
if (err) return cb(err);
|
||||
// Validate latest version before continuing
|
||||
if (response.active || response.locked) {
|
||||
fastly.cloneVersion(response.number, function (err, response) {
|
||||
if (err) return cb('Failed to clone latest version: ' + err);
|
||||
cb(null, response.number);
|
||||
fastly.cloneVersion(response.number, function (e, resp) {
|
||||
if (e) return cb('Failed to clone latest version: ' + e);
|
||||
cb(null, resp.number);
|
||||
});
|
||||
} else {
|
||||
cb(null, response.number);
|
||||
|
@ -46,11 +46,11 @@ async.auto({
|
|||
var recvCondition = '' +
|
||||
'if (' + notPassStatement + ') {\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' +
|
||||
' if (!req.http.Fastly-FF) {\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' +
|
||||
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
|
||||
' }\n' +
|
||||
|
@ -171,20 +171,19 @@ async.auto({
|
|||
if (err) return cb(err);
|
||||
cb(null, headers);
|
||||
});
|
||||
}]},
|
||||
function (err, results) {
|
||||
if (err) throw new Error(err);
|
||||
if (process.env.FASTLY_ACTIVATE_CHANGES) {
|
||||
fastly.activateVersion(results.version, function (err, response) {
|
||||
if (err) throw new Error(err);
|
||||
process.stdout.write('Successfully configured and activated version ' + response.number + '\n');
|
||||
if (process.env.FASTLY_PURGE_ALL) {
|
||||
fastly.purgeAll(FASTLY_SERVICE_ID, function (err) {
|
||||
if (err) throw new Error(err);
|
||||
process.stdout.write('Purged all.\n');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}]
|
||||
}, function (err, results) {
|
||||
if (err) throw new Error(err);
|
||||
if (process.env.FASTLY_ACTIVATE_CHANGES) {
|
||||
fastly.activateVersion(results.version, function (e, resp) {
|
||||
if (err) throw new Error(e);
|
||||
process.stdout.write('Successfully configured and activated version ' + resp.number + '\n');
|
||||
if (process.env.FASTLY_PURGE_ALL) {
|
||||
fastly.purgeAll(FASTLY_SERVICE_ID, function (error) {
|
||||
if (error) throw new Error(error);
|
||||
process.stdout.write('Purged all.\n');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -24,9 +24,9 @@ var FastlyConfigMethods = {
|
|||
*/
|
||||
getViewPaths: function (routes) {
|
||||
return routes.reduce(function (paths, route) {
|
||||
var path = route.routeAlias || route.pattern;
|
||||
if (paths.indexOf(path) === -1) {
|
||||
paths.push(path);
|
||||
var p = route.routeAlias || route.pattern;
|
||||
if (paths.indexOf(p) === -1) {
|
||||
paths.push(p);
|
||||
}
|
||||
return paths;
|
||||
}, []);
|
||||
|
@ -39,7 +39,7 @@ var FastlyConfigMethods = {
|
|||
* 2. /path/:arg([regex]) – :arg is removed, leaving just /path/([regex])
|
||||
*/
|
||||
expressPatternToRegex: function (pattern) {
|
||||
pattern = pattern.replace(/(:\w+)(\([^\)]+\))/gi, '$2');
|
||||
pattern = pattern.replace(/(:\w+)(\([^)]+\))/gi, '$2');
|
||||
return pattern.replace(/(:\w+)/gi, '.+?');
|
||||
},
|
||||
|
||||
|
@ -84,7 +84,7 @@ var FastlyConfigMethods = {
|
|||
return 'redirects/' + route.pattern;
|
||||
},
|
||||
|
||||
/**
|
||||
/*
|
||||
* Returns custom vcl configuration as a string that sets the varnish
|
||||
* Time to Live (TTL) for responses that come from s3.
|
||||
*
|
||||
|
|
|
@ -19,8 +19,8 @@ module.exports = function (apiKey, serviceId) {
|
|||
*
|
||||
* @return {string}
|
||||
*/
|
||||
fastly.getFastlyAPIPrefix = function (serviceId, version) {
|
||||
return '/service/' + encodeURIComponent(serviceId) + '/version/' + version;
|
||||
fastly.getFastlyAPIPrefix = function (servId, version) {
|
||||
return '/service/' + encodeURIComponent(servId) + '/version/' + version;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -32,15 +32,15 @@ module.exports = function (apiKey, serviceId) {
|
|||
if (!this.serviceId) {
|
||||
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) {
|
||||
if (err) {
|
||||
return cb('Failed to fetch versions: ' + err);
|
||||
}
|
||||
var latestVersion = versions.reduce(function (latestVersion, version) {
|
||||
if (!latestVersion) return version;
|
||||
if (version.number > latestVersion.number) return version;
|
||||
return latestVersion;
|
||||
var latestVersion = versions.reduce(function (lateVersion, version) {
|
||||
if (!lateVersion) return version;
|
||||
if (version.number > lateVersion.number) return version;
|
||||
return lateVersion;
|
||||
});
|
||||
return cb(null, latestVersion);
|
||||
});
|
||||
|
@ -63,16 +63,16 @@ module.exports = function (apiKey, serviceId) {
|
|||
var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/condition';
|
||||
return this.request('PUT', putUrl, condition, function (err, response) {
|
||||
if (err && err.statusCode === 404) {
|
||||
this.request('POST', postUrl, condition, function (err, response) {
|
||||
if (err) {
|
||||
return cb('Failed while inserting condition \"' + condition.statement + '\": ' + err);
|
||||
this.request('POST', postUrl, condition, function (e, resp) {
|
||||
if (e) {
|
||||
return cb('Failed while inserting condition "' + condition.statement + '": ' + e);
|
||||
}
|
||||
return cb(null, response);
|
||||
return cb(null, resp);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (err) {
|
||||
return cb('Failed to update condition \"' + condition.statement + '\": ' + err);
|
||||
return cb('Failed to update condition "' + condition.statement + '": ' + err);
|
||||
}
|
||||
return cb(null, response);
|
||||
}.bind(this));
|
||||
|
@ -95,11 +95,11 @@ module.exports = function (apiKey, serviceId) {
|
|||
var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/header';
|
||||
return this.request('PUT', putUrl, header, function (err, response) {
|
||||
if (err && err.statusCode === 404) {
|
||||
this.request('POST', postUrl, header, function (err, response) {
|
||||
if (err) {
|
||||
return cb('Failed to insert header: ' + err);
|
||||
this.request('POST', postUrl, header, function (e, resp) {
|
||||
if (e) {
|
||||
return cb('Failed to insert header: ' + e);
|
||||
}
|
||||
return cb(null, response);
|
||||
return cb(null, resp);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -127,11 +127,11 @@ module.exports = function (apiKey, serviceId) {
|
|||
var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/response_object';
|
||||
return this.request('PUT', putUrl, responseObj, function (err, response) {
|
||||
if (err && err.statusCode === 404) {
|
||||
this.request('POST', postUrl, responseObj, function (err, response) {
|
||||
if (err) {
|
||||
return cb('Failed to insert response object: ' + err);
|
||||
this.request('POST', postUrl, responseObj, function (e, resp) {
|
||||
if (e) {
|
||||
return cb('Failed to insert response object: ' + e);
|
||||
}
|
||||
return cb(null, response);
|
||||
return cb(null, resp);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ module.exports = function (apiKey, serviceId) {
|
|||
this.request('PUT', url, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
/*
|
||||
* Upsert a custom vcl file. Attempts a PUT, and falls back
|
||||
* to POST if not there already.
|
||||
*
|
||||
|
@ -186,16 +186,16 @@ module.exports = function (apiKey, serviceId) {
|
|||
return this.request('PUT', url, content, function (err, response) {
|
||||
if (err && err.statusCode === 404) {
|
||||
content.name = name;
|
||||
this.request('POST', postUrl, content, function (err, response) {
|
||||
if (err) {
|
||||
return cb('Failed while adding custom vcl \"' + name + '\": ' + err);
|
||||
this.request('POST', postUrl, content, function (e, resp) {
|
||||
if (e) {
|
||||
return cb('Failed while adding custom vcl "' + name + '": ' + e);
|
||||
}
|
||||
return cb(null, response);
|
||||
return cb(null, resp);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (err) {
|
||||
return cb('Failed to update custom vcl \"' + name + '\": ' + err);
|
||||
return cb('Failed to update custom vcl "' + name + '": ' + err);
|
||||
}
|
||||
return cb(null, response);
|
||||
}.bind(this));
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
/**
|
||||
/*
|
||||
* Constructor
|
||||
*/
|
||||
function Handler (route) {
|
||||
const Handler = function (route) {
|
||||
// Handle redirects
|
||||
if (route.redirect) {
|
||||
return (req, res) => { res.redirect(route.redirect); };
|
||||
return (req, res) => {
|
||||
res.redirect(route.redirect);
|
||||
};
|
||||
}
|
||||
|
||||
var url = '/' + route.name + '.html';
|
||||
|
@ -12,9 +14,9 @@ function Handler (route) {
|
|||
req.url = url;
|
||||
next();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
/*
|
||||
* Export a new instance
|
||||
*/
|
||||
module.exports = function (route) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"ab": "Аҧсшәа",
|
||||
"ar": "العربية",
|
||||
"an": "Aragonés",
|
||||
"ast": "Asturianu",
|
||||
|
|
45
package.json
45
package.json
|
@ -33,24 +33,27 @@
|
|||
"devDependencies": {
|
||||
"async": "1.5.2",
|
||||
"autoprefixer": "6.3.6",
|
||||
"babel-core": "6.10.4",
|
||||
"babel-eslint": "5.0.4",
|
||||
"babel-loader": "6.2.4",
|
||||
"babel-preset-es2015": "6.9.0",
|
||||
"babel-preset-react": "6.11.1",
|
||||
"babel-cli": "6.26.0",
|
||||
"babel-core": "6.23.1",
|
||||
"babel-eslint": "8.0.2",
|
||||
"babel-loader": "7.1.0",
|
||||
"babel-preset-es2015": "6.22.0",
|
||||
"babel-preset-react": "6.22.0",
|
||||
"cheerio": "1.0.0-rc.2",
|
||||
"classnames": "2.1.3",
|
||||
"classnames": "2.2.5",
|
||||
"cookie": "0.2.2",
|
||||
"copy-webpack-plugin": "0.2.0",
|
||||
"create-react-class": "15.6.2",
|
||||
"css-loader": "0.23.1",
|
||||
"eslint": "1.3.1",
|
||||
"eslint": "4.7.1",
|
||||
"eslint-config-scratch": "5.0.0",
|
||||
"eslint-plugin-json": "1.2.0",
|
||||
"eslint-plugin-react": "3.3.1",
|
||||
"eslint-plugin-react": "7.4.0",
|
||||
"exenv": "1.2.0",
|
||||
"fastly": "1.2.1",
|
||||
"file-loader": "0.8.4",
|
||||
"formsy-react": "0.18.0",
|
||||
"formsy-react-components": "0.7.1",
|
||||
"formsy-react": "0.19.5",
|
||||
"formsy-react-components": "0.11.1",
|
||||
"git-bundle-sha": "0.0.2",
|
||||
"glob": "5.0.15",
|
||||
"google-libphonenumber": "1.0.21",
|
||||
|
@ -59,6 +62,7 @@
|
|||
"json-loader": "0.5.2",
|
||||
"json2po-stream": "1.0.3",
|
||||
"keymirror": "0.1.1",
|
||||
"lodash.bindall": "4.4.0",
|
||||
"lodash.clone": "3.0.3",
|
||||
"lodash.defaultsdeep": "3.10.0",
|
||||
"lodash.isarray": "3.0.4",
|
||||
|
@ -70,21 +74,22 @@
|
|||
"node-sass": "4.6.1",
|
||||
"pako": "0.2.8",
|
||||
"po2icu": "0.0.2",
|
||||
"postcss-loader": "0.8.2",
|
||||
"postcss-loader": "2.0.10",
|
||||
"prop-types": "15.6.0",
|
||||
"raven-js": "3.0.4",
|
||||
"react": "15.1.0",
|
||||
"react-dom": "15.0.1",
|
||||
"react": "15.5.4",
|
||||
"react-dom": "15.5.4",
|
||||
"react-intl": "2.1.2",
|
||||
"react-modal": "1.5.2",
|
||||
"react-onclickoutside": "4.1.1",
|
||||
"react-modal": "3.1.11",
|
||||
"react-onclickoutside": "6.7.1",
|
||||
"react-redux": "4.4.5",
|
||||
"react-responsive": "1.1.4",
|
||||
"react-responsive": "3.0.0",
|
||||
"react-slick": "0.12.2",
|
||||
"react-telephone-input": "3.4.5",
|
||||
"react-telephone-input": "3.8.6",
|
||||
"redux": "3.5.2",
|
||||
"redux-thunk": "2.0.1",
|
||||
"sass-lint": "1.5.1",
|
||||
"sass-loader": "2.0.1",
|
||||
"sass-loader": "6.0.6",
|
||||
"scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master",
|
||||
"slick-carousel": "1.5.8",
|
||||
"source-map-support": "0.3.2",
|
||||
|
@ -92,8 +97,8 @@
|
|||
"tap": "7.1.2",
|
||||
"url-loader": "0.5.6",
|
||||
"watch": "0.16.0",
|
||||
"webpack": "1.12.14",
|
||||
"webpack-dev-middleware": "1.2.0",
|
||||
"webpack": "2.7.0",
|
||||
"webpack-dev-middleware": "2.0.4",
|
||||
"xhr": "2.2.0"
|
||||
},
|
||||
"nyc": {
|
||||
|
|
11
src/.eslintrc.js
Normal file
11
src/.eslintrc.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
extends: ['scratch', 'scratch/es6', 'scratch/react'],
|
||||
env: {
|
||||
browser: true
|
||||
},
|
||||
globals: {
|
||||
process: true
|
||||
},
|
||||
plugins: ['json']
|
||||
};
|
|
@ -1,33 +1,35 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const bindAll = require('lodash.bindall');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./accordion.scss');
|
||||
|
||||
var Accordion = React.createClass({
|
||||
type: 'Accordion',
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
titleAs: 'div',
|
||||
contentAs: 'div'
|
||||
};
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
class Accordion extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleClick'
|
||||
]);
|
||||
this.state = {
|
||||
isOpen: false
|
||||
};
|
||||
},
|
||||
toggleContent: function () {
|
||||
}
|
||||
handleClick (e) {
|
||||
e.preventDefault();
|
||||
this.setState({isOpen: !this.state.isOpen});
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames({
|
||||
'content': true,
|
||||
'open': this.state.isOpen
|
||||
}
|
||||
render () {
|
||||
const classes = classNames({
|
||||
content: true,
|
||||
open: this.state.isOpen
|
||||
});
|
||||
return (
|
||||
<div className="accordion">
|
||||
<this.props.titleAs className="title"
|
||||
onClick={this.toggleContent}>
|
||||
<this.props.titleAs
|
||||
className="title"
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
{this.props.title}
|
||||
</this.props.titleAs>
|
||||
<this.props.contentAs className={classes}>
|
||||
|
@ -36,6 +38,16 @@ var Accordion = React.createClass({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Accordion.propTypes = {
|
||||
content: PropTypes.node,
|
||||
title: PropTypes.string
|
||||
};
|
||||
|
||||
Accordion.defaultProps = {
|
||||
contentAs: 'div',
|
||||
titleAs: 'div'
|
||||
};
|
||||
|
||||
module.exports = Accordion;
|
||||
|
|
|
@ -1,37 +1,39 @@
|
|||
var React = require('react');
|
||||
var connect = require('react-redux').connect;
|
||||
const bindAll = require('lodash.bindall');
|
||||
const connect = require('react-redux').connect;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var Button = require('../forms/button.jsx');
|
||||
const Button = require('../forms/button.jsx');
|
||||
|
||||
require('./adminpanel.scss');
|
||||
|
||||
var AdminPanel = React.createClass({
|
||||
type: 'AdminPanel',
|
||||
getInitialState: function () {
|
||||
return {
|
||||
class AdminPanel extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleToggleVisibility'
|
||||
]);
|
||||
this.state = {
|
||||
showPanel: false
|
||||
};
|
||||
},
|
||||
handleToggleVisibility: function (e) {
|
||||
}
|
||||
handleToggleVisibility (e) {
|
||||
e.preventDefault();
|
||||
this.setState({showPanel: !this.state.showPanel});
|
||||
},
|
||||
render: function () {
|
||||
// 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;
|
||||
}
|
||||
render () {
|
||||
if (!this.props.isAdmin) return false;
|
||||
|
||||
if (this.state.showPanel) {
|
||||
return (
|
||||
<div id="admin-panel" className="visible">
|
||||
<div
|
||||
className="visible"
|
||||
id="admin-panel"
|
||||
>
|
||||
<span
|
||||
className="toggle"
|
||||
onClick={this.handleToggleVisibility}>
|
||||
|
||||
onClick={this.handleToggleVisibility}
|
||||
>
|
||||
x
|
||||
</span>
|
||||
<div className="admin-header">
|
||||
|
@ -44,8 +46,15 @@ var AdminPanel = React.createClass({
|
|||
<dd>
|
||||
<ul className="cache-list">
|
||||
<li>
|
||||
<form method="post" action="/scratch_admin/page/clear-anon-cache/">
|
||||
<input type="hidden" name="path" value="/" />
|
||||
<form
|
||||
action="/scratch_admin/page/clear-anon-cache/"
|
||||
method="post"
|
||||
>
|
||||
<input
|
||||
name="path"
|
||||
type="hidden"
|
||||
value="/"
|
||||
/>
|
||||
<div className="button-row">
|
||||
<span>For anonymous users:</span>
|
||||
<Button type="submit">
|
||||
|
@ -53,34 +62,39 @@ var AdminPanel = React.createClass({
|
|||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div id="admin-panel" className="hidden">
|
||||
<span
|
||||
className="toggle"
|
||||
onClick={this.handleToggleVisibility}>
|
||||
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="hidden"
|
||||
id="admin-panel"
|
||||
>
|
||||
<span
|
||||
className="toggle"
|
||||
onClick={this.handleToggleVisibility}
|
||||
>
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var mapStateToProps = function (state) {
|
||||
return {
|
||||
session: state.session
|
||||
};
|
||||
AdminPanel.propTypes = {
|
||||
children: PropTypes.node,
|
||||
isAdmin: PropTypes.bool
|
||||
};
|
||||
|
||||
var ConnectedAdminPanel = connect(mapStateToProps)(AdminPanel);
|
||||
const mapStateToProps = state => ({
|
||||
isAdmin: state.permissions.admin
|
||||
});
|
||||
|
||||
const ConnectedAdminPanel = connect(mapStateToProps)(AdminPanel);
|
||||
|
||||
module.exports = ConnectedAdminPanel;
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
const classNames = require('classnames');
|
||||
const omit = require('lodash.omit');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var Avatar = React.createClass({
|
||||
type: 'Avatar',
|
||||
propTypes: {
|
||||
src: React.PropTypes.string
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
src: '//cdn2.scratch.mit.edu/get_image/user/2584924_24x24.png?v=1438702210.96'
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'avatar',
|
||||
this.props.className
|
||||
);
|
||||
return <img {... this.props} className={classes} />;
|
||||
}
|
||||
});
|
||||
const Avatar = props => (
|
||||
<img
|
||||
className={classNames('avatar', props.className)}
|
||||
{...omit(props, ['className'])}
|
||||
/>
|
||||
);
|
||||
|
||||
Avatar.propTypes = {
|
||||
className: PropTypes.string,
|
||||
src: PropTypes.string
|
||||
};
|
||||
|
||||
Avatar.defaultProps = {
|
||||
src: '//cdn2.scratch.mit.edu/get_image/user/2584924_24x24.png?v=1438702210.96'
|
||||
};
|
||||
|
||||
module.exports = Avatar;
|
||||
|
|
|
@ -1,40 +1,38 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./box.scss');
|
||||
|
||||
var Box = React.createClass({
|
||||
type: 'Box',
|
||||
propTypes: {
|
||||
title: React.PropTypes.string.isRequired,
|
||||
subtitle: React.PropTypes.string,
|
||||
moreTitle: React.PropTypes.string,
|
||||
moreHref: React.PropTypes.string,
|
||||
moreProps: React.PropTypes.object
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'box',
|
||||
this.props.className
|
||||
);
|
||||
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>
|
||||
const Box = props => (
|
||||
<div className={classNames('box', props.className)}>
|
||||
<div className="box-header">
|
||||
<h4>{props.title}</h4>
|
||||
<h5>{props.subtitle}</h5>
|
||||
<p>
|
||||
<a
|
||||
href={props.moreHref}
|
||||
{...props.moreProps}
|
||||
>
|
||||
{props.moreTitle}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="box-content">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
<div className="box-content">
|
||||
{props.children}
|
||||
</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;
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./card.scss');
|
||||
|
||||
var Card = React.createClass({
|
||||
displayName: 'Card',
|
||||
render: function () {
|
||||
return (
|
||||
<div className={classNames(['card', this.props.className])}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const Card = props => (
|
||||
<div className={classNames(['card', props.className])}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
|
||||
Card.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = Card;
|
||||
|
|
|
@ -1,95 +1,115 @@
|
|||
var classNames = require('classnames');
|
||||
var defaults = require('lodash.defaults');
|
||||
var React = require('react');
|
||||
var Slider = require('react-slick');
|
||||
const classNames = require('classnames');
|
||||
const defaults = require('lodash.defaults');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const Slider = require('react-slick');
|
||||
|
||||
var Thumbnail = require('../thumbnail/thumbnail.jsx');
|
||||
const Thumbnail = require('../thumbnail/thumbnail.jsx');
|
||||
|
||||
var frameless = require('../../lib/frameless.js');
|
||||
const frameless = require('../../lib/frameless.js');
|
||||
|
||||
require('slick-carousel/slick/slick.scss');
|
||||
require('slick-carousel/slick/slick-theme.scss');
|
||||
require('./carousel.scss');
|
||||
|
||||
/**
|
||||
* Displays content in horizontal scrolling box. Example usage: splash page rows.
|
||||
*/
|
||||
var Carousel = React.createClass({
|
||||
type: 'Carousel',
|
||||
propTypes: {
|
||||
items: React.PropTypes.array
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
items: require('./carousel.json'),
|
||||
showRemixes: false,
|
||||
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: {
|
||||
const Carousel = props => {
|
||||
defaults(props.settings, {
|
||||
centerMode: false,
|
||||
dots: false,
|
||||
infinite: false,
|
||||
lazyLoad: true,
|
||||
slidesToShow: 5,
|
||||
slidesToScroll: 5,
|
||||
variableWidth: true,
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: frameless.mobile,
|
||||
settings: {
|
||||
arrows: true,
|
||||
slidesToScroll: 1,
|
||||
slidesToShow: 1,
|
||||
centerMode: true
|
||||
}},
|
||||
{breakpoint: frameless.tablet, settings: {
|
||||
}
|
||||
},
|
||||
{
|
||||
breakpoint: frameless.tablet,
|
||||
settings: {
|
||||
slidesToScroll: 2,
|
||||
slidesToShow: 2
|
||||
}},
|
||||
{breakpoint: frameless.desktop, settings: {
|
||||
}
|
||||
},
|
||||
{
|
||||
breakpoint: frameless.desktop,
|
||||
settings: {
|
||||
slidesToScroll: 4,
|
||||
slidesToShow: 4
|
||||
}}
|
||||
]
|
||||
});
|
||||
var arrows = this.props.items.length > settings.slidesToShow;
|
||||
var classes = classNames(
|
||||
'carousel',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<Slider className={classes} arrows={arrows} {... settings}>
|
||||
{this.props.items.map(function (item) {
|
||||
var href = '';
|
||||
switch (this.props.type) {
|
||||
case 'gallery':
|
||||
href = '/studios/' + item.id + '/';
|
||||
break;
|
||||
case 'project':
|
||||
href = '/projects/' + item.id + '/';
|
||||
break;
|
||||
default:
|
||||
href = '/' + item.type + '/' + item.id + '/';
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
const arrows = props.items.length > props.settings.slidesToShow;
|
||||
return (
|
||||
<Slider
|
||||
arrows={arrows}
|
||||
className={classNames('carousel', props.className)}
|
||||
{... props.settings}
|
||||
>
|
||||
{props.items.map(item => {
|
||||
let href = '';
|
||||
switch (props.type) {
|
||||
case 'gallery':
|
||||
href = `/studios/${item.id}/`;
|
||||
break;
|
||||
case 'project':
|
||||
href = `/projects/${item.id}/`;
|
||||
break;
|
||||
default:
|
||||
href = `/${item.type}/${item.id}/`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Thumbnail key={[this.key, item.id].join('.')}
|
||||
showLoves={this.props.showLoves}
|
||||
showRemixes={this.props.showRemixes}
|
||||
type={this.props.type}
|
||||
href={href}
|
||||
title={item.title}
|
||||
src={item.image}
|
||||
creator={item.author.username}
|
||||
remixes={item.stats.remixes}
|
||||
loves={item.stats.loves} />
|
||||
);
|
||||
}.bind(this))}
|
||||
</Slider>
|
||||
);
|
||||
}
|
||||
});
|
||||
return (
|
||||
<Thumbnail
|
||||
creator={item.author.username}
|
||||
href={href}
|
||||
key={[props.type, item.id].join('.')}
|
||||
loves={item.stats.loves}
|
||||
remixes={item.stats.remixes}
|
||||
showLoves={props.showLoves}
|
||||
showRemixes={props.showRemixes}
|
||||
src={item.image}
|
||||
title={item.title}
|
||||
type={props.type}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</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;
|
||||
|
|
|
@ -1,97 +1,118 @@
|
|||
// This component handles json returned via proxy from a django server,
|
||||
// or directly from a django server, and the model structure that system
|
||||
// has.
|
||||
var classNames = require('classnames');
|
||||
var defaults = require('lodash.defaults');
|
||||
var React = require('react');
|
||||
var Slider = require('react-slick');
|
||||
const classNames = require('classnames');
|
||||
const defaults = require('lodash.defaults');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const Slider = require('react-slick');
|
||||
|
||||
var Thumbnail = require('../thumbnail/thumbnail.jsx');
|
||||
const Thumbnail = require('../thumbnail/thumbnail.jsx');
|
||||
|
||||
var frameless = require('../../lib/frameless.js');
|
||||
const frameless = require('../../lib/frameless.js');
|
||||
|
||||
require('slick-carousel/slick/slick.scss');
|
||||
require('slick-carousel/slick/slick-theme.scss');
|
||||
require('./carousel.scss');
|
||||
|
||||
/**
|
||||
* Displays content in horizontal scrolling box. Example usage: splash page rows.
|
||||
*/
|
||||
var LegacyCarousel = React.createClass({
|
||||
type: 'LegacyCarousel',
|
||||
propTypes: {
|
||||
items: React.PropTypes.array
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
items: require('./carousel.json'),
|
||||
showRemixes: false,
|
||||
showLoves: false
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
var settings = this.props.settings || {};
|
||||
defaults(settings, {
|
||||
centerMode: false,
|
||||
dots: false,
|
||||
infinite: false,
|
||||
lazyLoad: true,
|
||||
slidesToShow: 5,
|
||||
slidesToScroll: 5,
|
||||
variableWidth: true,
|
||||
responsive: [
|
||||
{breakpoint: frameless.mobile, settings: {
|
||||
const Carousel = props => {
|
||||
defaults(props.settings, {
|
||||
centerMode: false,
|
||||
dots: false,
|
||||
infinite: false,
|
||||
lazyLoad: true,
|
||||
slidesToShow: 5,
|
||||
slidesToScroll: 5,
|
||||
variableWidth: true,
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: frameless.mobile,
|
||||
settings: {
|
||||
arrows: true,
|
||||
slidesToScroll: 1,
|
||||
slidesToShow: 1,
|
||||
centerMode: true
|
||||
}},
|
||||
{breakpoint: frameless.tablet, settings: {
|
||||
}
|
||||
},
|
||||
{
|
||||
breakpoint: frameless.tablet,
|
||||
settings: {
|
||||
slidesToScroll: 2,
|
||||
slidesToShow: 2
|
||||
}},
|
||||
{breakpoint: frameless.desktop, settings: {
|
||||
}
|
||||
},
|
||||
{
|
||||
breakpoint: frameless.desktop,
|
||||
settings: {
|
||||
slidesToScroll: 4,
|
||||
slidesToShow: 4
|
||||
}}
|
||||
]
|
||||
});
|
||||
var arrows = this.props.items.length > settings.slidesToShow;
|
||||
var classes = classNames(
|
||||
'carousel',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<Slider className={classes} arrows={arrows} {... settings}>
|
||||
{this.props.items.map(function (item) {
|
||||
var href = '';
|
||||
switch (item.type) {
|
||||
case 'gallery':
|
||||
href = '/studios/' + item.id + '/';
|
||||
break;
|
||||
case 'project':
|
||||
href = '/projects/' + item.id + '/';
|
||||
break;
|
||||
default:
|
||||
href = '/' + item.type + '/' + item.id + '/';
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
const arrows = props.items.length > props.settings.slidesToShow;
|
||||
return (
|
||||
<Slider
|
||||
arrows={arrows}
|
||||
className={classNames('carousel', props.className)}
|
||||
{... props.settings}
|
||||
>
|
||||
{props.items.map(item => {
|
||||
let href = '';
|
||||
switch (item.type) {
|
||||
case 'gallery':
|
||||
href = `/studios/${item.id}/`;
|
||||
break;
|
||||
case 'project':
|
||||
href = `/projects/${item.id}/`;
|
||||
break;
|
||||
default:
|
||||
href = `/${item.type}/${item.id}/`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Thumbnail key={[this.key, item.id].join('.')}
|
||||
showLoves={this.props.showLoves}
|
||||
showRemixes={this.props.showRemixes}
|
||||
type={item.type}
|
||||
href={href}
|
||||
title={item.title}
|
||||
src={item.thumbnail_url}
|
||||
creator={item.creator}
|
||||
remixes={item.remixers_count}
|
||||
loves={item.love_count} />
|
||||
);
|
||||
}.bind(this))}
|
||||
</Slider>
|
||||
);
|
||||
}
|
||||
});
|
||||
return (
|
||||
<Thumbnail
|
||||
creator={item.creator}
|
||||
href={href}
|
||||
key={[props.type, item.id].join('.')}
|
||||
loves={item.love_count}
|
||||
remixes={item.remixers_count}
|
||||
showLoves={props.showLoves}
|
||||
showRemixes={props.showRemixes}
|
||||
src={item.thumbnail_url}
|
||||
title={item.title}
|
||||
type={item.type}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Slider>
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = LegacyCarousel;
|
||||
Carousel.propTypes = {
|
||||
className: PropTypes.string,
|
||||
items: PropTypes.arrayOf(PropTypes.any),
|
||||
settings: PropTypes.shape({
|
||||
centerMode: PropTypes.bool,
|
||||
dots: PropTypes.bool,
|
||||
infinite: PropTypes.bool,
|
||||
lazyLoad: PropTypes.bool,
|
||||
slidesToShow: PropTypes.number,
|
||||
slidesToScroll: PropTypes.number,
|
||||
variableWidth: PropTypes.bool,
|
||||
responsive: PropTypes.array
|
||||
}),
|
||||
showLoves: PropTypes.bool,
|
||||
showRemixes: PropTypes.bool,
|
||||
type: PropTypes.string
|
||||
};
|
||||
|
||||
Carousel.defaultProps = {
|
||||
items: require('./carousel.json'),
|
||||
settings: {},
|
||||
showRemixes: false,
|
||||
showLoves: false,
|
||||
type: 'project'
|
||||
};
|
||||
|
||||
module.exports = Carousel;
|
||||
|
|
|
@ -1,34 +1,33 @@
|
|||
var classNames = require('classnames');
|
||||
var FormattedRelative = require('react-intl').FormattedRelative;
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const FormattedRelative = require('react-intl').FormattedRelative;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var EmojiText = require('../emoji-text/emoji-text.jsx');
|
||||
const EmojiText = require('../emoji-text/emoji-text.jsx');
|
||||
|
||||
require('./comment.scss');
|
||||
|
||||
var CommentText = React.createClass({
|
||||
type: 'CommentText',
|
||||
propTypes: {
|
||||
comment: React.PropTypes.string.isRequired,
|
||||
datetimeCreated: React.PropTypes.string,
|
||||
className: React.PropTypes.string
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'comment-text',
|
||||
this.props.class
|
||||
);
|
||||
return (
|
||||
<div className={classes}>
|
||||
<EmojiText className="mod-comment" text={this.props.comment} />
|
||||
{typeof this.props.datetimeCreated !== 'undefined' ? [
|
||||
<p className="comment-text-timestamp">
|
||||
<FormattedRelative value={new Date(this.props.datetimeCreated)} />
|
||||
</p>
|
||||
] : []}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const CommentText = props => (
|
||||
<div className={classNames('comment-text', props.className)}>
|
||||
<EmojiText
|
||||
className="mod-comment"
|
||||
text={props.comment}
|
||||
/>
|
||||
{typeof props.datetimeCreated === 'undefined' ? [] : [
|
||||
<p
|
||||
className="comment-text-timestamp"
|
||||
key="comment-text-timestamp"
|
||||
>
|
||||
<FormattedRelative value={new Date(props.datetimeCreated)} />
|
||||
</p>
|
||||
]}
|
||||
</div>
|
||||
);
|
||||
|
||||
CommentText.propTypes = {
|
||||
className: PropTypes.string,
|
||||
comment: PropTypes.string.isRequired,
|
||||
datetimeCreated: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = CommentText;
|
||||
|
|
|
@ -1,22 +1,29 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./deck.scss');
|
||||
|
||||
var Deck = React.createClass({
|
||||
displayName: 'Deck',
|
||||
render: function () {
|
||||
return (
|
||||
<div className={classNames(['deck', this.props.className])}>
|
||||
<div className="inner">
|
||||
<a href="/" aria-label="Scratch">
|
||||
<img className="logo" src="/images/logo_sm.png" />
|
||||
</a>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const Deck = props => (
|
||||
<div className={classNames(['deck', props.className])}>
|
||||
<div className="inner">
|
||||
<a
|
||||
aria-label="Scratch"
|
||||
href="/"
|
||||
>
|
||||
<img
|
||||
className="logo"
|
||||
src="/images/logo_sm.png"
|
||||
/>
|
||||
</a>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Deck.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = Deck;
|
||||
|
|
|
@ -1,33 +1,29 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./banner.scss');
|
||||
|
||||
/**
|
||||
* Container for messages displayed below the nav bar that can be dismissed
|
||||
* (See: email not confirmed banner)
|
||||
*/
|
||||
var Banner = React.createClass({
|
||||
type: 'Banner',
|
||||
propTypes: {
|
||||
onRequestDismiss: React.PropTypes.func
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'banner',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="inner">
|
||||
{this.props.children}
|
||||
{this.props.onRequestDismiss ? [
|
||||
<a className="close" key="close" href="#" onClick={this.props.onRequestDismiss}>x</a>
|
||||
] : []}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const Banner = props => (
|
||||
<div className={classNames('banner', props.className)}>
|
||||
<div className="inner">
|
||||
{props.children}
|
||||
{props.onRequestDismiss ? [
|
||||
<a
|
||||
className="close"
|
||||
href="#"
|
||||
key="close"
|
||||
onClick={props.onRequestDismiss}
|
||||
>x</a>
|
||||
] : []}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Banner.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
onRequestDismiss: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = Banner;
|
||||
|
|
|
@ -1,40 +1,46 @@
|
|||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
const bindAll = require('lodash.bindall');
|
||||
const classNames = require('classnames');
|
||||
const onClickOutside = require('react-onclickoutside').default;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./dropdown.scss');
|
||||
|
||||
var Dropdown = React.createClass({
|
||||
type: 'Dropdown',
|
||||
mixins: [
|
||||
require('react-onclickoutside')
|
||||
],
|
||||
propTypes: {
|
||||
onRequestClose: React.PropTypes.func,
|
||||
isOpen: React.PropTypes.bool
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
as: 'div',
|
||||
isOpen: false
|
||||
};
|
||||
},
|
||||
handleClickOutside: function () {
|
||||
class Dropdown extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleClickOutside'
|
||||
]);
|
||||
}
|
||||
handleClickOutside () {
|
||||
if (this.props.isOpen) {
|
||||
this.props.onRequestClose();
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'dropdown',
|
||||
this.props.className,
|
||||
{open: this.props.isOpen}
|
||||
);
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<this.props.as className={classes}>
|
||||
<this.props.as
|
||||
className={classNames('dropdown', this.props.className, {
|
||||
open: this.props.isOpen
|
||||
})}
|
||||
>
|
||||
{this.props.children}
|
||||
</this.props.as>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = Dropdown;
|
||||
Dropdown.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
isOpen: PropTypes.bool,
|
||||
onRequestClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
Dropdown.defaultProps = {
|
||||
as: 'div',
|
||||
isOpen: false
|
||||
};
|
||||
|
||||
module.exports = onClickOutside(Dropdown);
|
||||
|
|
|
@ -24,8 +24,12 @@
|
|||
}
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
color: $type-white;
|
||||
&:link,
|
||||
&:visited,
|
||||
&:active {
|
||||
background-color: transparent;
|
||||
color: $type-white;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
|
|
|
@ -1,33 +1,25 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./emoji-text.scss');
|
||||
|
||||
var EmojiText = React.createClass({
|
||||
type: 'EmojiText',
|
||||
propTyes: {
|
||||
text: React.PropTypes.string.isRequired,
|
||||
className: React.PropTypes.string
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
as: 'p'
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'emoji-text',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<this.props.as
|
||||
className={classes}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: this.props.text
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
const EmojiText = props => (
|
||||
<props.as
|
||||
className={classNames('emoji-text', props.className)}
|
||||
dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
|
||||
__html: props.text
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
EmojiText.propTypes = {
|
||||
className: PropTypes.string,
|
||||
text: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
EmojiText.defaultProps = {
|
||||
as: 'p'
|
||||
};
|
||||
|
||||
module.exports = EmojiText;
|
||||
|
|
|
@ -1,26 +1,22 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./flex-row.scss');
|
||||
|
||||
var FlexRow = React.createClass({
|
||||
type: 'FlexRow',
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
as: 'div'
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'flex-row',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<this.props.as className={classes}>
|
||||
{this.props.children}
|
||||
</this.props.as>
|
||||
);
|
||||
}
|
||||
});
|
||||
const FlexRow = props => (
|
||||
<props.as className={classNames('flex-row', props.className)}>
|
||||
{props.children}
|
||||
</props.as>
|
||||
);
|
||||
|
||||
FlexRow.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
FlexRow.defaultProps = {
|
||||
as: 'div'
|
||||
};
|
||||
|
||||
module.exports = FlexRow;
|
||||
|
|
|
@ -1,125 +1,152 @@
|
|||
var React = require('react');
|
||||
const React = require('react');
|
||||
|
||||
var FlexRow = require('../../../flex-row/flex-row.jsx');
|
||||
var FooterBox = require('../../container/footer.jsx');
|
||||
const FlexRow = require('../../../flex-row/flex-row.jsx');
|
||||
const FooterBox = require('../../container/footer.jsx');
|
||||
|
||||
require('../footer.scss');
|
||||
|
||||
var ConferenceFooter = React.createClass({
|
||||
type: 'ConferenceFooter',
|
||||
render: function () {
|
||||
return (
|
||||
<FooterBox>
|
||||
<div className="collaborators">
|
||||
<h4>Sponsors</h4>
|
||||
const ConferenceFooter = () => (
|
||||
<FooterBox>
|
||||
<div className="collaborators">
|
||||
<h4>Sponsors</h4>
|
||||
<FlexRow as="ul">
|
||||
<li className="odl">
|
||||
<a href="https://odl.mit.edu/">
|
||||
<img
|
||||
alt="MIT Office of Digital Learning"
|
||||
src="/images/conference/footer/mit-odl.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="intel">
|
||||
<a href="http://www.intel.com/content/www/us/en/homepage.html">
|
||||
<img
|
||||
alt="Intel"
|
||||
src="/images/conference/footer/intel.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="lego">
|
||||
<a href="http://www.legofoundation.com/">
|
||||
<img
|
||||
alt="LEGO Foundation"
|
||||
src="/images/conference/footer/lego-foundation.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="google">
|
||||
<a href="http://www.google.com/">
|
||||
<img
|
||||
alt="Google"
|
||||
src="/images/conference/footer/google.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="siegel">
|
||||
<a href="http://www.siegelendowment.org/">
|
||||
<img
|
||||
alt="Siegel Family Endowment"
|
||||
src="/images/conference/footer/siegel-endowment.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="nostarch">
|
||||
<a href="https://www.nostarch.com/">
|
||||
<img
|
||||
alt="No Starch Press"
|
||||
src="/images/conference/footer/no-starch.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="scratchfoundation">
|
||||
<a href="http://www.scratchfoundation.org/">
|
||||
<img
|
||||
alt="Scratch Foundation"
|
||||
src="/images/conference/footer/scratch-foundation.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</FlexRow>
|
||||
</div>
|
||||
<FlexRow className="scratch-links">
|
||||
<div className="family">
|
||||
<h4>Scratch Family</h4>
|
||||
<FlexRow>
|
||||
<FlexRow
|
||||
as="ul"
|
||||
className="column"
|
||||
>
|
||||
<li>
|
||||
<a href="https://scratch.mit.edu">Scratch</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.scratchjr.org/">ScratchJr</a>
|
||||
</li>
|
||||
</FlexRow>
|
||||
<FlexRow
|
||||
as="ul"
|
||||
className="column"
|
||||
>
|
||||
<li>
|
||||
<a href="http://www.scratchfoundation.org/">Scratch Foundation</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://scratched.gse.harvard.edu/">ScratchEd</a>
|
||||
</li>
|
||||
</FlexRow>
|
||||
<FlexRow
|
||||
as="ul"
|
||||
className="column"
|
||||
>
|
||||
<li>
|
||||
<a href="http://day.scratch.mit.edu">Scratch Day</a>
|
||||
</li>
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
<p className="legal">
|
||||
Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab.
|
||||
</p>
|
||||
</div>
|
||||
<div className="media">
|
||||
<div className="contact-us">
|
||||
<h4>Contact</h4>
|
||||
<p>
|
||||
<a href="mailto:help@scratch.mit.edu">
|
||||
Email Us
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="social">
|
||||
<FlexRow as="ul">
|
||||
<li className="odl">
|
||||
<a href="https://odl.mit.edu/">
|
||||
<img src="/images/conference/footer/mit-odl.png"
|
||||
alt="MIT Office of Digital Learning" />
|
||||
<li>
|
||||
<a href="//www.twitter.com/scratch">
|
||||
<img
|
||||
alt="scratch twitter"
|
||||
src="/images/conference/footer/twitter.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="intel">
|
||||
<a href="http://www.intel.com/content/www/us/en/homepage.html">
|
||||
<img src="/images/conference/footer/intel.png"
|
||||
alt="Intel" />
|
||||
<li>
|
||||
<a href="//www.facebook.com/scratchteam">
|
||||
<img
|
||||
alt="scratch facebook"
|
||||
src="/images/conference/footer/facebook.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="lego">
|
||||
<a href="http://www.legofoundation.com/">
|
||||
<img src="/images/conference/footer/lego-foundation.png"
|
||||
alt="LEGO Foundation" />
|
||||
</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" />
|
||||
<li>
|
||||
<a href="http://medium.com/scratchfoundation-blog">
|
||||
<img
|
||||
alt="scratch foundation blog"
|
||||
src="/images/conference/footer/medium.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" 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>
|
||||
);
|
||||
}
|
||||
});
|
||||
</div>
|
||||
</FlexRow>
|
||||
</FooterBox>
|
||||
);
|
||||
|
||||
module.exports = ConferenceFooter;
|
||||
|
|
|
@ -1,84 +1,100 @@
|
|||
var React = require('react');
|
||||
var ReactIntl = require('react-intl');
|
||||
const injectIntl = require('react-intl').injectIntl;
|
||||
const intlShape = require('react-intl').intlShape;
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const React = require('react');
|
||||
|
||||
var injectIntl = ReactIntl.injectIntl;
|
||||
var FormattedMessage = ReactIntl.FormattedMessage;
|
||||
|
||||
var FlexRow = require('../../../flex-row/flex-row.jsx');
|
||||
var FooterBox = require('../../container/footer.jsx');
|
||||
var LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
|
||||
const FlexRow = require('../../../flex-row/flex-row.jsx');
|
||||
const FooterBox = require('../../container/footer.jsx');
|
||||
const LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
|
||||
|
||||
require('../footer.scss');
|
||||
|
||||
var ConferenceFooter = React.createClass({
|
||||
type: 'ConferenceFooter',
|
||||
render: function () {
|
||||
return (
|
||||
<FooterBox>
|
||||
<FlexRow className="scratch-links">
|
||||
<div className="family">
|
||||
<h4><FormattedMessage id='footer.scratchFamily' /></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">
|
||||
<FormattedMessage id='general.copyright' />
|
||||
</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>
|
||||
const ConferenceFooter = props => (
|
||||
<FooterBox>
|
||||
<FlexRow className="scratch-links">
|
||||
<div className="family">
|
||||
<h4><FormattedMessage id="footer.scratchFamily" /></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>
|
||||
<LanguageChooser locale={this.props.intl.locale} />
|
||||
</FooterBox>
|
||||
);
|
||||
}
|
||||
});
|
||||
<p className="legal">
|
||||
<FormattedMessage id="general.copyright" />
|
||||
</p>
|
||||
</div>
|
||||
<div className="media">
|
||||
<div className="contact-us">
|
||||
<h4>Contact</h4>
|
||||
<p>
|
||||
<a href="mailto:help@scratch.mit.edu">
|
||||
Email Us
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="social">
|
||||
<FlexRow as="ul">
|
||||
<li>
|
||||
<a href="//www.twitter.com/scratch">
|
||||
<img
|
||||
alt="scratch twitter"
|
||||
src="/images/conference/footer/twitter.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="//www.facebook.com/scratchteam">
|
||||
<img
|
||||
alt="scratch facebook"
|
||||
src="/images/conference/footer/facebook.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://medium.com/scratchfoundation-blog">
|
||||
<img
|
||||
alt="scratch foundation blog"
|
||||
src="/images/conference/footer/medium.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</FlexRow>
|
||||
</div>
|
||||
</div>
|
||||
</FlexRow>
|
||||
<LanguageChooser locale={props.intl.locale} />
|
||||
</FooterBox>
|
||||
);
|
||||
|
||||
ConferenceFooter.propTypes = {
|
||||
intl: intlShape
|
||||
};
|
||||
|
||||
module.exports = injectIntl(ConferenceFooter);
|
||||
|
|
|
@ -1,84 +1,146 @@
|
|||
var React = require('react');
|
||||
var ReactIntl = require('react-intl');
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const injectIntl = require('react-intl').injectIntl;
|
||||
const intlShape = require('react-intl').intlShape;
|
||||
const React = require('react');
|
||||
|
||||
var injectIntl = ReactIntl.injectIntl;
|
||||
var FormattedMessage = ReactIntl.FormattedMessage;
|
||||
|
||||
var FlexRow = require('../../../flex-row/flex-row.jsx');
|
||||
var FooterBox = require('../../container/footer.jsx');
|
||||
var LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
|
||||
const FlexRow = require('../../../flex-row/flex-row.jsx');
|
||||
const FooterBox = require('../../container/footer.jsx');
|
||||
const LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
|
||||
|
||||
require('../footer.scss');
|
||||
|
||||
var ConferenceFooter = React.createClass({
|
||||
type: 'ConferenceFooter',
|
||||
render: function () {
|
||||
return (
|
||||
<FooterBox>
|
||||
<FlexRow className="scratch-links">
|
||||
<div className="family">
|
||||
<h4><FormattedMessage id='footer.scratchFamily' /></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">
|
||||
<FormattedMessage id='general.copyright' />
|
||||
</p>
|
||||
</div>
|
||||
<div className="media">
|
||||
<div className="contact-us">
|
||||
<h4>Contact</h4>
|
||||
<p>
|
||||
<a href="mailto:conference@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>
|
||||
const ConferenceFooter = props => (
|
||||
<FooterBox>
|
||||
<FlexRow className="scratch-links">
|
||||
<div className="family">
|
||||
<h4><FormattedMessage id="footer.scratchFamily" /></h4>
|
||||
<FlexRow>
|
||||
<FlexRow
|
||||
as="ul"
|
||||
className="column"
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
href="https://scratch.mit.edu"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Scratch
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="http://www.scratchjr.org/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
ScratchJr
|
||||
</a>
|
||||
</li>
|
||||
</FlexRow>
|
||||
<FlexRow
|
||||
as="ul"
|
||||
className="column"
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
href="http://www.scratchfoundation.org/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Scratch Foundation
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="http://scratched.gse.harvard.edu/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
ScratchEd
|
||||
</a>
|
||||
</li>
|
||||
</FlexRow>
|
||||
<FlexRow
|
||||
as="ul"
|
||||
className="column"
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
href="http://day.scratch.mit.edu"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Scratch Day
|
||||
</a>
|
||||
</li>
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
<LanguageChooser locale={this.props.intl.locale} />
|
||||
</FooterBox>
|
||||
);
|
||||
}
|
||||
});
|
||||
<p className="legal">
|
||||
<FormattedMessage id="general.copyright" />
|
||||
</p>
|
||||
</div>
|
||||
<div className="media">
|
||||
<div className="contact-us">
|
||||
<h4>Contact</h4>
|
||||
<p>
|
||||
<a
|
||||
href="mailto:conference@scratch.mit.edu"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Email Us
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="social">
|
||||
<FlexRow as="ul">
|
||||
<li>
|
||||
<a
|
||||
href="//www.twitter.com/scratch"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
alt="scratch twitter"
|
||||
src="/images/conference/footer/twitter.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="//www.facebook.com/scratchteam"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
alt="scratch facebook"
|
||||
src="/images/conference/footer/facebook.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="http://medium.com/scratchfoundation-blog"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
alt="scratch foundation blog"
|
||||
src="/images/conference/footer/medium.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</FlexRow>
|
||||
</div>
|
||||
</div>
|
||||
</FlexRow>
|
||||
<LanguageChooser locale={props.intl.locale} />
|
||||
</FooterBox>
|
||||
);
|
||||
|
||||
ConferenceFooter.propTypes = {
|
||||
intl: intlShape
|
||||
};
|
||||
|
||||
module.exports = injectIntl(ConferenceFooter);
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
var React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./footer.scss');
|
||||
|
||||
var FooterBox = React.createClass({
|
||||
type: 'FooterBox',
|
||||
render: function () {
|
||||
return (
|
||||
<div className="inner">
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const FooterBox = props => (
|
||||
<div className="inner">
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
|
||||
FooterBox.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
module.exports = FooterBox;
|
||||
|
|
|
@ -1,226 +1,225 @@
|
|||
var React = require('react');
|
||||
var ReactIntl = require('react-intl');
|
||||
var FormattedMessage = ReactIntl.FormattedMessage;
|
||||
var injectIntl = ReactIntl.injectIntl;
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const injectIntl = require('react-intl').injectIntl;
|
||||
const intlShape = require('react-intl').intlShape;
|
||||
const MediaQuery = require('react-responsive').default;
|
||||
const React = require('react');
|
||||
|
||||
var FooterBox = require('../container/footer.jsx');
|
||||
var LanguageChooser = require('../../languagechooser/languagechooser.jsx');
|
||||
const FooterBox = require('../container/footer.jsx');
|
||||
const LanguageChooser = require('../../languagechooser/languagechooser.jsx');
|
||||
|
||||
var MediaQuery = require('react-responsive');
|
||||
var frameless = require('../../../lib/frameless');
|
||||
const frameless = require('../../../lib/frameless');
|
||||
|
||||
require('./footer.scss');
|
||||
|
||||
var Footer = React.createClass({
|
||||
type: 'Footer',
|
||||
render: function () {
|
||||
return (
|
||||
<FooterBox>
|
||||
<MediaQuery maxWidth={frameless.tablet - 1}>
|
||||
<div className="lists">
|
||||
<dl>
|
||||
<dd>
|
||||
<a href="/about">
|
||||
<FormattedMessage id='general.aboutScratch' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/jobs">
|
||||
<FormattedMessage id='general.jobs' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/contact-us/">
|
||||
<FormattedMessage id='general.contactUs' />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dd>
|
||||
<a href="/terms_of_use">
|
||||
<FormattedMessage id='general.termsOfUse' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/privacy_policy">
|
||||
<FormattedMessage id='general.privacyPolicy' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/community_guidelines">
|
||||
<FormattedMessage id='general.guidelines' />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={frameless.tablet}>
|
||||
<div className="lists">
|
||||
<dl>
|
||||
<dt>
|
||||
<FormattedMessage id='general.about' />
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="/about">
|
||||
<FormattedMessage id='general.aboutScratch' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/parents/">
|
||||
<FormattedMessage id='general.forParents' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/educators">
|
||||
<FormattedMessage id='general.forEducators' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/developers">
|
||||
<FormattedMessage id='general.forDevelopers' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/info/credits">
|
||||
<FormattedMessage id='general.credits' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/jobs">
|
||||
<FormattedMessage id='general.jobs' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="http://wiki.scratch.mit.edu/wiki/Scratch_Press">
|
||||
<FormattedMessage id='general.press' />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>
|
||||
<FormattedMessage id='general.community' />
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="/community_guidelines">
|
||||
<FormattedMessage id='general.guidelines' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/discuss/">
|
||||
<FormattedMessage id='footer.discuss' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="https://wiki.scratch.mit.edu/">
|
||||
<FormattedMessage id='general.wiki' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/statistics/">
|
||||
<FormattedMessage id='general.statistics' />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
const Footer = props => (
|
||||
<FooterBox>
|
||||
<MediaQuery maxWidth={frameless.tablet - 1}>
|
||||
<div className="lists">
|
||||
<dl>
|
||||
<dd>
|
||||
<a href="/about">
|
||||
<FormattedMessage id="general.aboutScratch" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/jobs">
|
||||
<FormattedMessage id="general.jobs" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/contact-us/">
|
||||
<FormattedMessage id="general.contactUs" />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dd>
|
||||
<a href="/terms_of_use">
|
||||
<FormattedMessage id="general.termsOfUse" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/privacy_policy">
|
||||
<FormattedMessage id="general.privacyPolicy" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/community_guidelines">
|
||||
<FormattedMessage id="general.guidelines" />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={frameless.tablet}>
|
||||
<div className="lists">
|
||||
<dl>
|
||||
<dt>
|
||||
<FormattedMessage id="general.about" />
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="/about">
|
||||
<FormattedMessage id="general.aboutScratch" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/parents/">
|
||||
<FormattedMessage id="general.forParents" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/educators">
|
||||
<FormattedMessage id="general.forEducators" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/developers">
|
||||
<FormattedMessage id="general.forDevelopers" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/info/credits">
|
||||
<FormattedMessage id="general.credits" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/jobs">
|
||||
<FormattedMessage id="general.jobs" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="http://wiki.scratch.mit.edu/wiki/Scratch_Press">
|
||||
<FormattedMessage id="general.press" />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>
|
||||
<FormattedMessage id="general.community" />
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="/community_guidelines">
|
||||
<FormattedMessage id="general.guidelines" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/discuss/">
|
||||
<FormattedMessage id="footer.discuss" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="https://wiki.scratch.mit.edu/">
|
||||
<FormattedMessage id="general.wiki" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/statistics/">
|
||||
<FormattedMessage id="general.statistics" />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl>
|
||||
<dt>
|
||||
<FormattedMessage id='general.support' />
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="/tips">
|
||||
<FormattedMessage id='general.tips' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/info/faq">
|
||||
<FormattedMessage id='general.faq' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/download">
|
||||
<FormattedMessage id='general.offlineEditor' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/contact-us/">
|
||||
<FormattedMessage id='general.contactUs' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/store">
|
||||
<FormattedMessage id='general.scratchStore' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="https://secure.donationpay.org/scratchfoundation/">
|
||||
<FormattedMessage id='general.donate'/>
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>
|
||||
<FormattedMessage id="general.support" />
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="/tips">
|
||||
<FormattedMessage id="general.tips" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/info/faq">
|
||||
<FormattedMessage id="general.faq" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/download">
|
||||
<FormattedMessage id="general.offlineEditor" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/contact-us/">
|
||||
<FormattedMessage id="general.contactUs" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/store">
|
||||
<FormattedMessage id="general.scratchStore" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="https://secure.donationpay.org/scratchfoundation/">
|
||||
<FormattedMessage id="general.donate" />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl>
|
||||
<dt>
|
||||
<FormattedMessage id='general.legal'/>
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="/terms_of_use">
|
||||
<FormattedMessage id='general.termsOfUse' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/privacy_policy">
|
||||
<FormattedMessage id='general.privacyPolicy' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/DMCA">
|
||||
<FormattedMessage id='general.dmca' />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>
|
||||
<FormattedMessage id="general.legal" />
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="/terms_of_use">
|
||||
<FormattedMessage id="general.termsOfUse" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/privacy_policy">
|
||||
<FormattedMessage id="general.privacyPolicy" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/DMCA">
|
||||
<FormattedMessage id="general.dmca" />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl>
|
||||
<dt>
|
||||
<FormattedMessage id='footer.scratchFamily' />
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="http://scratched.gse.harvard.edu/">
|
||||
<FormattedMessage id='general.scratchEd' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="http://www.scratchjr.org/">
|
||||
<FormattedMessage id='general.scratchJr' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="http://day.scratch.mit.edu/">
|
||||
Scratch Day
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/conference">
|
||||
<FormattedMessage id='general.scratchConference' />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="http://www.scratchfoundation.org/">
|
||||
<FormattedMessage id='general.scratchFoundation' />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</MediaQuery>
|
||||
<LanguageChooser locale={this.props.intl.locale} />
|
||||
<dl>
|
||||
<dt>
|
||||
<FormattedMessage id="footer.scratchFamily" />
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="http://scratched.gse.harvard.edu/">
|
||||
<FormattedMessage id="general.scratchEd" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="http://www.scratchjr.org/">
|
||||
<FormattedMessage id="general.scratchJr" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="http://day.scratch.mit.edu/">
|
||||
Scratch Day
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/conference">
|
||||
<FormattedMessage id="general.scratchConference" />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="http://www.scratchfoundation.org/">
|
||||
<FormattedMessage id="general.scratchFoundation" />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</MediaQuery>
|
||||
<LanguageChooser locale={props.intl.locale} />
|
||||
|
||||
<div className="copyright">
|
||||
<p>
|
||||
<FormattedMessage id='general.copyright' />
|
||||
</p>
|
||||
</div>
|
||||
</FooterBox>
|
||||
);
|
||||
}
|
||||
});
|
||||
<div className="copyright">
|
||||
<p>
|
||||
<FormattedMessage id="general.copyright" />
|
||||
</p>
|
||||
</div>
|
||||
</FooterBox>
|
||||
);
|
||||
|
||||
Footer.propTypes = {
|
||||
intl: intlShape.isRequired
|
||||
};
|
||||
|
||||
module.exports = injectIntl(Footer);
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
const classNames = require('classnames');
|
||||
const omit = require('lodash.omit');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./button.scss');
|
||||
|
||||
var Button = React.createClass({
|
||||
type: 'Button',
|
||||
propTypes: {
|
||||
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'button',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<button {... this.props} className={classes} >{this.props.children}</button>
|
||||
);
|
||||
}
|
||||
});
|
||||
const Button = props => {
|
||||
const classes = classNames('button', props.className);
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classes}
|
||||
{...omit(props, ['className', 'children'])}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
Button.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = Button;
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./charcount.scss');
|
||||
|
||||
var CharCount = React.createClass({
|
||||
type: 'CharCount',
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
maxCharacters: 0,
|
||||
currentCharacters: 0
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'char-count',
|
||||
this.props.className,
|
||||
{overmax: (this.props.currentCharacters > this.props.maxCharacters)}
|
||||
);
|
||||
return (
|
||||
<p className={classes}>
|
||||
{this.props.currentCharacters}/{this.props.maxCharacters}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
});
|
||||
const CharCount = props => (
|
||||
<p
|
||||
className={classNames('char-count', props.className, {
|
||||
overmax: (props.currentCharacters > props.maxCharacters)
|
||||
})}
|
||||
>
|
||||
{props.currentCharacters}/{props.maxCharacters}
|
||||
</p>
|
||||
);
|
||||
|
||||
CharCount.propTypes = {
|
||||
className: PropTypes.string,
|
||||
currentCharacters: PropTypes.number,
|
||||
maxCharacters: PropTypes.number
|
||||
};
|
||||
|
||||
CharCount.defaultProps = {
|
||||
currentCharacters: 0,
|
||||
maxCharacters: 0
|
||||
};
|
||||
|
||||
module.exports = CharCount;
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
var classNames = require('classnames');
|
||||
var FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup;
|
||||
var React = require('react');
|
||||
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
|
||||
var inputHOC = require('./input-hoc.jsx');
|
||||
const classNames = require('classnames');
|
||||
const FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
|
||||
const inputHOC = require('./input-hoc.jsx');
|
||||
|
||||
require('./row.scss');
|
||||
require('./checkbox-group.scss');
|
||||
|
||||
var CheckboxGroup = React.createClass({
|
||||
type: 'CheckboxGroup',
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'checkbox-group',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<div className={classes}>
|
||||
<FRCCheckboxGroup {... this.props} className={classes} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const CheckboxGroup = props => (
|
||||
<div className={classNames('checkbox-group', props.className)}>
|
||||
<FRCCheckboxGroup
|
||||
className={classNames('checkbox-group', props.className)}
|
||||
{... props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
CheckboxGroup.propTypes = {
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = inputHOC(defaultValidationHOC(CheckboxGroup));
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
var classNames = require('classnames');
|
||||
var FRCCheckbox = require('formsy-react-components').Checkbox;
|
||||
var React = require('react');
|
||||
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
|
||||
var inputHOC = require('./input-hoc.jsx');
|
||||
const classNames = require('classnames');
|
||||
const FRCCheckbox = require('formsy-react-components').Checkbox;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
|
||||
const inputHOC = require('./input-hoc.jsx');
|
||||
|
||||
require('./row.scss');
|
||||
require('./checkbox.scss');
|
||||
|
||||
var Checkbox = React.createClass({
|
||||
type: 'Checkbox',
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'checkbox-row',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<FRCCheckbox rowClassName={classes} {... this.props} />
|
||||
);
|
||||
}
|
||||
});
|
||||
const Checkbox = props => (
|
||||
<FRCCheckbox
|
||||
rowClassName={classNames('checkbox-row', props.className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
Checkbox.propTypes = {
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = inputHOC(defaultValidationHOC(Checkbox));
|
||||
|
|
|
@ -1,47 +1,61 @@
|
|||
var classNames = require('classnames');
|
||||
var Formsy = require('formsy-react');
|
||||
var omit = require('lodash.omit');
|
||||
var React = require('react');
|
||||
var validations = require('./validations.jsx').validations;
|
||||
const bindAll = require('lodash.bindall');
|
||||
const classNames = require('classnames');
|
||||
const Formsy = require('formsy-react');
|
||||
const omit = require('lodash.omit');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
for (var validation in validations) {
|
||||
const validations = require('./validations.jsx').validations;
|
||||
|
||||
for (const validation in validations) {
|
||||
Formsy.addValidationRule(validation, validations[validation]);
|
||||
}
|
||||
|
||||
var Form = React.createClass({
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
noValidate: true,
|
||||
onChange: function () {}
|
||||
};
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
class Form extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleChange'
|
||||
]);
|
||||
this.state = {
|
||||
allValues: {}
|
||||
};
|
||||
},
|
||||
onChange: function (currentValues, isChanged) {
|
||||
}
|
||||
handleChange (currentValues, isChanged) {
|
||||
this.setState({allValues: omit(currentValues, 'all')});
|
||||
this.props.onChange(currentValues, isChanged);
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'form',
|
||||
this.props.className
|
||||
);
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<Formsy.Form {... this.props} className={classes} ref="formsy" onChange={this.onChange}>
|
||||
{React.Children.map(this.props.children, function (child) {
|
||||
<Formsy.Form
|
||||
className={classNames('form', this.props.className)}
|
||||
ref={form => {
|
||||
this.formsy = form;
|
||||
}}
|
||||
onChange={this.handleChange}
|
||||
{...this.props}
|
||||
>
|
||||
{React.Children.map(this.props.children, child => {
|
||||
if (!child) return child;
|
||||
if (child.props.name === 'all') {
|
||||
return React.cloneElement(child, {value: this.state.allValues});
|
||||
} else {
|
||||
return child;
|
||||
}
|
||||
}.bind(this))}
|
||||
return child;
|
||||
})}
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Form.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
Form.defaultProps = {
|
||||
noValidate: true,
|
||||
onChange: function () {}
|
||||
};
|
||||
|
||||
module.exports = Form;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
var Formsy = require('formsy-react');
|
||||
var React = require('react');
|
||||
const Formsy = require('formsy-react');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./general-error.scss');
|
||||
|
||||
|
@ -10,13 +11,18 @@ require('./general-error.scss');
|
|||
* give it a name, and apply your validation error to
|
||||
* the name of the GeneralError component.
|
||||
*/
|
||||
module.exports = Formsy.HOC(React.createClass({
|
||||
render: function () {
|
||||
if (!this.props.showError()) return null;
|
||||
return (
|
||||
<p className="general-error">
|
||||
{this.props.getErrorMessage()}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
}));
|
||||
const GeneralError = props => {
|
||||
if (!props.showError()) return null;
|
||||
return (
|
||||
<p className="general-error">
|
||||
{props.getErrorMessage()}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
GeneralError.propTypes = {
|
||||
getErrorMessage: PropTypes.func,
|
||||
showError: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = Formsy.HOC(GeneralError);
|
||||
|
|
|
@ -1,20 +1,32 @@
|
|||
var React = require('react');
|
||||
const omit = require('lodash.omit');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
module.exports = function InputComponentMixin (Component) {
|
||||
var InputComponent = React.createClass({
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
messages: {
|
||||
'general.notRequired': 'Not Required'
|
||||
}
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Component help={this.props.required ? null : this.props.messages['general.notRequired']}
|
||||
{...this.props} />
|
||||
);
|
||||
/**
|
||||
* Higher-order component for building an input field
|
||||
* @param {React.Component} Component an input component
|
||||
* @return {React.Component} a wrapped input component
|
||||
*/
|
||||
module.exports = Component => {
|
||||
const InputComponent = props => (
|
||||
<Component
|
||||
help={props.required ? null : props.messages['general.notRequired']}
|
||||
{...omit(props, ['messages'])}
|
||||
/>
|
||||
);
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -1,46 +1,57 @@
|
|||
var classNames = require('classnames');
|
||||
var FRCInput = require('formsy-react-components').Input;
|
||||
var React = require('react');
|
||||
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
|
||||
var inputHOC = require('./input-hoc.jsx');
|
||||
const bindAll = require('lodash.bindall');
|
||||
const classNames = require('classnames');
|
||||
const FRCInput = require('formsy-react-components').Input;
|
||||
const omit = require('lodash.omit');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
|
||||
const inputHOC = require('./input-hoc.jsx');
|
||||
|
||||
require('./input.scss');
|
||||
require('./row.scss');
|
||||
|
||||
var Input = React.createClass({
|
||||
type: 'Input',
|
||||
getDefaultProps: function () {
|
||||
return {};
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
class Input extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleInvalid',
|
||||
'handleValid'
|
||||
]);
|
||||
this.state = {
|
||||
status: ''
|
||||
};
|
||||
},
|
||||
onValid: function () {
|
||||
}
|
||||
handleValid () {
|
||||
this.setState({
|
||||
status: 'pass'
|
||||
});
|
||||
},
|
||||
onInvalid: function () {
|
||||
}
|
||||
handleInvalid () {
|
||||
this.setState({
|
||||
status: 'fail'
|
||||
});
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
this.state.status,
|
||||
this.props.className,
|
||||
{'no-label': (typeof this.props.label === 'undefined')}
|
||||
);
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<FRCInput {... this.props}
|
||||
className="input"
|
||||
rowClassName={classes}
|
||||
onValid={this.onValid}
|
||||
onInvalid={this.onInvalid} />
|
||||
<FRCInput
|
||||
className="input"
|
||||
rowClassName={classNames(
|
||||
this.state.status,
|
||||
this.props.className,
|
||||
{'no-label': (typeof this.props.label === 'undefined')}
|
||||
)}
|
||||
onInvalid={this.handleInvalid}
|
||||
onValid={this.handleValid}
|
||||
{...omit(this.props, ['className'])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Input.propTypes = {
|
||||
className: PropTypes.string,
|
||||
label: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = inputHOC(defaultValidationHOC(Input));
|
||||
|
|
|
@ -1,23 +1,33 @@
|
|||
var allCountries = require('react-telephone-input/lib/country_data').allCountries;
|
||||
var classNames = require('classnames');
|
||||
var ComponentMixin = require('formsy-react-components').ComponentMixin;
|
||||
var FormsyMixin = require('formsy-react').Mixin;
|
||||
var React = require('react');
|
||||
var ReactPhoneInput = require('react-telephone-input/lib/withStyles');
|
||||
var Row = require('formsy-react-components').Row;
|
||||
const allCountries = require('react-telephone-input/lib/country_data').allCountries;
|
||||
const classNames = require('classnames');
|
||||
const ComponentMixin = require('formsy-react-components').ComponentMixin;
|
||||
const createReactClass = require('create-react-class');
|
||||
const FormsyMixin = require('formsy-react').Mixin;
|
||||
const omit = require('lodash.omit');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const ReactPhoneInput = require('react-telephone-input/lib/withStyles').default;
|
||||
const Row = require('formsy-react-components').Row;
|
||||
|
||||
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
|
||||
var inputHOC = require('./input-hoc.jsx');
|
||||
var intl = require('../../lib/intl.jsx');
|
||||
var validationHOCFactory = require('./validations.jsx').validationHOCFactory;
|
||||
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
|
||||
const inputHOC = require('./input-hoc.jsx');
|
||||
const intl = require('../../lib/intl.jsx');
|
||||
const validationHOCFactory = require('./validations.jsx').validationHOCFactory;
|
||||
|
||||
var allIso2 = allCountries.map(function (country) {return country.iso2;});
|
||||
const allIso2 = allCountries.map(country => (country.iso2));
|
||||
|
||||
require('./row.scss');
|
||||
require('./phone-input.scss');
|
||||
|
||||
var PhoneInput = React.createClass({
|
||||
const PhoneInput = createReactClass({ // eslint-disable-line react/prefer-es6-class
|
||||
displayName: 'PhoneInput',
|
||||
propTypes: {
|
||||
className: PropTypes.string,
|
||||
defaultCountry: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
name: PropTypes.string,
|
||||
onChange: PropTypes.func
|
||||
},
|
||||
mixins: [
|
||||
FormsyMixin,
|
||||
ComponentMixin
|
||||
|
@ -31,29 +41,34 @@ var PhoneInput = React.createClass({
|
|||
defaultCountry: 'us'
|
||||
};
|
||||
},
|
||||
onChangeInput: function (number, country) {
|
||||
var value = {national_number: number, country_code: country};
|
||||
handleChangeInput: function (number, country) {
|
||||
const value = {
|
||||
national_number: number,
|
||||
country_code: country
|
||||
};
|
||||
this.setValue(value);
|
||||
this.props.onChange(this.props.name, value);
|
||||
},
|
||||
render: function () {
|
||||
var defaultCountry = PhoneInput.getDefaultProps().defaultCountry;
|
||||
let defaultCountry = PhoneInput.getDefaultProps().defaultCountry;
|
||||
if (allIso2.indexOf(this.props.defaultCountry.toLowerCase()) !== -1) {
|
||||
defaultCountry = this.props.defaultCountry.toLowerCase();
|
||||
defaultCountry = this.props.defaultCountry.toLowerCase();
|
||||
}
|
||||
return (
|
||||
<Row {... this.getRowProperties()}
|
||||
htmlFor={this.getId()}
|
||||
rowClassName={classNames('phone-input', this.props.className)}
|
||||
<Row
|
||||
htmlFor={this.getId()}
|
||||
rowClassName={classNames('phone-input', this.props.className)}
|
||||
{...this.getRowProperties()}
|
||||
>
|
||||
<div className="input-group">
|
||||
<ReactPhoneInput className="form-control"
|
||||
{... this.props}
|
||||
defaultCountry={defaultCountry}
|
||||
onChange={this.onChangeInput}
|
||||
id={this.getId()}
|
||||
label={null}
|
||||
disabled={this.isFormDisabled() || this.props.disabled}
|
||||
<ReactPhoneInput
|
||||
className="form-control"
|
||||
defaultCountry={defaultCountry}
|
||||
disabled={this.isFormDisabled() || this.props.disabled}
|
||||
id={this.getId()}
|
||||
label={null}
|
||||
onChange={this.handleChangeInput}
|
||||
{...omit(this.props, ['className', 'disabled', 'onChange'])}
|
||||
/>
|
||||
{this.renderHelp()}
|
||||
{this.renderErrorMessage()}
|
||||
|
@ -63,7 +78,7 @@ var PhoneInput = React.createClass({
|
|||
}
|
||||
});
|
||||
|
||||
var phoneValidationHOC = validationHOCFactory({
|
||||
const phoneValidationHOC = validationHOCFactory({
|
||||
isPhone: <intl.FormattedMessage id="teacherRegistration.validationPhoneNumber" />
|
||||
});
|
||||
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
var classNames = require('classnames');
|
||||
var FRCRadioGroup = require('formsy-react-components').RadioGroup;
|
||||
var React = require('react');
|
||||
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
|
||||
var inputHOC = require('./input-hoc.jsx');
|
||||
const classNames = require('classnames');
|
||||
const FRCRadioGroup = require('formsy-react-components').RadioGroup;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
|
||||
const inputHOC = require('./input-hoc.jsx');
|
||||
|
||||
require('./row.scss');
|
||||
require('./radio-group.scss');
|
||||
|
||||
var RadioGroup = React.createClass({
|
||||
type: 'RadioGroup',
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'radio-group',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<FRCRadioGroup {... this.props} className={classes} />
|
||||
);
|
||||
}
|
||||
});
|
||||
const RadioGroup = props => (
|
||||
<FRCRadioGroup
|
||||
className={classNames('radio-group', props.className)}
|
||||
{... props}
|
||||
/>
|
||||
);
|
||||
|
||||
RadioGroup.propTypes = {
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = inputHOC(defaultValidationHOC(RadioGroup));
|
||||
|
|
|
@ -1,33 +1,31 @@
|
|||
var classNames = require('classnames');
|
||||
var defaults = require('lodash.defaultsdeep');
|
||||
var FRCSelect = require('formsy-react-components').Select;
|
||||
var React = require('react');
|
||||
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
|
||||
var inputHOC = require('./input-hoc.jsx');
|
||||
const classNames = require('classnames');
|
||||
const defaults = require('lodash.defaultsdeep');
|
||||
const FRCSelect = require('formsy-react-components').Select;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
|
||||
const inputHOC = require('./input-hoc.jsx');
|
||||
|
||||
require('./row.scss');
|
||||
require('./select.scss');
|
||||
|
||||
var Select = React.createClass({
|
||||
type: 'Select',
|
||||
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>
|
||||
);
|
||||
const Select = props => {
|
||||
if (props.required && !props.value) {
|
||||
props = defaults({}, props, {value: props.options[0].value});
|
||||
}
|
||||
});
|
||||
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));
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
var classNames = require('classnames');
|
||||
var FRCTextarea = require('formsy-react-components').Textarea;
|
||||
var React = require('react');
|
||||
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
|
||||
var inputHOC = require('./input-hoc.jsx');
|
||||
const classNames = require('classnames');
|
||||
const FRCTextarea = require('formsy-react-components').Textarea;
|
||||
const omit = require('lodash.omit');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
|
||||
const inputHOC = require('./input-hoc.jsx');
|
||||
|
||||
require('./row.scss');
|
||||
require('./textarea.scss');
|
||||
|
||||
var TextArea = React.createClass({
|
||||
type: 'TextArea',
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'textarea-row',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<FRCTextarea {... this.props}
|
||||
className="textarea"
|
||||
rowClassName={classes} />
|
||||
);
|
||||
}
|
||||
});
|
||||
const TextArea = props => (
|
||||
<FRCTextarea
|
||||
className="textarea"
|
||||
rowClassName={classNames('textarea-row', props.className)}
|
||||
{...omit(props, ['className'])}
|
||||
/>
|
||||
);
|
||||
|
||||
TextArea.propTypes = {
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = inputHOC(defaultValidationHOC(TextArea));
|
||||
|
|
|
@ -1,48 +1,46 @@
|
|||
var defaults = require('lodash.defaultsdeep');
|
||||
var intl = require('../../lib/intl.jsx');
|
||||
var libphonenumber = require('google-libphonenumber');
|
||||
var phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance();
|
||||
var React = require('react');
|
||||
|
||||
module.exports = {};
|
||||
const defaults = require('lodash.defaultsdeep');
|
||||
const intl = require('../../lib/intl.jsx');
|
||||
const libphonenumber = require('google-libphonenumber');
|
||||
const omit = require('lodash.omit');
|
||||
const phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance();
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
module.exports.validations = {
|
||||
notEquals: function (values, value, neq) {
|
||||
return value !== neq;
|
||||
},
|
||||
notEqualsField: function (values, value, field) {
|
||||
return value !== values[field];
|
||||
},
|
||||
isPhone: function (values, value) {
|
||||
notEquals: (values, value, neq) => (value !== neq),
|
||||
notEqualsField: (values, value, field) => (value !== values[field]),
|
||||
isPhone: (values, value) => {
|
||||
if (typeof value === 'undefined') return true;
|
||||
if (value && value.national_number === '+') return true;
|
||||
try {
|
||||
var parsed = phoneNumberUtil.parse(value.national_number, value.country_code.iso2);
|
||||
const parsed = phoneNumberUtil.parse(value.national_number, value.country_code.iso2);
|
||||
return phoneNumberUtil.isValidNumber(parsed);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
return phoneNumberUtil.isValidNumber(parsed);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.validations.notEqualsUsername = module.exports.validations.notEquals;
|
||||
|
||||
module.exports.validationHOCFactory = function (defaultValidationErrors) {
|
||||
return function (Component) {
|
||||
var ValidatedComponent = React.createClass({
|
||||
render: function () {
|
||||
var validationErrors = defaults(
|
||||
{},
|
||||
defaultValidationErrors,
|
||||
this.props.validationErrors
|
||||
);
|
||||
return (
|
||||
<Component {...this.props} validationErrors={validationErrors} />
|
||||
);
|
||||
}
|
||||
});
|
||||
return ValidatedComponent;
|
||||
module.exports.validationHOCFactory = defaultValidationErrors => (Component => {
|
||||
const ValidatedComponent = props => (
|
||||
<Component
|
||||
validationErrors={defaults(
|
||||
{},
|
||||
defaultValidationErrors,
|
||||
props.validationErrors
|
||||
)}
|
||||
{...omit(props, ['validationErrors'])}
|
||||
/>
|
||||
);
|
||||
|
||||
ValidatedComponent.propTypes = {
|
||||
validationErrors: PropTypes.object // eslint-disable-line react/forbid-prop-types
|
||||
};
|
||||
};
|
||||
|
||||
return ValidatedComponent;
|
||||
});
|
||||
|
||||
module.exports.defaultValidationHOC = module.exports.validationHOCFactory({
|
||||
isDefaultRequiredValue: <intl.FormattedMessage id="form.validationRequired" />
|
||||
|
|
|
@ -1,79 +1,68 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var Thumbnail = require('../thumbnail/thumbnail.jsx');
|
||||
var FlexRow = require('../flex-row/flex-row.jsx');
|
||||
const Thumbnail = require('../thumbnail/thumbnail.jsx');
|
||||
const FlexRow = require('../flex-row/flex-row.jsx');
|
||||
|
||||
require('./grid.scss');
|
||||
|
||||
var Grid = React.createClass({
|
||||
type: 'Grid',
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
items: require('./grid.json'),
|
||||
itemType: 'projects',
|
||||
showLoves: false,
|
||||
showFavorites: false,
|
||||
showRemixes: false,
|
||||
showViews: false,
|
||||
showAvatar: false
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'grid',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<div className={classes}>
|
||||
<FlexRow>
|
||||
{this.props.items.map(function (item, key) {
|
||||
var href = '/' + this.props.itemType + '/' + item.id + '/';
|
||||
const Grid = props => (
|
||||
<div className={classNames('grid', props.className)}>
|
||||
<FlexRow>
|
||||
{props.items.map((item, key) => {
|
||||
const href = `/${props.itemType}/${item.id}/`;
|
||||
if (props.itemType === 'projects') {
|
||||
return (
|
||||
<Thumbnail
|
||||
avatar={`https://cdn2.scratch.mit.edu/get_image/user/${item.author.id}_32x32.png`}
|
||||
creator={item.author.username}
|
||||
favorites={item.stats.favorites}
|
||||
href={href}
|
||||
key={key}
|
||||
loves={item.stats.loves}
|
||||
remixes={item.stats.remixes}
|
||||
showAvatar={props.showAvatar}
|
||||
showFavorites={props.showFavorites}
|
||||
showLoves={props.showLoves}
|
||||
showRemixes={props.showRemixes}
|
||||
showViews={props.showViews}
|
||||
src={item.image}
|
||||
title={item.title}
|
||||
type={'project'}
|
||||
views={item.stats.views}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Thumbnail
|
||||
href={href}
|
||||
key={key}
|
||||
owner={item.owner}
|
||||
src={item.image}
|
||||
title={item.title}
|
||||
type={'gallery'}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</FlexRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (this.props.itemType == 'projects') {
|
||||
return (
|
||||
<Thumbnail
|
||||
key={key}
|
||||
showLoves={this.props.showLoves}
|
||||
showFavorites={this.props.showFavorites}
|
||||
showRemixes={this.props.showRemixes}
|
||||
showViews={this.props.showViews}
|
||||
showAvatar={this.props.showAvatar}
|
||||
type={'project'}
|
||||
href={href}
|
||||
title={item.title}
|
||||
src={item.image}
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
||||
Grid.propTypes = {
|
||||
className: PropTypes.string,
|
||||
itemType: PropTypes.string,
|
||||
items: PropTypes.arrayOf(PropTypes.object)
|
||||
};
|
||||
|
||||
Grid.defaultProps = {
|
||||
items: require('./grid.json'),
|
||||
itemType: 'projects',
|
||||
showLoves: false,
|
||||
showFavorites: false,
|
||||
showRemixes: false,
|
||||
showViews: false,
|
||||
showAvatar: false
|
||||
};
|
||||
|
||||
module.exports = Grid;
|
||||
|
|
|
@ -1,39 +1,34 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
var TitleBanner = require('../../components/title-banner/title-banner.jsx');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const TitleBanner = require('../../components/title-banner/title-banner.jsx');
|
||||
|
||||
require('./informationpage.scss');
|
||||
|
||||
/**
|
||||
/*
|
||||
* Container for a table of contents
|
||||
* alongside a long body of text
|
||||
*/
|
||||
var InformationPage = React.createClass({
|
||||
type: 'InformationPage',
|
||||
propTypes: {
|
||||
title: React.PropTypes.string.isRequired
|
||||
},
|
||||
render: function () {
|
||||
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>
|
||||
const InformationPage = props => (
|
||||
<div className="information-page">
|
||||
<TitleBanner className="masthead">
|
||||
<div className="inner">
|
||||
<h1 className="title-banner-h1">
|
||||
{props.title}
|
||||
</h1>
|
||||
</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;
|
||||
|
|
|
@ -1,111 +1,128 @@
|
|||
var connect = require('react-redux').connect;
|
||||
var React = require('react');
|
||||
const bindAll = require('lodash.bindall');
|
||||
const connect = require('react-redux').connect;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var sessionActions = require('../../redux/session.js');
|
||||
const sessionActions = require('../../redux/session.js');
|
||||
|
||||
var IframeModal = require('../modal/iframe/modal.jsx');
|
||||
var Registration = require('../registration/registration.jsx');
|
||||
const IframeModal = require('../modal/iframe/modal.jsx');
|
||||
const Registration = require('../registration/registration.jsx');
|
||||
|
||||
require('./intro.scss');
|
||||
|
||||
var Intro = React.createClass({
|
||||
type: 'Intro',
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
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: {}
|
||||
};
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
class Intro extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleShowVideo',
|
||||
'handleCloseVideo',
|
||||
'handleJoinClick',
|
||||
'handleCloseRegistration',
|
||||
'handleCompleteRegistration'
|
||||
]);
|
||||
this.state = {
|
||||
videoOpen: false
|
||||
};
|
||||
},
|
||||
showVideo: function () {
|
||||
}
|
||||
handleShowVideo () {
|
||||
this.setState({videoOpen: true});
|
||||
},
|
||||
closeVideo: function () {
|
||||
}
|
||||
handleCloseVideo () {
|
||||
this.setState({videoOpen: false});
|
||||
},
|
||||
handleJoinClick: function (e) {
|
||||
}
|
||||
handleJoinClick (e) {
|
||||
e.preventDefault();
|
||||
this.setState({'registrationOpen': true});
|
||||
},
|
||||
closeRegistration: function () {
|
||||
this.setState({'registrationOpen': false});
|
||||
},
|
||||
completeRegistration: function () {
|
||||
this.setState({registrationOpen: true});
|
||||
}
|
||||
handleCloseRegistration () {
|
||||
this.setState({registrationOpen: false});
|
||||
}
|
||||
handleCompleteRegistration () {
|
||||
this.props.dispatch(sessionActions.refreshSession());
|
||||
this.closeRegistration();
|
||||
},
|
||||
render: function () {
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<div className="intro">
|
||||
<div className="content">
|
||||
<h1 dangerouslySetInnerHTML={{__html: this.props.messages['intro.tagLine']}}>
|
||||
</h1>
|
||||
<h1
|
||||
dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
|
||||
__html: this.props.messages['intro.tagLine']
|
||||
}}
|
||||
/>
|
||||
<div className="sprites">
|
||||
<a className="sprite sprite-1" href="/projects/editor/?tip_bar=getStarted">
|
||||
<a
|
||||
className="sprite sprite-1"
|
||||
href="/projects/editor/?tip_bar=getStarted"
|
||||
>
|
||||
<img
|
||||
alt="Scratch Cat"
|
||||
className="costume costume-1"
|
||||
src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-a.png"
|
||||
alt="Scratch Cat" />
|
||||
/>
|
||||
<img
|
||||
alt="Scratch Cat"
|
||||
className="costume costume-2"
|
||||
src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-b.png"
|
||||
alt="Scratch Cat" />
|
||||
<div className="circle"></div>
|
||||
/>
|
||||
<div className="circle" />
|
||||
<div className="text">
|
||||
{this.props.messages['intro.tryItOut']}
|
||||
</div>
|
||||
</a>
|
||||
<a className="sprite sprite-2" href="/starter_projects/">
|
||||
<a
|
||||
className="sprite sprite-2"
|
||||
href="/starter_projects/"
|
||||
>
|
||||
<img
|
||||
alt="Tera"
|
||||
className="costume costume-1"
|
||||
src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-a.png"
|
||||
alt="Tera" />
|
||||
/>
|
||||
<img
|
||||
alt="Tera"
|
||||
className="costume costume-2"
|
||||
src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-b.png"
|
||||
alt="Tera" />
|
||||
<div className="circle"></div>
|
||||
/>
|
||||
<div className="circle" />
|
||||
<div className="text">
|
||||
{this.props.messages['intro.seeExamples']}
|
||||
</div>
|
||||
</a>
|
||||
<a className="sprite sprite-3" href="#" onClick={this.handleJoinClick}>
|
||||
<a
|
||||
className="sprite sprite-3"
|
||||
href="#"
|
||||
onClick={this.handleJoinClick}
|
||||
>
|
||||
<img
|
||||
alt="Gobo"
|
||||
className="costume costume-1"
|
||||
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-a.png"
|
||||
alt="Gobo" />
|
||||
/>
|
||||
<img
|
||||
alt="Gobo"
|
||||
className="costume costume-2"
|
||||
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-b.png"
|
||||
alt="Gobo" />
|
||||
<div className="circle"></div>
|
||||
/>
|
||||
<div className="circle" />
|
||||
<div className="text">
|
||||
{this.props.messages['intro.joinScratch']}
|
||||
</div>
|
||||
<div className="text subtext">{this.props.messages['intro.itsFree']}</div>
|
||||
</a>
|
||||
<Registration key="registration"
|
||||
isOpen={this.state.registrationOpen}
|
||||
onRequestClose={this.closeRegistration}
|
||||
onRegistrationDone={this.completeRegistration} />
|
||||
<Registration
|
||||
isOpen={this.state.registrationOpen}
|
||||
key="registration"
|
||||
onRegistrationDone={this.handleCompleteRegistration}
|
||||
onRequestClose={this.handleCloseRegistration}
|
||||
/>
|
||||
</div>
|
||||
<div className="description"
|
||||
dangerouslySetInnerHTML={{__html: this.props.messages['intro.description']}}></div>
|
||||
<div
|
||||
className="description"
|
||||
dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
|
||||
__html: this.props.messages['intro.description']
|
||||
}}
|
||||
/>
|
||||
<div className="links">
|
||||
<a href="/about/">
|
||||
{this.props.messages['intro.aboutScratch']}
|
||||
|
@ -113,33 +130,70 @@ var Intro = React.createClass({
|
|||
<a href="/educators/">
|
||||
{this.props.messages['intro.forEducators']}
|
||||
</a>
|
||||
<a className="last" href="/parents/">
|
||||
<a
|
||||
className="last"
|
||||
href="/parents/"
|
||||
>
|
||||
{this.props.messages['intro.forParents']}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="video">
|
||||
<div className="play-button" onClick={this.showVideo}></div>
|
||||
<img src="//cdn.scratch.mit.edu/scratchr2/static/images/hp-video-screenshot.png"
|
||||
alt="Intro Video" />
|
||||
<div
|
||||
className="play-button"
|
||||
onClick={this.handleShowVideo}
|
||||
/>
|
||||
<img
|
||||
alt="Intro Video"
|
||||
src="//cdn.scratch.mit.edu/scratchr2/static/images/hp-video-screenshot.png"
|
||||
/>
|
||||
</div>
|
||||
<IframeModal
|
||||
className="mod-intro-video"
|
||||
isOpen={this.state.videoOpen}
|
||||
onRequestClose={this.closeVideo}
|
||||
src="//player.vimeo.com/video/65583694?title=0&byline=0&portrait=0"
|
||||
onRequestClose={this.handleCloseVideo}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var mapStateToProps = function (state) {
|
||||
return {
|
||||
session: state.session
|
||||
};
|
||||
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
|
||||
})
|
||||
};
|
||||
|
||||
var ConnectedIntro = connect(mapStateToProps)(Intro);
|
||||
Intro.defaultProps = {
|
||||
messages: {
|
||||
'intro.aboutScratch': 'ABOUT SCRATCH',
|
||||
'intro.forEducators': 'FOR EDUCATORS',
|
||||
'intro.forParents': 'FOR PARENTS',
|
||||
'intro.itsFree': 'it\'s free!',
|
||||
'intro.joinScratch': 'JOIN SCRATCH',
|
||||
'intro.seeExamples': 'SEE EXAMPLES',
|
||||
'intro.tagLine': 'Create stories, games, and animations<br /> Share with others around the world',
|
||||
'intro.tryItOut': 'TRY IT OUT',
|
||||
'intro.description': 'A creative learning community with <span class="project-count"> ' +
|
||||
'over 14 million </span>projects shared'
|
||||
},
|
||||
session: {}
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
session: state.session
|
||||
});
|
||||
|
||||
const ConnectedIntro = connect(mapStateToProps)(Intro);
|
||||
|
||||
module.exports = ConnectedIntro;
|
||||
|
|
|
@ -1,46 +1,57 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const bindAll = require('lodash.bindall');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var jar = require('../../lib/jar.js');
|
||||
var languages = require('../../../languages.json');
|
||||
var Form = require('../forms/form.jsx');
|
||||
var Select = require('../forms/select.jsx');
|
||||
const jar = require('../../lib/jar.js');
|
||||
const languages = require('../../../languages.json');
|
||||
const Form = require('../forms/form.jsx');
|
||||
const Select = require('../forms/select.jsx');
|
||||
|
||||
require('./languagechooser.scss');
|
||||
|
||||
/**
|
||||
* Footer dropdown menu that allows one to change their language.
|
||||
*/
|
||||
var LanguageChooser = React.createClass({
|
||||
type: 'LanguageChooser',
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
languages: languages,
|
||||
locale: 'en'
|
||||
};
|
||||
},
|
||||
onSetLanguage: function (name, value) {
|
||||
class LanguageChooser extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleSetLanguage'
|
||||
]);
|
||||
}
|
||||
handleSetLanguage (name, value) {
|
||||
jar.set('scratchlanguage', value);
|
||||
window.location.reload();
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'language-chooser',
|
||||
this.props.className
|
||||
);
|
||||
var languageOptions = Object.keys(this.props.languages).map(function (value) {
|
||||
return {value: value, label: this.props.languages[value]};
|
||||
}.bind(this));
|
||||
}
|
||||
render () {
|
||||
const languageOptions = Object.keys(this.props.languages).map(value => ({
|
||||
value: value,
|
||||
label: this.props.languages[value]
|
||||
}));
|
||||
return (
|
||||
<Form className={classes}>
|
||||
<Select name="language"
|
||||
options={languageOptions}
|
||||
value={this.props.locale}
|
||||
onChange={this.onSetLanguage}
|
||||
required />
|
||||
<Form className={classNames('language-chooser', this.props.className)}>
|
||||
<Select
|
||||
required
|
||||
name="language"
|
||||
options={languageOptions}
|
||||
value={this.props.locale}
|
||||
onChange={this.handleSetLanguage}
|
||||
/>
|
||||
</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;
|
||||
|
|
|
@ -1,66 +1,108 @@
|
|||
var React = require('react');
|
||||
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const bindAll = require('lodash.bindall');
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var log = require('../../lib/log.js');
|
||||
const log = require('../../lib/log.js');
|
||||
|
||||
var Form = require('../forms/form.jsx');
|
||||
var Input = require('../forms/input.jsx');
|
||||
var Button = require('../forms/button.jsx');
|
||||
var Spinner = require('../spinner/spinner.jsx');
|
||||
const Form = require('../forms/form.jsx');
|
||||
const Input = require('../forms/input.jsx');
|
||||
const Button = require('../forms/button.jsx');
|
||||
const Spinner = require('../spinner/spinner.jsx');
|
||||
|
||||
require('./login.scss');
|
||||
|
||||
var Login = React.createClass({
|
||||
type: 'Login',
|
||||
propTypes: {
|
||||
onLogIn: React.PropTypes.func,
|
||||
error: React.PropTypes.string
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
class Login extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleSubmit'
|
||||
]);
|
||||
this.state = {
|
||||
waiting: false
|
||||
};
|
||||
},
|
||||
handleSubmit: function (formData) {
|
||||
}
|
||||
handleSubmit (formData) {
|
||||
this.setState({waiting: true});
|
||||
this.props.onLogIn(formData, function (err) {
|
||||
this.props.onLogIn(formData, err => {
|
||||
if (err) log.error(err);
|
||||
this.setState({waiting: false});
|
||||
}.bind(this));
|
||||
},
|
||||
render: function () {
|
||||
var error;
|
||||
});
|
||||
}
|
||||
render () {
|
||||
let error;
|
||||
if (this.props.error) {
|
||||
error = <div className="error">{this.props.error}</div>;
|
||||
}
|
||||
return (
|
||||
<div className="login">
|
||||
<Form onSubmit={this.handleSubmit}>
|
||||
<label htmlFor="username" key="usernameLabel">
|
||||
<FormattedMessage id='general.username' />
|
||||
<label
|
||||
htmlFor="username"
|
||||
key="usernameLabel"
|
||||
>
|
||||
<FormattedMessage id="general.username" />
|
||||
</label>
|
||||
<Input type="text" ref="username" name="username" maxLength="30" key="usernameInput" required />
|
||||
<label htmlFor="password" key="passwordLabel">
|
||||
<FormattedMessage id='general.password' />
|
||||
<Input
|
||||
required
|
||||
key="usernameInput"
|
||||
maxLength="30"
|
||||
name="username"
|
||||
ref={input => {
|
||||
this.username = input;
|
||||
}}
|
||||
type="text"
|
||||
/>
|
||||
<label
|
||||
htmlFor="password"
|
||||
key="passwordLabel"
|
||||
>
|
||||
<FormattedMessage id="general.password" />
|
||||
</label>
|
||||
<Input type="password" ref="password" name="password" key="passwordInput" required />
|
||||
<Input
|
||||
required
|
||||
key="passwordInput"
|
||||
name="password"
|
||||
ref={input => {
|
||||
this.password = input;
|
||||
}}
|
||||
type="password"
|
||||
/>
|
||||
{this.state.waiting ? [
|
||||
<Button className="submit-button white" type="submit" disabled="disabled" key="submitButton">
|
||||
<Button
|
||||
className="submit-button white"
|
||||
disabled="disabled"
|
||||
key="submitButton"
|
||||
type="submit"
|
||||
>
|
||||
<Spinner />
|
||||
</Button>
|
||||
] : [
|
||||
<Button className="submit-button white" type="submit" key="submitButton">
|
||||
<FormattedMessage id='general.signIn' />
|
||||
<Button
|
||||
className="submit-button white"
|
||||
key="submitButton"
|
||||
type="submit"
|
||||
>
|
||||
<FormattedMessage id="general.signIn" />
|
||||
</Button>
|
||||
]}
|
||||
<a className="right" href="/accounts/password_reset/" key="passwordResetLink">
|
||||
<FormattedMessage id='login.needHelp' />
|
||||
<a
|
||||
className="right"
|
||||
href="/accounts/password_reset/"
|
||||
key="passwordResetLink"
|
||||
>
|
||||
<FormattedMessage id="login.needHelp" />
|
||||
</a>
|
||||
{error}
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Login.propTypes = {
|
||||
error: PropTypes.string,
|
||||
onLogIn: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = Login;
|
||||
|
|
|
@ -1,60 +1,68 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
var MediaQuery = require('react-responsive');
|
||||
var frameless = require('../../lib/frameless');
|
||||
const bindAll = require('lodash.bindall');
|
||||
const classNames = require('classnames');
|
||||
const MediaQuery = require('react-responsive').default;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const frameless = require('../../lib/frameless');
|
||||
|
||||
require('./masonrygrid.scss');
|
||||
|
||||
var MasonryGrid = React.createClass({
|
||||
type: 'MasonryGrid',
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
as: 'div'
|
||||
};
|
||||
},
|
||||
reorderColumns: function (items, cols) {
|
||||
var a1 = [];
|
||||
var a2 = [];
|
||||
var a3 = [];
|
||||
var i = 0;
|
||||
//only implemented for 2 and 3 columns so far - easy to extend if needed
|
||||
class MasonryGrid extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'reorderColumns'
|
||||
]);
|
||||
}
|
||||
reorderColumns (items, cols) {
|
||||
const a1 = [];
|
||||
const a2 = [];
|
||||
const a3 = [];
|
||||
let i = 0;
|
||||
// only implemented for 2 and 3 columns so far - easy to extend if needed
|
||||
if (cols > 1 && cols < 4) {
|
||||
for (i=0;i<items.length;i++){
|
||||
var col = (i+cols)%cols;
|
||||
for (i = 0; i < items.length; i++){
|
||||
const col = (i + cols) % cols;
|
||||
if (col === 0) {
|
||||
a1.push(items[i]);
|
||||
}
|
||||
else if (col === 1) {
|
||||
} else if (col === 1) {
|
||||
a2.push(items[i]);
|
||||
}
|
||||
else if (col === 2) {
|
||||
} else if (col === 2) {
|
||||
a3.push(items[i]);
|
||||
}
|
||||
}
|
||||
return a1.concat(a2,a3);
|
||||
} else {
|
||||
return items;
|
||||
return a1.concat(a2, a3);
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'masonry',
|
||||
this.props.className
|
||||
);
|
||||
return items;
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<this.props.as className={classes}>
|
||||
<MediaQuery maxWidth={frameless.tablet - 1} >
|
||||
<this.props.as className={classNames('masonry', this.props.className)}>
|
||||
<MediaQuery maxWidth={frameless.tablet - 1}>
|
||||
{this.props.children}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={frameless.tablet} maxWidth={frameless.desktop - 1} >
|
||||
<MediaQuery
|
||||
maxWidth={frameless.desktop - 1}
|
||||
minWidth={frameless.tablet}
|
||||
>
|
||||
{this.reorderColumns(this.props.children, 2)}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={frameless.desktop} >
|
||||
<MediaQuery minWidth={frameless.desktop}>
|
||||
{this.reorderColumns(this.props.children, 3)}
|
||||
</MediaQuery>
|
||||
</this.props.as>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
MasonryGrid.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
MasonryGrid.defaultProps = {
|
||||
as: 'div'
|
||||
};
|
||||
|
||||
module.exports = MasonryGrid;
|
||||
|
|
|
@ -1,39 +1,53 @@
|
|||
var React = require('react');
|
||||
const bindAll = require('lodash.bindall');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const Box = require('../box/box.jsx');
|
||||
const LegacyCarousel = require('../carousel/legacy-carousel.jsx');
|
||||
const IframeModal = require('../modal/iframe/modal.jsx');
|
||||
const NestedCarousel = require('../nestedcarousel/nestedcarousel.jsx');
|
||||
|
||||
require('./microworld.scss');
|
||||
|
||||
var Box = require('../box/box.jsx');
|
||||
var LegacyCarousel = require('../carousel/legacy-carousel.jsx');
|
||||
var IframeModal = require('../modal/iframe/modal.jsx');
|
||||
var NestedCarousel = require('../nestedcarousel/nestedcarousel.jsx');
|
||||
|
||||
var Microworld = React.createClass({
|
||||
type: 'Microworld',
|
||||
propTypes: {
|
||||
microworldData: React.PropTypes.node.isRequired
|
||||
},
|
||||
markVideoOpen: function (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 */}
|
||||
|
||||
var videoOpenArr = this.state.videoOpen;
|
||||
videoOpenArr[key] = true;
|
||||
this.setState({videoOpen: videoOpenArr});
|
||||
},
|
||||
markVideoClosed: function (key) {
|
||||
{/* When a video's x is clicked, mark it as closed, so the video Modal will disappear.
|
||||
Key is the number of the video, so distinguish between different videos on the page */}
|
||||
var videoOpenArr = this.state.videoOpen;
|
||||
videoOpenArr[key] = false;
|
||||
this.setState({videoOpen: videoOpenArr});
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
class Microworld extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'markVideoOpen',
|
||||
'markVideoClosed',
|
||||
'renderVideos',
|
||||
'renderVideo',
|
||||
'renderEditorWindow',
|
||||
'renderTips',
|
||||
'renderStarterProject',
|
||||
'renderProjectIdeasBox',
|
||||
'renderForum',
|
||||
'renderDesignStudio'
|
||||
]);
|
||||
this.state = {
|
||||
videoOpen: {}
|
||||
};
|
||||
},
|
||||
renderVideos: function () {
|
||||
var videos = this.props.microworldData.videos;
|
||||
}
|
||||
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;
|
||||
this.setState({videoOpen: videoOpenArr});
|
||||
}
|
||||
markVideoClosed (key) {
|
||||
/*
|
||||
When a video's x is clicked, mark it as closed, so the video Modal will disappear.
|
||||
Key is the number of the video, so distinguish between different videos on the page
|
||||
*/
|
||||
const videoOpenArr = this.state.videoOpen;
|
||||
videoOpenArr[key] = false;
|
||||
this.setState({videoOpen: videoOpenArr});
|
||||
}
|
||||
renderVideos () {
|
||||
const videos = this.props.microworldData.videos;
|
||||
if (!videos || videos.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -48,26 +62,32 @@ var Microworld = React.createClass({
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
renderVideo: function (video, key) {
|
||||
}
|
||||
renderVideo (video, key) {
|
||||
return (
|
||||
<div>
|
||||
<div className="video">
|
||||
<div className="play-button" onClick={this.markVideoOpen.bind(this, key)}>
|
||||
</div>
|
||||
<div
|
||||
className="play-button"
|
||||
onClick={() => { // eslint-disable-line react/jsx-no-bind
|
||||
this.markVideoOpen(key);
|
||||
}}
|
||||
/>
|
||||
<img src={video.image} />
|
||||
</div>
|
||||
<IframeModal
|
||||
className="mod-microworld-video"
|
||||
isOpen={this.state.videoOpen[key]}
|
||||
onRequestClose={this.markVideoClosed.bind(this, key)}
|
||||
src={video.link}
|
||||
onRequestClose={() => { // eslint-disable-line react/jsx-no-bind
|
||||
this.markVideoClosed(key);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
renderEditorWindow: function () {
|
||||
var projectId = this.props.microworldData.microworld_project_id;
|
||||
}
|
||||
renderEditorWindow () {
|
||||
const projectId = this.props.microworldData.microworld_project_id;
|
||||
|
||||
if (!projectId) {
|
||||
return null;
|
||||
|
@ -75,30 +95,37 @@ var Microworld = React.createClass({
|
|||
return (
|
||||
<div className="editor section">
|
||||
<h1 className="sectionheader">Start Creating!</h1>
|
||||
<iframe src={'//scratch.mit.edu/projects/embed-editor/' + projectId + '/?isMicroworld=true'}
|
||||
frameBorder="0"> </iframe>
|
||||
<iframe
|
||||
frameBorder="0"
|
||||
src={`//scratch.mit.edu/projects/embed-editor/${projectId}/?isMicroworld=true`}
|
||||
/>
|
||||
{this.renderTips()}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
renderTips: function () {
|
||||
var tips = this.props.microworldData.tips;
|
||||
}
|
||||
renderTips () {
|
||||
const tips = this.props.microworldData.tips;
|
||||
if (!tips || tips.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="box nestedcarousel">
|
||||
<div className="box-header">
|
||||
</div>
|
||||
<div className="box-header" />
|
||||
<div className="box-content">
|
||||
<NestedCarousel items={tips} settings={{slidesToShow:1,slidesToScroll:1}}/>
|
||||
<NestedCarousel
|
||||
items={tips}
|
||||
settings={{
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
renderStarterProject: function () {
|
||||
var starterProjects = this.props.microworldData.starter_projects;
|
||||
);
|
||||
}
|
||||
renderStarterProject () {
|
||||
const starterProjects = this.props.microworldData.starter_projects;
|
||||
if (!starterProjects || starterProjects.length <= 0){
|
||||
return null;
|
||||
}
|
||||
|
@ -107,39 +134,42 @@ var Microworld = React.createClass({
|
|||
<div className="project-ideas">
|
||||
<h1 className="sectionheader">Check out ideas for more projects</h1>
|
||||
<Box
|
||||
key="starter_projects"
|
||||
title="More Starter Projects"
|
||||
key="starter_projects">
|
||||
>
|
||||
<LegacyCarousel items={starterProjects} />
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
renderProjectIdeasBox: function () {
|
||||
var communityProjects = this.props.microworldData.community_projects;
|
||||
}
|
||||
renderProjectIdeasBox () {
|
||||
const communityProjects = this.props.microworldData.community_projects;
|
||||
if (!communityProjects || communityProjects.size <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var featured = communityProjects.featured_projects;
|
||||
var all = communityProjects.newest_projects;
|
||||
const featured = communityProjects.featured_projects;
|
||||
const all = communityProjects.newest_projects;
|
||||
|
||||
var rows = [];
|
||||
const rows = [];
|
||||
if (featured && featured.length > 0){
|
||||
rows.push(
|
||||
<Box
|
||||
key="community_featured_projects"
|
||||
title="Featured Community Projects"
|
||||
key="community_featured_projects">
|
||||
>
|
||||
<LegacyCarousel items={featured} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
if (all && all.length > 0) {
|
||||
rows.push(
|
||||
<Box
|
||||
title="All Community Projects"
|
||||
key="community_all_projects">
|
||||
<LegacyCarousel items={all} />
|
||||
</Box>
|
||||
key="community_all_projects"
|
||||
title="All Community Projects"
|
||||
>
|
||||
<LegacyCarousel items={all} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
if (rows.length <= 0) {
|
||||
|
@ -151,67 +181,88 @@ var Microworld = React.createClass({
|
|||
{rows}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
renderForum: function () {
|
||||
}
|
||||
renderForum () {
|
||||
if (!this.props.microworldData.show_forum) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="forum">
|
||||
<h1 className="sectionheader">Chat with others!</h1>
|
||||
<img src="/images/forum-image.png"/>
|
||||
</div>
|
||||
<div className="forum">
|
||||
<h1 className="sectionheader">Chat with others!</h1>
|
||||
<img src="/images/forum-image.png" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
renderDesignStudio: function () {
|
||||
var designChallenge = this.props.microworldData.design_challenge;
|
||||
}
|
||||
renderDesignStudio () {
|
||||
const designChallenge = this.props.microworldData.design_challenge;
|
||||
if (!designChallenge) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let studioHref = '';
|
||||
if (designChallenge.studio_id) {
|
||||
var studioHref = 'https://scratch.mit.edu//studios/' + designChallenge.studio_id + '/';
|
||||
studioHref = `https://scratch.mit.edu//studios/${designChallenge.studio_id}/`;
|
||||
}
|
||||
if (designChallenge.project_id) {
|
||||
return (
|
||||
<div className="side-by-side section">
|
||||
<h1 className="sectionheader">Join our Design Challenge!</h1>
|
||||
<div className="design-studio">
|
||||
<iframe src={'https://scratch.mit.edu/projects/' + designChallenge.project_id +
|
||||
'/#fullscreen'} frameBorder="0"> </iframe>
|
||||
<iframe
|
||||
frameBorder="0"
|
||||
src={`https://scratch.mit.edu/projects/${designChallenge.project_id}/#fullscreen`}
|
||||
/>
|
||||
</div>
|
||||
<div className="design-studio-projects">
|
||||
<Box title="Examples"
|
||||
key="scratch_design_studio"
|
||||
moreTitle={studioHref ? 'Visit the studio' : null}
|
||||
moreHref={studioHref ? studioHref : null}>
|
||||
<Box
|
||||
key="scratch_design_studio"
|
||||
moreHref={studioHref ? studioHref : null}
|
||||
moreTitle={studioHref ? 'Visit the studio' : null}
|
||||
title="Examples"
|
||||
>
|
||||
{/* The two carousels are used to show two rows of projects, one above the
|
||||
other. This should be probably be changed, to allow better scrolling. */}
|
||||
<LegacyCarousel settings={{slidesToShow:2,slidesToScroll:2}}
|
||||
items={this.props.microworldData.design_challenge.studio1} />
|
||||
<LegacyCarousel settings={{slidesToShow:2,slidesToScroll:2}}
|
||||
items={this.props.microworldData.design_challenge.studio2} />
|
||||
<LegacyCarousel
|
||||
items={this.props.microworldData.design_challenge.studio1}
|
||||
settings={{
|
||||
slidesToShow: 2,
|
||||
slidesToScroll: 2
|
||||
}}
|
||||
/>
|
||||
<LegacyCarousel
|
||||
items={this.props.microworldData.design_challenge.studio2}
|
||||
settings={{
|
||||
slidesToShow: 2,
|
||||
slidesToScroll: 2
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div className="section">
|
||||
<h1 className="sectionheader">Join our Design Challenge!</h1>
|
||||
<Box
|
||||
key="scratch_design_studio"
|
||||
moreHref={studioHref ? studioHref : null}
|
||||
moreTitle={studioHref ? 'Visit the studio' : null}
|
||||
title="design Challenge Projects"
|
||||
>
|
||||
<LegacyCarousel
|
||||
items={
|
||||
this.props.microworldData.design_challenge.studio1.concat(
|
||||
this.props.microworldData.design_challenge.studio2
|
||||
)
|
||||
}
|
||||
/>
|
||||
`</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<div className="inner microworld">
|
||||
<div className="top-banner section">
|
||||
|
@ -231,6 +282,10 @@ var Microworld = React.createClass({
|
|||
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Microworld.propTypes = {
|
||||
microworldData: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
module.exports = Microworld;
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
var classNames = require('classnames');
|
||||
var omit = require('lodash.omit');
|
||||
var React = require('react');
|
||||
var ReactModal = require('react-modal');
|
||||
const bindAll = require('lodash.bindall');
|
||||
const classNames = require('classnames');
|
||||
const omit = require('lodash.omit');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const ReactModal = require('react-modal');
|
||||
|
||||
require('./modal.scss');
|
||||
|
||||
|
@ -10,47 +12,55 @@ ReactModal.setAppElement(document.getElementById('view'));
|
|||
/**
|
||||
* Container for pop up windows (See: registration window)
|
||||
*/
|
||||
var Modal = React.createClass({
|
||||
type: 'Modal',
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
overlayClassName: React.PropTypes.string
|
||||
},
|
||||
requestClose: function () {
|
||||
class Modal extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleRequestClose'
|
||||
]);
|
||||
}
|
||||
handleRequestClose () {
|
||||
return this.modal.portal.requestClose();
|
||||
},
|
||||
render: function () {
|
||||
var modalClasses = classNames(
|
||||
'modal-content',
|
||||
this.props.className
|
||||
);
|
||||
var overlayClasses = classNames(
|
||||
'modal-overlay',
|
||||
this.props.overlayClassName
|
||||
);
|
||||
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<ReactModal
|
||||
ref={
|
||||
function (component) {
|
||||
this.modal = component;
|
||||
}.bind(this)
|
||||
}
|
||||
className={modalClasses}
|
||||
overlayClassName={overlayClasses}
|
||||
appElement={document.getElementById('view')}
|
||||
className={{
|
||||
base: classNames('modal-content', this.props.className),
|
||||
afterOpen: classNames('modal-content', this.props.className),
|
||||
beforeClose: classNames('modal-content', this.props.className)
|
||||
}}
|
||||
overlayClassName={{
|
||||
base: classNames('modal-overlay', this.props.overlayClassName),
|
||||
afterOpen: classNames('modal-overlay', this.props.overlayClassName),
|
||||
beforeClose: classNames('modal-overlay', this.props.overlayClassName)
|
||||
}}
|
||||
ref={component => {
|
||||
this.modal = component;
|
||||
}}
|
||||
{...omit(this.props, ['className', 'overlayClassName'])}
|
||||
>
|
||||
<div className="modal-content-close" onClick={this.requestClose}>
|
||||
<div
|
||||
className="modal-content-close"
|
||||
onClick={this.handleRequestClose}
|
||||
>
|
||||
<img
|
||||
alt="close-icon"
|
||||
className="modal-content-close-img"
|
||||
src="/svgs/modal/close-x.svg"
|
||||
alt="close-icon"
|
||||
/>
|
||||
</div>
|
||||
{this.props.children}
|
||||
</ReactModal>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Modal.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
overlayClassName: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = Modal;
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
background-color: transparentize($ui-blue, .3);
|
||||
}
|
||||
|
||||
.ReactModal__Content:focus {
|
||||
.modal-content:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,34 +1,28 @@
|
|||
var classNames = require('classnames');
|
||||
var omit = require('lodash.omit');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const omit = require('lodash.omit');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var Modal = require('../base/modal.jsx');
|
||||
const Modal = require('../base/modal.jsx');
|
||||
|
||||
require('./modal.scss');
|
||||
|
||||
var IframeModal = React.createClass({
|
||||
propTypes: {
|
||||
isOpen: React.PropTypes.bool,
|
||||
onRequestClose: React.PropTypes.func,
|
||||
className: React.PropTypes.string,
|
||||
componentRef: React.PropTypes.func,
|
||||
src: React.PropTypes.string
|
||||
},
|
||||
render: function () {
|
||||
var iframeClasses = classNames(
|
||||
'modal-content-iframe',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<Modal {...omit(this.props, ['src'])}>
|
||||
<iframe
|
||||
ref={this.props.componentRef}
|
||||
src={this.props.src}
|
||||
className={iframeClasses}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
});
|
||||
const IframeModal = props => (
|
||||
<Modal {...omit(props, ['src'])}>
|
||||
<iframe
|
||||
className={classNames('modal-content-iframe', props.className)}
|
||||
ref={props.componentRef}
|
||||
src={props.src}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
IframeModal.propTypes = {
|
||||
className: PropTypes.string,
|
||||
componentRef: PropTypes.func,
|
||||
isOpen: PropTypes.bool,
|
||||
onRequestClose: PropTypes.func,
|
||||
src: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = IframeModal;
|
||||
|
|
|
@ -1,121 +1,138 @@
|
|||
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||
var MediaQuery = require('react-responsive');
|
||||
var omit = require('lodash.omit');
|
||||
var React = require('react');
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const MediaQuery = require('react-responsive').default;
|
||||
const omit = require('lodash.omit');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var FlexRow = require('../../flex-row/flex-row.jsx');
|
||||
var frameless = require('../../../lib/frameless');
|
||||
var Modal = require('../base/modal.jsx');
|
||||
var TitleBanner = require('../../title-banner/title-banner.jsx');
|
||||
const FlexRow = require('../../flex-row/flex-row.jsx');
|
||||
const frameless = require('../../../lib/frameless');
|
||||
const Modal = require('../base/modal.jsx');
|
||||
const TitleBanner = require('../../title-banner/title-banner.jsx');
|
||||
|
||||
require('../../forms/button.scss');
|
||||
require('./modal.scss');
|
||||
|
||||
var TTTModal = React.createClass({
|
||||
propTypes: {
|
||||
title: React.PropTypes.string.isRequired,
|
||||
description: React.PropTypes.string.isRequired,
|
||||
tutorialLoc: React.PropTypes.string.isRequired,
|
||||
activityLoc: React.PropTypes.string.isRequired,
|
||||
guideLoc: React.PropTypes.string.isRequired,
|
||||
thumbUrl: React.PropTypes.string.isRequired,
|
||||
bannerUrl: React.PropTypes.string.isRequired
|
||||
},
|
||||
render: function () {
|
||||
var modalOmit = [
|
||||
'title',
|
||||
'description',
|
||||
'tutorialLoc',
|
||||
'activityLoc',
|
||||
'guideLoc',
|
||||
'thumbUrl',
|
||||
'bannerUrl'
|
||||
];
|
||||
return (
|
||||
<Modal
|
||||
className="mod-ttt"
|
||||
{...omit(this.props, modalOmit)}
|
||||
const TTTModal = props => (
|
||||
<Modal
|
||||
className="mod-ttt"
|
||||
{...omit(
|
||||
props,
|
||||
[
|
||||
'title',
|
||||
'description',
|
||||
'tutorialLoc',
|
||||
'activityLoc',
|
||||
'guideLoc',
|
||||
'thumbUrl',
|
||||
'bannerUrl'
|
||||
]
|
||||
)}
|
||||
>
|
||||
<TitleBanner className="mod-ttt">
|
||||
<MediaQuery minWidth={frameless.mobile}>
|
||||
<img
|
||||
alt=""
|
||||
className="mod-ttt-img"
|
||||
src={props.bannerUrl}
|
||||
/>
|
||||
</MediaQuery>
|
||||
<MediaQuery maxWidth={frameless.mobile - 1}>
|
||||
<img
|
||||
alt=""
|
||||
className="mod-ttt-img"
|
||||
src={props.thumbUrl}
|
||||
/>
|
||||
</MediaQuery>
|
||||
</TitleBanner>
|
||||
<div className="ttt-title">
|
||||
<h2>{props.title}</h2>
|
||||
<p className="ttt-description">{props.description}</p>
|
||||
</div>
|
||||
<ul className="modal-content-ttt">
|
||||
<FlexRow
|
||||
as="li"
|
||||
className="mod-ttt-item"
|
||||
>
|
||||
<TitleBanner className="mod-ttt">
|
||||
<MediaQuery minWidth={frameless.mobile}>
|
||||
<img className="mod-ttt-img" src={this.props.bannerUrl} alt="" />
|
||||
</MediaQuery>
|
||||
<MediaQuery maxWidth={frameless.mobile - 1}>
|
||||
<img className="mod-ttt-img" src={this.props.thumbUrl} alt="" />
|
||||
</MediaQuery>
|
||||
</TitleBanner>
|
||||
<div className="ttt-title">
|
||||
<h2>{this.props.title}</h2>
|
||||
<p className="ttt-description">{this.props.description}</p>
|
||||
<div className="modal-content-ttt-text">
|
||||
<div className="modal-content-ttt-title">
|
||||
<img
|
||||
alt="tutorial-icon"
|
||||
className="modal-content-ttt-title-img"
|
||||
src="/svgs/ttt/tutorial.svg"
|
||||
/>
|
||||
<FormattedMessage id="ttt.tutorial" />
|
||||
</div>
|
||||
<p className="modal-content-ttt-subtitle">
|
||||
<FormattedMessage id="ttt.tutorialSubtitle" />
|
||||
</p>
|
||||
</div>
|
||||
<ul className="modal-content-ttt">
|
||||
<FlexRow as="li" className="mod-ttt-item">
|
||||
<div className="modal-content-ttt-text">
|
||||
<div className="modal-content-ttt-title">
|
||||
<img
|
||||
className="modal-content-ttt-title-img"
|
||||
src="/svgs/ttt/tutorial.svg"
|
||||
alt="tutorial-icon"
|
||||
/>
|
||||
<FormattedMessage id="ttt.tutorial" />
|
||||
</div>
|
||||
<p className="modal-content-ttt-subtitle">
|
||||
<FormattedMessage id="ttt.tutorialSubtitle" />
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href={this.props.tutorialLoc}
|
||||
className="button white mod-ttt-item"
|
||||
>
|
||||
<FormattedMessage id="tile.tryIt" />
|
||||
</a>
|
||||
</FlexRow>
|
||||
<FlexRow as="li" className="mod-ttt-item">
|
||||
<div className="modal-content-ttt-text">
|
||||
<div className="modal-content-ttt-title">
|
||||
<img
|
||||
className="modal-content-ttt-title-img"
|
||||
src="/svgs/ttt/activity-cards.svg"
|
||||
alt="activity-cards-icon"
|
||||
/>
|
||||
<FormattedMessage id="ttt.activityTitle" />
|
||||
</div>
|
||||
<p className="modal-content-ttt-subtitle">
|
||||
<FormattedMessage id="ttt.activitySubtitle" />
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href={this.props.activityLoc}
|
||||
className="button white mod-ttt-item"
|
||||
>
|
||||
<FormattedMessage id="ttt.open" />
|
||||
</a>
|
||||
</FlexRow>
|
||||
<FlexRow as="li" className="mod-ttt-item">
|
||||
<div className="modal-content-ttt-text">
|
||||
<div className="modal-content-ttt-title">
|
||||
<img
|
||||
className="modal-content-ttt-title-img"
|
||||
src="/svgs/ttt/educator-guide.svg"
|
||||
alt="educator-guide-icon"
|
||||
/>
|
||||
<FormattedMessage id="ttt.educatorTitle" />
|
||||
</div>
|
||||
<p className="modal-content-ttt-subtitle">
|
||||
<FormattedMessage id="ttt.educatorSubtitle" />
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href={this.props.guideLoc}
|
||||
className="button white mod-ttt-item"
|
||||
>
|
||||
<FormattedMessage id="ttt.open" />
|
||||
</a>
|
||||
</FlexRow>
|
||||
</ul>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
});
|
||||
<a
|
||||
className="button white mod-ttt-item"
|
||||
href={props.tutorialLoc}
|
||||
>
|
||||
<FormattedMessage id="tile.tryIt" />
|
||||
</a>
|
||||
</FlexRow>
|
||||
<FlexRow
|
||||
as="li"
|
||||
className="mod-ttt-item"
|
||||
>
|
||||
<div className="modal-content-ttt-text">
|
||||
<div className="modal-content-ttt-title">
|
||||
<img
|
||||
alt="activity-cards-icon"
|
||||
className="modal-content-ttt-title-img"
|
||||
src="/svgs/ttt/activity-cards.svg"
|
||||
/>
|
||||
<FormattedMessage id="ttt.activityTitle" />
|
||||
</div>
|
||||
<p className="modal-content-ttt-subtitle">
|
||||
<FormattedMessage id="ttt.activitySubtitle" />
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
className="button white mod-ttt-item"
|
||||
href={props.activityLoc}
|
||||
>
|
||||
<FormattedMessage id="ttt.open" />
|
||||
</a>
|
||||
</FlexRow>
|
||||
<FlexRow
|
||||
as="li"
|
||||
className="mod-ttt-item"
|
||||
>
|
||||
<div className="modal-content-ttt-text">
|
||||
<div className="modal-content-ttt-title">
|
||||
<img
|
||||
alt="educator-guide-icon"
|
||||
className="modal-content-ttt-title-img"
|
||||
src="/svgs/ttt/educator-guide.svg"
|
||||
/>
|
||||
<FormattedMessage id="ttt.educatorTitle" />
|
||||
</div>
|
||||
<p className="modal-content-ttt-subtitle">
|
||||
<FormattedMessage id="ttt.educatorSubtitle" />
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
className="button white mod-ttt-item"
|
||||
href={props.guideLoc}
|
||||
>
|
||||
<FormattedMessage id="ttt.open" />
|
||||
</a>
|
||||
</FlexRow>
|
||||
</ul>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
TTTModal.propTypes = {
|
||||
activityLoc: PropTypes.string.isRequired,
|
||||
bannerUrl: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
guideLoc: PropTypes.string.isRequired,
|
||||
thumbUrl: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
tutorialLoc: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
module.exports = TTTModal;
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./navigation.scss');
|
||||
|
||||
var NavigationBox = React.createClass({
|
||||
type: 'NavigationBox',
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'inner',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<div className={classes}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const NavigationBox = props => (
|
||||
<div className={classNames('inner', props.className)}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
|
||||
NavigationBox.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = NavigationBox;
|
||||
|
|
|
@ -1,42 +1,55 @@
|
|||
var React = require('react');
|
||||
const React = require('react');
|
||||
|
||||
var NavigationBox = require('../../base/navigation.jsx');
|
||||
const NavigationBox = require('../../base/navigation.jsx');
|
||||
|
||||
require('./navigation.scss');
|
||||
|
||||
var Navigation = React.createClass({
|
||||
type: 'Navigation',
|
||||
render: function () {
|
||||
return (
|
||||
<NavigationBox>
|
||||
<ul className="ul mod-2016">
|
||||
<li className="li-left mod-logo mod-2016">
|
||||
<a href="/conference/2016" className="logo-a">
|
||||
<img
|
||||
src="/images/logo_sm.png"
|
||||
alt="Scratch Logo"
|
||||
className="logo-a-image"
|
||||
/>
|
||||
<p className="logo-a-title">Conference</p>
|
||||
const Navigation = () => (
|
||||
<NavigationBox>
|
||||
<ul className="ul mod-2016">
|
||||
<li className="li-left mod-logo mod-2016">
|
||||
<a
|
||||
className="logo-a"
|
||||
href="/conference/2016"
|
||||
>
|
||||
<img
|
||||
alt="Scratch Logo"
|
||||
className="logo-a-image"
|
||||
src="/images/logo_sm.png"
|
||||
/>
|
||||
<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>
|
||||
</li>
|
||||
<li className="li-right mod-2016">
|
||||
<ul className="li-right-ul mod-2016">
|
||||
<li className="link expect">
|
||||
<a href="/conference/2016/expect" className="link-a">What to Expect</a>
|
||||
</li>
|
||||
<li className="link plan">
|
||||
<a href="/conference/2016/plan" className="link-a">Plan Your Visit</a>
|
||||
</li>
|
||||
<li className="link schedule">
|
||||
<a href="/conference/2016/schedule" className="link-a">Schedule</a>
|
||||
</li>
|
||||
</ul>
|
||||
<li className="link plan">
|
||||
<a
|
||||
className="link-a"
|
||||
href="/conference/2016/plan"
|
||||
>
|
||||
Plan Your Visit
|
||||
</a>
|
||||
</li>
|
||||
<li className="link schedule">
|
||||
<a
|
||||
className="link-a"
|
||||
href="/conference/2016/schedule"
|
||||
>
|
||||
Schedule
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</NavigationBox>
|
||||
);
|
||||
}
|
||||
});
|
||||
</li>
|
||||
</ul>
|
||||
</NavigationBox>
|
||||
);
|
||||
|
||||
module.exports = Navigation;
|
||||
|
|
|
@ -1,29 +1,27 @@
|
|||
var React = require('react');
|
||||
const React = require('react');
|
||||
|
||||
var NavigationBox = require('../../base/navigation.jsx');
|
||||
const NavigationBox = require('../../base/navigation.jsx');
|
||||
|
||||
require('./navigation.scss');
|
||||
|
||||
var Navigation = React.createClass({
|
||||
type: 'Navigation',
|
||||
render: function () {
|
||||
return (
|
||||
<NavigationBox>
|
||||
<ul className="ul mod-2017">
|
||||
<li className="li-left mod-logo mod-2017">
|
||||
<a href="/conference" className="logo-a">
|
||||
<img
|
||||
src="/images/logo_sm.png"
|
||||
alt="Scratch Logo"
|
||||
className="logo-a-image"
|
||||
/>
|
||||
<p className="logo-a-title">Conferences</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</NavigationBox>
|
||||
);
|
||||
}
|
||||
});
|
||||
const Navigation = () => (
|
||||
<NavigationBox>
|
||||
<ul className="ul mod-2017">
|
||||
<li className="li-left mod-logo mod-2017">
|
||||
<a
|
||||
className="logo-a"
|
||||
href="/conference"
|
||||
>
|
||||
<img
|
||||
alt="Scratch Logo"
|
||||
className="logo-a-image"
|
||||
src="/images/logo_sm.png"
|
||||
/>
|
||||
<p className="logo-a-title">Conferences</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</NavigationBox>
|
||||
);
|
||||
|
||||
module.exports = Navigation;
|
||||
|
|
|
@ -1,29 +1,27 @@
|
|||
var React = require('react');
|
||||
const React = require('react');
|
||||
|
||||
var NavigationBox = require('../../base/navigation.jsx');
|
||||
const NavigationBox = require('../../base/navigation.jsx');
|
||||
|
||||
require('./navigation.scss');
|
||||
|
||||
var Navigation = React.createClass({
|
||||
type: 'Navigation',
|
||||
render: function () {
|
||||
return (
|
||||
<NavigationBox>
|
||||
<ul className="ul mod-2018">
|
||||
<li className="li-left mod-logo mod-2018">
|
||||
<a href="/" className="logo-a">
|
||||
<img
|
||||
src="/images/logo_sm.png"
|
||||
alt="Scratch Logo"
|
||||
className="logo-a-image"
|
||||
/>
|
||||
<p className="logo-a-title">Conferences</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</NavigationBox>
|
||||
);
|
||||
}
|
||||
});
|
||||
const Navigation = () => (
|
||||
<NavigationBox>
|
||||
<ul className="ul mod-2018">
|
||||
<li className="li-left mod-logo mod-2018">
|
||||
<a
|
||||
className="logo-a"
|
||||
href="/"
|
||||
>
|
||||
<img
|
||||
alt="Scratch Logo"
|
||||
className="logo-a-image"
|
||||
src="/images/logo_sm.png"
|
||||
/>
|
||||
<p className="logo-a-title">Conferences</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</NavigationBox>
|
||||
);
|
||||
|
||||
module.exports = Navigation;
|
||||
|
|
|
@ -1,31 +1,48 @@
|
|||
var classNames = require('classnames');
|
||||
var connect = require('react-redux').connect;
|
||||
var React = require('react');
|
||||
var ReactIntl = require('react-intl');
|
||||
var FormattedMessage = ReactIntl.FormattedMessage;
|
||||
var injectIntl = ReactIntl.injectIntl;
|
||||
const bindAll = require('lodash.bindall');
|
||||
const classNames = require('classnames');
|
||||
const connect = require('react-redux').connect;
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const injectIntl = require('react-intl').injectIntl;
|
||||
const intlShape = require('react-intl').intlShape;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var messageCountActions = require('../../../redux/message-count.js');
|
||||
var sessionActions = require('../../../redux/session.js');
|
||||
const messageCountActions = require('../../../redux/message-count.js');
|
||||
const sessionActions = require('../../../redux/session.js');
|
||||
|
||||
var api = require('../../../lib/api');
|
||||
var Avatar = require('../../avatar/avatar.jsx');
|
||||
var Button = require('../../forms/button.jsx');
|
||||
var Dropdown = require('../../dropdown/dropdown.jsx');
|
||||
var Form = require('../../forms/form.jsx');
|
||||
var Input = require('../../forms/input.jsx');
|
||||
var log = require('../../../lib/log.js');
|
||||
var Login = require('../../login/login.jsx');
|
||||
var Modal = require('../../modal/base/modal.jsx');
|
||||
var NavigationBox = require('../base/navigation.jsx');
|
||||
var Registration = require('../../registration/registration.jsx');
|
||||
const api = require('../../../lib/api');
|
||||
const Avatar = require('../../avatar/avatar.jsx');
|
||||
const Button = require('../../forms/button.jsx');
|
||||
const Dropdown = require('../../dropdown/dropdown.jsx');
|
||||
const Form = require('../../forms/form.jsx');
|
||||
const Input = require('../../forms/input.jsx');
|
||||
const log = require('../../../lib/log.js');
|
||||
const Login = require('../../login/login.jsx');
|
||||
const Modal = require('../../modal/base/modal.jsx');
|
||||
const NavigationBox = require('../base/navigation.jsx');
|
||||
const Registration = require('../../registration/registration.jsx');
|
||||
|
||||
require('./navigation.scss');
|
||||
|
||||
var Navigation = React.createClass({
|
||||
type: 'Navigation',
|
||||
getInitialState: function () {
|
||||
return {
|
||||
class Navigation extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'getProfileUrl',
|
||||
'handleJoinClick',
|
||||
'handleLoginClick',
|
||||
'handleCloseLogin',
|
||||
'handleLogIn',
|
||||
'handleLogOut',
|
||||
'handleAccountNavClick',
|
||||
'handleCloseAccountNav',
|
||||
'showCanceledDeletion',
|
||||
'handleCloseCanceledDeletion',
|
||||
'handleCloseRegistration',
|
||||
'handleCompleteRegistration',
|
||||
'handleSearchSubmit'
|
||||
]);
|
||||
this.state = {
|
||||
accountNavOpen: false,
|
||||
canceledDeletionOpen: false,
|
||||
loginOpen: false,
|
||||
|
@ -33,155 +50,153 @@ var Navigation = React.createClass({
|
|||
registrationOpen: false,
|
||||
messageCountIntervalId: -1 // javascript method interval id for getting messsage count.
|
||||
};
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
session: {},
|
||||
unreadMessageCount: 0, // bubble number to display how many notifications someone has.
|
||||
searchTerm: ''
|
||||
};
|
||||
},
|
||||
componentDidMount: function () {
|
||||
}
|
||||
componentDidMount () {
|
||||
if (this.props.session.session.user) {
|
||||
var intervalId = setInterval(function () {
|
||||
this.props.dispatch(messageCountActions.getCount(this.props.session.session.user.username));
|
||||
}.bind(this), 120000); // check for new messages every 2 mins.
|
||||
this.setState({'messageCountIntervalId': intervalId});
|
||||
const intervalId = setInterval(() => {
|
||||
this.props.dispatch(
|
||||
messageCountActions.getCount(this.props.session.session.user.username)
|
||||
);
|
||||
}, 120000); // check for new messages every 2 mins.
|
||||
this.setState({ // eslint-disable-line react/no-did-mount-set-state
|
||||
messageCountIntervalId: intervalId
|
||||
});
|
||||
}
|
||||
},
|
||||
componentDidUpdate: function (prevProps) {
|
||||
if (prevProps.session.session.user != this.props.session.session.user) {
|
||||
this.setState({
|
||||
'loginOpen': false,
|
||||
'accountNavOpen': false
|
||||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
if (prevProps.session.session.user !== this.props.session.session.user) {
|
||||
this.setState({ // eslint-disable-line react/no-did-update-set-state
|
||||
loginOpen: false,
|
||||
accountNavOpen: false
|
||||
});
|
||||
if (this.props.session.session.user) {
|
||||
var intervalId = setInterval(function () {
|
||||
this.props.dispatch(messageCountActions.getCount(this.props.session.session.user.username));
|
||||
}.bind(this), 120000); // check for new messages every 2 mins.
|
||||
this.setState({'messageCountIntervalId': intervalId});
|
||||
const intervalId = setInterval(() => {
|
||||
this.props.dispatch(
|
||||
messageCountActions.getCount(this.props.session.session.user.username)
|
||||
);
|
||||
}, 120000); // check for new messages every 2 mins.
|
||||
this.setState({ // eslint-disable-line react/no-did-update-set-state
|
||||
messageCountIntervalId: intervalId
|
||||
});
|
||||
} else {
|
||||
// clear message count check, and set to default id.
|
||||
clearInterval(this.state.messageCountIntervalId);
|
||||
this.props.dispatch(messageCountActions.setCount(0));
|
||||
this.setState({
|
||||
'messageCountIntervalId': -1
|
||||
this.setState({ // eslint-disable-line react/no-did-update-set-state
|
||||
messageCountIntervalId: -1
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
}
|
||||
componentWillUnmount () {
|
||||
// clear message interval if it exists
|
||||
if (this.state.messageCountIntervalId != -1) {
|
||||
if (this.state.messageCountIntervalId !== -1) {
|
||||
clearInterval(this.state.messageCountIntervalId);
|
||||
this.props.dispatch(messageCountActions.setCount(0));
|
||||
this.setState({
|
||||
'messageCountIntervalId': -1
|
||||
messageCountIntervalId: -1
|
||||
});
|
||||
}
|
||||
},
|
||||
getProfileUrl: function () {
|
||||
}
|
||||
getProfileUrl () {
|
||||
if (!this.props.session.session.user) return;
|
||||
return '/users/' + this.props.session.session.user.username + '/';
|
||||
},
|
||||
handleJoinClick: function (e) {
|
||||
return `/users/${this.props.session.session.user.username}/`;
|
||||
}
|
||||
handleJoinClick (e) {
|
||||
e.preventDefault();
|
||||
this.setState({'registrationOpen': true});
|
||||
},
|
||||
handleLoginClick: function (e) {
|
||||
this.setState({registrationOpen: true});
|
||||
}
|
||||
handleLoginClick (e) {
|
||||
e.preventDefault();
|
||||
this.setState({'loginOpen': !this.state.loginOpen});
|
||||
},
|
||||
closeLogin: function () {
|
||||
this.setState({'loginOpen': false});
|
||||
},
|
||||
handleLogIn: function (formData, callback) {
|
||||
this.setState({'loginError': null});
|
||||
formData['useMessages'] = true;
|
||||
this.setState({loginOpen: !this.state.loginOpen});
|
||||
}
|
||||
handleCloseLogin () {
|
||||
this.setState({loginOpen: false});
|
||||
}
|
||||
handleLogIn (formData, callback) {
|
||||
this.setState({loginError: null});
|
||||
formData.useMessages = true;
|
||||
api({
|
||||
method: 'post',
|
||||
host: '',
|
||||
uri: '/accounts/login/',
|
||||
json: formData,
|
||||
useCsrf: true
|
||||
}, function (err, body) {
|
||||
if (err) this.setState({'loginError': err.message});
|
||||
}, (err, body) => {
|
||||
if (err) this.setState({loginError: err.message});
|
||||
if (body) {
|
||||
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) {
|
||||
window.location = body.redirect;
|
||||
}
|
||||
// Update login error message to a friendlier one if it exists
|
||||
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());
|
||||
this.setState({loginError: body.msg});
|
||||
}
|
||||
}
|
||||
// JS error already logged by api mixin
|
||||
callback();
|
||||
}.bind(this));
|
||||
},
|
||||
handleLogOut: function (e) {
|
||||
});
|
||||
}
|
||||
handleLogOut (e) {
|
||||
e.preventDefault();
|
||||
api({
|
||||
host: '',
|
||||
method: 'post',
|
||||
uri: '/accounts/logout/',
|
||||
useCsrf: true
|
||||
}, function (err) {
|
||||
}, err => {
|
||||
if (err) log.error(err);
|
||||
this.closeLogin();
|
||||
this.handleCloseLogin();
|
||||
window.location = '/';
|
||||
}.bind(this));
|
||||
},
|
||||
handleAccountNavClick: function (e) {
|
||||
});
|
||||
}
|
||||
handleAccountNavClick (e) {
|
||||
e.preventDefault();
|
||||
this.setState({'accountNavOpen': true});
|
||||
},
|
||||
closeAccountNav: function () {
|
||||
this.setState({'accountNavOpen': false});
|
||||
},
|
||||
showCanceledDeletion: function () {
|
||||
this.setState({'canceledDeletionOpen': true});
|
||||
},
|
||||
closeCanceledDeletion: function () {
|
||||
this.setState({'canceledDeletionOpen': false});
|
||||
},
|
||||
closeRegistration: function () {
|
||||
this.setState({'registrationOpen': false});
|
||||
},
|
||||
completeRegistration: function () {
|
||||
this.setState({accountNavOpen: true});
|
||||
}
|
||||
handleCloseAccountNav () {
|
||||
this.setState({accountNavOpen: false});
|
||||
}
|
||||
showCanceledDeletion () {
|
||||
this.setState({canceledDeletionOpen: true});
|
||||
}
|
||||
handleCloseCanceledDeletion () {
|
||||
this.setState({canceledDeletionOpen: false});
|
||||
}
|
||||
handleCloseRegistration () {
|
||||
this.setState({registrationOpen: false});
|
||||
}
|
||||
handleCompleteRegistration () {
|
||||
this.props.dispatch(sessionActions.refreshSession());
|
||||
this.closeRegistration();
|
||||
},
|
||||
onSearchSubmit: function (formData) {
|
||||
window.location.href = '/search/projects?q=' + encodeURIComponent(formData.q);
|
||||
},
|
||||
render: function () {
|
||||
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';
|
||||
this.handleCloseRegistration();
|
||||
}
|
||||
handleSearchSubmit (formData) {
|
||||
window.location.href = `/search/projects?q=${encodeURIComponent(formData.q)}`;
|
||||
}
|
||||
render () {
|
||||
const createLink = this.props.session.session.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home';
|
||||
return (
|
||||
<NavigationBox className={classes}>
|
||||
<NavigationBox
|
||||
className={classNames({
|
||||
'logged-in': this.props.session.session.user
|
||||
})}
|
||||
>
|
||||
<ul>
|
||||
<li className="logo"><a href="/" aria-label="Scratch"></a></li>
|
||||
<li className="logo">
|
||||
<a
|
||||
aria-label="Scratch"
|
||||
href="/"
|
||||
/>
|
||||
</li>
|
||||
|
||||
<li className="link create">
|
||||
<a href={createLink}>
|
||||
|
@ -205,47 +220,76 @@ var Navigation = React.createClass({
|
|||
</li>
|
||||
|
||||
<li className="search">
|
||||
<Form onSubmit={this.onSearchSubmit}>
|
||||
<Button type="submit" className="btn-search" />
|
||||
<Input type="text"
|
||||
value={this.props.searchTerm}
|
||||
aria-label={formatMessage({id: 'general.search'})}
|
||||
placeholder={formatMessage({id: 'general.search'})}
|
||||
name="q" />
|
||||
<Form onSubmit={this.handleSearchSubmit}>
|
||||
<Button
|
||||
className="btn-search"
|
||||
type="submit"
|
||||
/>
|
||||
<Input
|
||||
aria-label={this.props.intl.formatMessage({id: 'general.search'})}
|
||||
name="q"
|
||||
placeholder={this.props.intl.formatMessage({id: 'general.search'})}
|
||||
type="text"
|
||||
value={this.props.searchTerm}
|
||||
/>
|
||||
</Form>
|
||||
</li>
|
||||
{this.props.session.status === sessionActions.Status.FETCHED ? (
|
||||
this.props.session.session.user ? [
|
||||
<li className="link right messages" key="messages">
|
||||
<li
|
||||
className="link right messages"
|
||||
key="messages"
|
||||
>
|
||||
<a
|
||||
href="/messages/"
|
||||
title={formatMessage({id: 'general.messages'})}>
|
||||
|
||||
<span className={messageClasses}>{this.props.unreadMessageCount}</span>
|
||||
title={this.props.intl.formatMessage({id: 'general.messages'})}
|
||||
>
|
||||
<span
|
||||
className={classNames({
|
||||
'message-count': true,
|
||||
'show': this.props.unreadMessageCount > 0
|
||||
})}
|
||||
>{this.props.unreadMessageCount}</span>
|
||||
<FormattedMessage id="general.messages" />
|
||||
</a>
|
||||
</li>,
|
||||
<li className="link right mystuff" key="mystuff">
|
||||
<li
|
||||
className="link right mystuff"
|
||||
key="mystuff"
|
||||
>
|
||||
<a
|
||||
href="/mystuff/"
|
||||
title={formatMessage({id: 'general.myStuff'})}>
|
||||
|
||||
title={this.props.intl.formatMessage({id: 'general.myStuff'})}
|
||||
>
|
||||
<FormattedMessage id="general.myStuff" />
|
||||
</a>
|
||||
</li>,
|
||||
<li className="link right account-nav" key="account-nav">
|
||||
<a className={dropdownClasses}
|
||||
href="#" onClick={this.handleAccountNavClick}>
|
||||
<Avatar src={this.props.session.session.user.thumbnailUrl} alt="" />
|
||||
<span className='profile-name'>
|
||||
<li
|
||||
className="link right account-nav"
|
||||
key="account-nav"
|
||||
>
|
||||
<a
|
||||
className={classNames({
|
||||
'user-info': true,
|
||||
'open': this.state.accountNavOpen
|
||||
})}
|
||||
href="#"
|
||||
onClick={this.handleAccountNavClick}
|
||||
>
|
||||
<Avatar
|
||||
alt=""
|
||||
src={this.props.session.session.user.thumbnailUrl}
|
||||
/>
|
||||
<span className="profile-name">
|
||||
{this.props.session.session.user.username}
|
||||
</span>
|
||||
</a>
|
||||
<Dropdown
|
||||
as="ul"
|
||||
isOpen={this.state.accountNavOpen}
|
||||
onRequestClose={this.closeAccountNav}
|
||||
className={process.env.SCRATCH_ENV}>
|
||||
as="ul"
|
||||
className={process.env.SCRATCH_ENV}
|
||||
isOpen={this.state.accountNavOpen}
|
||||
onRequestClose={this.handleCloseAccountNav}
|
||||
>
|
||||
<li>
|
||||
<a href={this.getProfileUrl()}>
|
||||
<FormattedMessage id="general.profile" />
|
||||
|
@ -264,8 +308,8 @@ var Navigation = React.createClass({
|
|||
</li>
|
||||
] : []}
|
||||
{this.props.permissions.student ? [
|
||||
<li>
|
||||
<a href={'/classes/' + this.props.session.session.user.classroomId + '/'}>
|
||||
<li key="my-class-li">
|
||||
<a href={`/classes/${this.props.session.session.user.classroomId}/`}>
|
||||
<FormattedMessage id="general.myClass" />
|
||||
</a>
|
||||
</li>
|
||||
|
@ -276,69 +320,123 @@ var Navigation = React.createClass({
|
|||
</a>
|
||||
</li>
|
||||
<li className="divider">
|
||||
<a href="#" onClick={this.handleLogOut}>
|
||||
<a
|
||||
href="#"
|
||||
onClick={this.handleLogOut}
|
||||
>
|
||||
<FormattedMessage id="navigation.signOut" />
|
||||
</a>
|
||||
</li>
|
||||
</Dropdown>
|
||||
</li>
|
||||
] : [
|
||||
<li className="link right join" key="join">
|
||||
<a href="#" onClick={this.handleJoinClick}>
|
||||
<li
|
||||
className="link right join"
|
||||
key="join"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
onClick={this.handleJoinClick}
|
||||
>
|
||||
<FormattedMessage id="general.joinScratch" />
|
||||
</a>
|
||||
</li>,
|
||||
<Registration
|
||||
key="registration"
|
||||
isOpen={this.state.registrationOpen}
|
||||
onRequestClose={this.closeRegistration}
|
||||
onRegistrationDone={this.completeRegistration} />,
|
||||
<li className="link right login-item" key="login">
|
||||
isOpen={this.state.registrationOpen}
|
||||
key="registration"
|
||||
onRegistrationDone={this.handleCompleteRegistration}
|
||||
onRequestClose={this.handleCloseRegistration}
|
||||
/>,
|
||||
<li
|
||||
className="link right login-item"
|
||||
key="login"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
onClick={this.handleLoginClick}
|
||||
className="ignore-react-onclickoutside"
|
||||
key="login-link">
|
||||
<FormattedMessage id="general.signIn" />
|
||||
</a>
|
||||
href="#"
|
||||
key="login-link"
|
||||
onClick={this.handleLoginClick}
|
||||
>
|
||||
<FormattedMessage id="general.signIn" />
|
||||
</a>
|
||||
<Dropdown
|
||||
className="login-dropdown with-arrow"
|
||||
isOpen={this.state.loginOpen}
|
||||
onRequestClose={this.closeLogin}
|
||||
key="login-dropdown">
|
||||
className="login-dropdown with-arrow"
|
||||
isOpen={this.state.loginOpen}
|
||||
key="login-dropdown"
|
||||
onRequestClose={this.handleCloseLogin}
|
||||
>
|
||||
<Login
|
||||
error={this.state.loginError}
|
||||
onLogIn={this.handleLogIn}
|
||||
error={this.state.loginError} />
|
||||
/>
|
||||
</Dropdown>
|
||||
</li>
|
||||
]) : [
|
||||
]}
|
||||
]) : []}
|
||||
</ul>
|
||||
<Modal isOpen={this.state.canceledDeletionOpen}
|
||||
onRequestClose={this.closeCanceledDeletion}
|
||||
style={{content:{padding: 15}}}>
|
||||
<Modal
|
||||
isOpen={this.state.canceledDeletionOpen}
|
||||
style={{
|
||||
content: {
|
||||
padding: 15
|
||||
}
|
||||
}}
|
||||
onRequestClose={this.handleCloseCanceledDeletion}
|
||||
>
|
||||
<h4>Your Account Will Not Be Deleted</h4>
|
||||
<h4><FormattedMessage id="general.noDeletionTitle" /></h4>
|
||||
<p>
|
||||
Your account was scheduled for deletion but you logged in. Your account has been reactivated.
|
||||
If you didn’t request for your account to be deleted, you should
|
||||
{' '}<a href="/accounts/password_reset/">change your password</a>{' '}
|
||||
to make sure your account is secure.
|
||||
<FormattedMessage
|
||||
id="general.noDeletionDescription"
|
||||
values={{
|
||||
resetLink: <a href="/accounts/password_reset/">
|
||||
{this.props.intl.formatMessage({id: 'general.noDeletionLink'})}
|
||||
</a>
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</Modal>
|
||||
</NavigationBox>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var mapStateToProps = function (state) {
|
||||
return {
|
||||
session: state.session,
|
||||
permissions: state.permissions,
|
||||
unreadMessageCount: state.messageCount.messageCount,
|
||||
searchTerm: state.navigation
|
||||
};
|
||||
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])
|
||||
};
|
||||
|
||||
var ConnectedNavigation = connect(mapStateToProps)(Navigation);
|
||||
Navigation.defaultProps = {
|
||||
session: {},
|
||||
unreadMessageCount: 0, // bubble number to display how many notifications someone has.
|
||||
searchTerm: ''
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
session: state.session,
|
||||
permissions: state.permissions,
|
||||
unreadMessageCount: state.messageCount.messageCount,
|
||||
searchTerm: state.navigation
|
||||
});
|
||||
|
||||
const ConnectedNavigation = connect(mapStateToProps)(Navigation);
|
||||
|
||||
module.exports = injectIntl(ConnectedNavigation);
|
||||
|
|
|
@ -1,75 +1,84 @@
|
|||
var classNames = require('classnames');
|
||||
var defaults = require('lodash.defaults');
|
||||
var React = require('react');
|
||||
var Slider = require('react-slick');
|
||||
const classNames = require('classnames');
|
||||
const defaults = require('lodash.defaults');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const Slider = require('react-slick');
|
||||
|
||||
var Thumbnail = require('../thumbnail/thumbnail.jsx');
|
||||
const Thumbnail = require('../thumbnail/thumbnail.jsx');
|
||||
|
||||
require('slick-carousel/slick/slick.scss');
|
||||
require('slick-carousel/slick/slick-theme.scss');
|
||||
require('./nestedcarousel.scss');
|
||||
|
||||
|
||||
{/*
|
||||
/*
|
||||
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).
|
||||
It creates the thumbnails without links.
|
||||
|
||||
Each slide has a title, and then a list of thumbnails, that will be shown together.
|
||||
*/}
|
||||
var NestedCarousel = React.createClass({
|
||||
type: 'NestedCarousel',
|
||||
propTypes: {
|
||||
items: React.PropTypes.array
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
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 NestedCarousel = props => {
|
||||
defaults(props.settings, {
|
||||
dots: true,
|
||||
infinite: false,
|
||||
lazyLoad: true,
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1,
|
||||
variableWidth: false
|
||||
});
|
||||
|
||||
var arrows = this.props.items.length > settings.slidesToShow;
|
||||
|
||||
var classes = classNames(
|
||||
'nestedcarousel',
|
||||
'carousel',
|
||||
this.props.className
|
||||
);
|
||||
|
||||
var stages = [];
|
||||
for (var i=0; i < this.props.items.length; i++) {
|
||||
var items = this.props.items[i].thumbnails;
|
||||
var thumbnails = [];
|
||||
for (var j=0; j < items.length; j++) {
|
||||
var item = items[j];
|
||||
thumbnails.push(
|
||||
<Thumbnail key={'inner_' + i + '_' + j}
|
||||
title={item.title}
|
||||
src={item.thumbnailUrl}
|
||||
linkTitle = {false} />);
|
||||
}
|
||||
stages.push(
|
||||
<div key={'outer_' + i}>
|
||||
<h3>{this.props.items[i].title}</h3>
|
||||
{thumbnails}
|
||||
</div>);
|
||||
const arrows = props.items.length > props.settings.slidesToShow;
|
||||
const stages = [];
|
||||
|
||||
for (let i = 0; i < props.items.length; i++) {
|
||||
const items = props.items[i].thumbnails;
|
||||
const thumbnails = [];
|
||||
for (let j = 0; j < items.length; j++) {
|
||||
const item = items[j];
|
||||
thumbnails.push(
|
||||
<Thumbnail
|
||||
key={`inner_${i}_${j}`}
|
||||
linkTitle={false}
|
||||
src={item.thumbnailUrl}
|
||||
title={item.title}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Slider className={classes} arrows={arrows} {... settings}>
|
||||
{stages}
|
||||
</Slider>
|
||||
stages.push(
|
||||
<div key={`outer_${i}`}>
|
||||
<h3>{props.items[i].title}</h3>
|
||||
{thumbnails}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
return (
|
||||
<Slider
|
||||
arrows={arrows}
|
||||
className={classNames('nestedcarousel', 'carousel', props.className)}
|
||||
{...props.settings}
|
||||
>
|
||||
{stages}
|
||||
</Slider>
|
||||
);
|
||||
};
|
||||
|
||||
NestedCarousel.propTypes = {
|
||||
className: PropTypes.string,
|
||||
items: PropTypes.arrayOf(PropTypes.object),
|
||||
settings: PropTypes.shape({
|
||||
dots: PropTypes.bool,
|
||||
infinite: PropTypes.bool,
|
||||
lazyLoad: PropTypes.bool,
|
||||
slidesToShow: PropTypes.number,
|
||||
slidesToScroll: PropTypes.number,
|
||||
variableWidth: PropTypes.bool
|
||||
})
|
||||
};
|
||||
|
||||
NestedCarousel.defaultProps = {
|
||||
settings: {},
|
||||
items: require('./nestedcarousel.json')
|
||||
};
|
||||
|
||||
module.exports = NestedCarousel;
|
||||
|
|
|
@ -1,54 +1,53 @@
|
|||
var React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var Box = require('../box/box.jsx');
|
||||
const Box = require('../box/box.jsx');
|
||||
|
||||
require('./news.scss');
|
||||
|
||||
var News = React.createClass({
|
||||
type: 'News',
|
||||
propTypes: {
|
||||
items: React.PropTypes.array
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
items: require('./news.json'),
|
||||
messages: {
|
||||
'general.viewAll': 'View All',
|
||||
'news.scratchNews': 'Scratch News'
|
||||
}
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Box
|
||||
className="news"
|
||||
title={this.props.messages['news.scratchNews']}
|
||||
moreTitle={this.props.messages['general.viewAll']}
|
||||
moreHref="/discuss/5/">
|
||||
const News = props => (
|
||||
<Box
|
||||
className="news"
|
||||
moreHref="/discuss/5/"
|
||||
moreTitle={props.messages['general.viewAll']}
|
||||
title={props.messages['news.scratchNews']}
|
||||
>
|
||||
<ul>
|
||||
{props.items.map(item => (
|
||||
<li key={item.id}>
|
||||
<a href={item.url}>
|
||||
<img
|
||||
alt=""
|
||||
className="news-image"
|
||||
height="53"
|
||||
src={item.image}
|
||||
width="53"
|
||||
/>
|
||||
<div className="news-description">
|
||||
<h4>{item.headline}</h4>
|
||||
<p>{item.copy}</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Box>
|
||||
);
|
||||
|
||||
<ul>
|
||||
{this.props.items.map(function (item) {
|
||||
return (
|
||||
<li key={item.id}>
|
||||
<a href={item.url}>
|
||||
<img src={item.image}
|
||||
className="news-image"
|
||||
width="53"
|
||||
height="53"
|
||||
alt=""
|
||||
/>
|
||||
<div className="news-description">
|
||||
<h4>{item.headline}</h4>
|
||||
<p>{item.copy}</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</Box>
|
||||
);
|
||||
News.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object),
|
||||
messages: PropTypes.shape({
|
||||
'general.viewAll': PropTypes.string,
|
||||
'news.scratchNews': PropTypes.string
|
||||
})
|
||||
};
|
||||
|
||||
News.defaultProps = {
|
||||
items: require('./news.json'),
|
||||
messages: {
|
||||
'general.viewAll': 'View All',
|
||||
'news.scratchNews': 'Scratch News'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = News;
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
var React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var Navigation = require('../../../navigation/conference/2016/navigation.jsx');
|
||||
var Footer = require('../../../footer/conference/2016/footer.jsx');
|
||||
const Navigation = require('../../../navigation/conference/2016/navigation.jsx');
|
||||
const Footer = require('../../../footer/conference/2016/footer.jsx');
|
||||
|
||||
require('../page.scss');
|
||||
|
||||
var Page = React.createClass({
|
||||
type: 'Page',
|
||||
render: function () {
|
||||
return (
|
||||
<div className="page mod-conference">
|
||||
<div id="navigation">
|
||||
<Navigation />
|
||||
</div>
|
||||
<div id="view">
|
||||
{this.props.children}
|
||||
</div>
|
||||
<div id="footer">
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const Page = props => (
|
||||
<div className="page mod-conference">
|
||||
<div id="navigation">
|
||||
<Navigation />
|
||||
</div>
|
||||
<div id="view">
|
||||
{props.children}
|
||||
</div>
|
||||
<div id="footer">
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Page.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
module.exports = Page;
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
var React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var Navigation = require('../../../navigation/conference/2017/navigation.jsx');
|
||||
var Footer = require('../../../footer/conference/2017/footer.jsx');
|
||||
const Navigation = require('../../../navigation/conference/2017/navigation.jsx');
|
||||
const Footer = require('../../../footer/conference/2017/footer.jsx');
|
||||
|
||||
require('../page.scss');
|
||||
|
||||
var Page = React.createClass({
|
||||
type: 'Page',
|
||||
render: function () {
|
||||
return (
|
||||
<div className="page mod-conference">
|
||||
<div id="navigation">
|
||||
<Navigation />
|
||||
</div>
|
||||
<div id="view">
|
||||
{this.props.children}
|
||||
</div>
|
||||
<div id="footer">
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const Page = props => (
|
||||
<div className="page mod-conference">
|
||||
<div id="navigation">
|
||||
<Navigation />
|
||||
</div>
|
||||
<div id="view">
|
||||
{props.children}
|
||||
</div>
|
||||
<div id="footer">
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Page.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
module.exports = Page;
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
var React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var Navigation = require('../../../navigation/conference/2018/navigation.jsx');
|
||||
var Footer = require('../../../footer/conference/2018/footer.jsx');
|
||||
const Navigation = require('../../../navigation/conference/2018/navigation.jsx');
|
||||
const Footer = require('../../../footer/conference/2018/footer.jsx');
|
||||
|
||||
require('../page.scss');
|
||||
|
||||
var Page = React.createClass({
|
||||
type: 'Page',
|
||||
render: function () {
|
||||
return (
|
||||
<div className="page mod-conference">
|
||||
<div id="navigation">
|
||||
<Navigation />
|
||||
</div>
|
||||
<div id="view">
|
||||
{this.props.children}
|
||||
</div>
|
||||
<div id="footer">
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const Page = props => (
|
||||
<div className="page mod-conference">
|
||||
<div id="navigation">
|
||||
<Navigation />
|
||||
</div>
|
||||
<div id="view">
|
||||
{props.children}
|
||||
</div>
|
||||
<div id="footer">
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Page.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
module.exports = Page;
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var Navigation = require('../../navigation/www/navigation.jsx');
|
||||
var Footer = require('../../footer/www/footer.jsx');
|
||||
const Navigation = require('../../navigation/www/navigation.jsx');
|
||||
const Footer = require('../../footer/www/footer.jsx');
|
||||
|
||||
var Page = React.createClass({
|
||||
type: 'Page',
|
||||
render: function () {
|
||||
var classes = classNames({
|
||||
'staging': process.env.SCRATCH_ENV == 'staging'
|
||||
});
|
||||
return (
|
||||
<div className="page">
|
||||
<div id="navigation" className={classes}>
|
||||
<Navigation />
|
||||
</div>
|
||||
<div id="view">
|
||||
{this.props.children}
|
||||
</div>
|
||||
<div id="footer">
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const Page = props => (
|
||||
<div className="page">
|
||||
<div
|
||||
className={classNames({
|
||||
staging: process.env.SCRATCH_ENV === 'staging'
|
||||
})}
|
||||
id="navigation"
|
||||
>
|
||||
<Navigation />
|
||||
</div>
|
||||
<div id="view">
|
||||
{props.children}
|
||||
</div>
|
||||
<div id="footer">
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Page.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
module.exports = Page;
|
||||
|
|
|
@ -1,42 +1,45 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'Progression',
|
||||
propTypes: {
|
||||
step: function (props, propName, componentName) {
|
||||
var stepValidator = function (props, propName) {
|
||||
if (props[propName] > -1 && props[propName] < props.children.length) {
|
||||
return null;
|
||||
} else {
|
||||
return new Error('Prop `step` out of range');
|
||||
const Progression = props => {
|
||||
const childProps = {
|
||||
activeStep: props.step,
|
||||
totalSteps: React.Children.count(props.children)
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={classNames('progression', props.className)}
|
||||
{...props}
|
||||
>
|
||||
{React.Children.map(props.children, (child, id) => {
|
||||
if (id === props.step) {
|
||||
return React.cloneElement(child, childProps);
|
||||
}
|
||||
};
|
||||
return (
|
||||
React.PropTypes.number.isRequired(props, propName, componentName) ||
|
||||
stepValidator(props, propName, componentName)
|
||||
);
|
||||
}
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
step: 0
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
var childProps = {
|
||||
activeStep: this.props.step,
|
||||
totalSteps: React.Children.count(this.props.children)
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Progression.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
step: function (props, propName, componentName) {
|
||||
const stepValidator = (propz, name) => {
|
||||
if (propz[name] > -1 && propz[name] < propz.children.length) {
|
||||
return null;
|
||||
}
|
||||
return new Error('Prop `step` out of range');
|
||||
};
|
||||
return (
|
||||
<div {... this.props}
|
||||
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>
|
||||
(typeof props[propName] === 'number' ? null : new Error('Not a number')) ||
|
||||
stepValidator(props, propName, componentName)
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Progression.defaultProps = {
|
||||
step: 0
|
||||
};
|
||||
|
||||
module.exports = Progression;
|
||||
|
|
|
@ -1,53 +1,62 @@
|
|||
var React = require('react');
|
||||
var IframeModal = require('../modal/iframe/modal.jsx');
|
||||
const bindAll = require('lodash.bindall');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const IframeModal = require('../modal/iframe/modal.jsx');
|
||||
|
||||
require('./registration.scss');
|
||||
|
||||
var Registration = React.createClass({
|
||||
propTypes: {
|
||||
isOpen: React.PropTypes.bool,
|
||||
onRegistrationDone: React.PropTypes.func,
|
||||
onRequestClose: React.PropTypes.func
|
||||
},
|
||||
onMessage: function (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') {
|
||||
class Registration extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleMessage',
|
||||
'toggleMessageListener'
|
||||
]);
|
||||
}
|
||||
componentDidMount () {
|
||||
if (this.props.isOpen) this.toggleMessageListener(true);
|
||||
}
|
||||
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();
|
||||
}
|
||||
},
|
||||
toggleMessageListener: function (present) {
|
||||
}
|
||||
toggleMessageListener (present) {
|
||||
if (present) {
|
||||
window.addEventListener('message', this.onMessage);
|
||||
window.addEventListener('message', this.handleMessage);
|
||||
} else {
|
||||
window.removeEventListener('message', this.onMessage);
|
||||
window.removeEventListener('message', this.handleMessage);
|
||||
}
|
||||
},
|
||||
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 () {
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<IframeModal
|
||||
isOpen={this.props.isOpen}
|
||||
onRequestClose={this.props.onRequestClose}
|
||||
className="mod-registration"
|
||||
componentRef={
|
||||
function (iframe) {
|
||||
this.registrationIframe = iframe;
|
||||
}.bind(this)
|
||||
}
|
||||
componentRef={iframe => { // eslint-disable-line react/jsx-no-bind
|
||||
this.registrationIframe = iframe;
|
||||
}}
|
||||
isOpen={this.props.isOpen}
|
||||
src="/accounts/standalone-registration/"
|
||||
onRequestClose={this.props.onRequestClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Registration.propTypes = {
|
||||
isOpen: PropTypes.bool,
|
||||
onRegistrationDone: PropTypes.func,
|
||||
onRequestClose: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = Registration;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,17 +1,18 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./slide.scss');
|
||||
|
||||
var Slide = React.createClass({
|
||||
displayName: 'Slide',
|
||||
render: function () {
|
||||
return (
|
||||
<div className={classNames(['slide', this.props.className])}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const Slide = props => (
|
||||
<div className={classNames(['slide', props.className])}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
|
||||
Slide.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = Slide;
|
||||
|
|
|
@ -1,57 +1,47 @@
|
|||
var classNames = require('classnames');
|
||||
var FormattedRelative = require('react-intl').FormattedRelative;
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const FormattedRelative = require('react-intl').FormattedRelative;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var FlexRow = require('../flex-row/flex-row.jsx');
|
||||
const FlexRow = require('../flex-row/flex-row.jsx');
|
||||
|
||||
require('./social-message.scss');
|
||||
|
||||
var SocialMessage = React.createClass({
|
||||
type: 'SocialMessage',
|
||||
propTypes: {
|
||||
as: React.PropTypes.string,
|
||||
datetime: React.PropTypes.string.isRequired,
|
||||
iconSrc: React.PropTypes.string,
|
||||
iconAlt: React.PropTypes.string,
|
||||
imgClassName: React.PropTypes.string
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
as: 'li'
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'social-message',
|
||||
this.props.className
|
||||
);
|
||||
var imgClass = classNames(
|
||||
'social-message-icon',
|
||||
this.props.imgClassName
|
||||
);
|
||||
return (
|
||||
<this.props.as className={classes}>
|
||||
<FlexRow className="mod-social-message">
|
||||
<div className="social-message-content">
|
||||
{typeof this.props.iconSrc !== 'undefined' ? [
|
||||
<img
|
||||
key="social-message-icon"
|
||||
className={imgClass}
|
||||
src={this.props.iconSrc}
|
||||
alt={this.props.iconAlt}
|
||||
/>
|
||||
] : []}
|
||||
<div>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
<span className="social-message-date">
|
||||
<FormattedRelative value={new Date(this.props.datetime)} />
|
||||
</span>
|
||||
</FlexRow>
|
||||
</this.props.as>
|
||||
);
|
||||
}
|
||||
});
|
||||
const SocialMessage = props => (
|
||||
<props.as className={classNames('social-message', props.className)}>
|
||||
<FlexRow className="mod-social-message">
|
||||
<div className="social-message-content">
|
||||
{typeof props.iconSrc === 'undefined' ? [] : [
|
||||
<img
|
||||
alt={props.iconAlt}
|
||||
className={classNames('social-message-icon', props.imgClassName)}
|
||||
key="social-message-icon"
|
||||
src={props.iconSrc}
|
||||
/>
|
||||
]}
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
<span className="social-message-date">
|
||||
<FormattedRelative value={new Date(props.datetime)} />
|
||||
</span>
|
||||
</FlexRow>
|
||||
</props.as>
|
||||
);
|
||||
|
||||
SocialMessage.propTypes = {
|
||||
as: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
datetime: PropTypes.string.isRequired,
|
||||
iconAlt: PropTypes.string,
|
||||
iconSrc: PropTypes.string,
|
||||
imgClassName: PropTypes.string
|
||||
};
|
||||
|
||||
SocialMessage.defaultProps = {
|
||||
as: 'li'
|
||||
};
|
||||
|
||||
module.exports = SocialMessage;
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
var range = require('lodash.range');
|
||||
var React = require('react');
|
||||
const range = require('lodash.range');
|
||||
const React = require('react');
|
||||
|
||||
require('./spinner.scss');
|
||||
|
||||
var Spinner = React.createClass({
|
||||
// Adapted from http://tobiasahlin.com/spinkit/
|
||||
type: 'Spinner',
|
||||
render: function () {
|
||||
return (
|
||||
<div className="spinner">
|
||||
{range(1,13).map(function (id) {
|
||||
return <div className={'circle' + id + ' circle'}></div>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
// Adapted from http://tobiasahlin.com/spinkit/
|
||||
const Spinner = () => (
|
||||
<div className="spinner">
|
||||
{range(1, 13).map(id => (
|
||||
<div
|
||||
className={`circle${id} circle`}
|
||||
key={`circle${id}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
module.exports = Spinner;
|
||||
|
|
|
@ -1,28 +1,29 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./stepnavigation.scss');
|
||||
|
||||
var StepNavigation = React.createClass({
|
||||
type: 'Navigation',
|
||||
render: function () {
|
||||
return (
|
||||
<ul className={classNames('step-navigation', this.props.className)}>
|
||||
{Array.apply(null, Array(this.props.steps)).map(function (v, step) {
|
||||
return (
|
||||
<li key={step}
|
||||
className={classNames({
|
||||
active: step < this.props.active,
|
||||
selected: step === this.props.active
|
||||
})}
|
||||
>
|
||||
<div className="indicator" />
|
||||
</li>
|
||||
);
|
||||
}.bind(this))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
});
|
||||
const StepNavigation = props => (
|
||||
<ul className={classNames('step-navigation', props.className)}>
|
||||
{Array.apply(null, Array(props.steps)).map((v, step) => (
|
||||
<li
|
||||
className={classNames({
|
||||
active: step < props.active,
|
||||
selected: step === props.active
|
||||
})}
|
||||
key={step}
|
||||
>
|
||||
<div className="indicator" />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
|
||||
StepNavigation.propTypes = {
|
||||
active: PropTypes.number,
|
||||
className: PropTypes.string,
|
||||
steps: PropTypes.number
|
||||
};
|
||||
|
||||
module.exports = StepNavigation;
|
||||
|
|
|
@ -1,36 +1,38 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./subnavigation.scss');
|
||||
|
||||
/**
|
||||
/*
|
||||
* Container for a custom, horizontal list of navigation elements
|
||||
* that can be displayed within a view or component.
|
||||
*/
|
||||
var SubNavigation = React.createClass({
|
||||
type: 'SubNavigation',
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
align: 'middle'
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
const SubNavigation = props => (
|
||||
<div
|
||||
className={classNames(
|
||||
[
|
||||
'sub-nav',
|
||||
this.props.className
|
||||
props.className
|
||||
],
|
||||
{
|
||||
'sub-nav-align-left': this.props.align === 'left',
|
||||
'sub-nav-align-right': this.props.align === 'right'
|
||||
'sub-nav-align-left': props.align === 'left',
|
||||
'sub-nav-align-right': props.align === 'right'
|
||||
}
|
||||
);
|
||||
return (
|
||||
<div className={classes}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
|
||||
SubNavigation.propTypes = {
|
||||
align: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
SubNavigation.defaultProps = {
|
||||
align: 'middle'
|
||||
};
|
||||
|
||||
module.exports = SubNavigation;
|
||||
|
|
|
@ -1,28 +1,26 @@
|
|||
var classNames = require('classnames');
|
||||
var SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
|
||||
|
||||
require('./tabs.scss');
|
||||
|
||||
/**
|
||||
/*
|
||||
* Container for a custom, horizontal list of navigation elements
|
||||
* that can be displayed within a view or component.
|
||||
*/
|
||||
var Tabs = React.createClass({
|
||||
type: 'Tabs',
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'tabs',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<div className='tab-background'>
|
||||
<SubNavigation className={classes}>
|
||||
{this.props.children}
|
||||
</SubNavigation>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const Tabs = props => (
|
||||
<div className="tab-background">
|
||||
<SubNavigation className={classNames('tabs', props.className)}>
|
||||
{props.children}
|
||||
</SubNavigation>
|
||||
</div>
|
||||
);
|
||||
|
||||
Tabs.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = Tabs;
|
||||
|
|
|
@ -1,86 +1,100 @@
|
|||
var classNames = require('classnames');
|
||||
var connect = require('react-redux').connect;
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const connect = require('react-redux').connect;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var sessionActions = require('../../redux/session.js');
|
||||
const sessionActions = require('../../redux/session.js');
|
||||
|
||||
var TitleBanner = require('../title-banner/title-banner.jsx');
|
||||
var Button = require('../forms/button.jsx');
|
||||
var FlexRow = require('../flex-row/flex-row.jsx');
|
||||
const TitleBanner = require('../title-banner/title-banner.jsx');
|
||||
const Button = require('../forms/button.jsx');
|
||||
const FlexRow = require('../flex-row/flex-row.jsx');
|
||||
|
||||
require('./teacher-banner.scss');
|
||||
|
||||
var TeacherBanner = React.createClass({
|
||||
type: 'TeacherBanner',
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
messages: {
|
||||
'teacherbanner.greeting': 'Hi',
|
||||
'teacherbanner.subgreeting': 'Teacher Account',
|
||||
'teacherbanner.classesButton': 'My Classes',
|
||||
'teacherbanner.resourcesButton': 'Educator Resources',
|
||||
'teacherbanner.faqButton': 'Teacher Account FAQ'
|
||||
},
|
||||
session: {}
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'teacher-banner',
|
||||
this.props.className
|
||||
);
|
||||
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 TeacherBanner = props => (
|
||||
<TitleBanner className={classNames('teacher-banner', props.className)}>
|
||||
<FlexRow className="inner">
|
||||
<div className="welcome">
|
||||
{props.sessionStatus === sessionActions.Status.FETCHED ? (
|
||||
props.user ? [
|
||||
<h3 key="greeting">
|
||||
{props.messages['teacherbanner.greeting']},{' '}
|
||||
{props.user.username}
|
||||
</h3>,
|
||||
<p
|
||||
className="title-banner-p"
|
||||
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>
|
||||
);
|
||||
|
||||
var mapStateToProps = function (state) {
|
||||
return {
|
||||
session: state.session
|
||||
};
|
||||
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
|
||||
})
|
||||
};
|
||||
|
||||
var ConnectedTeacherBanner = connect(mapStateToProps)(TeacherBanner);
|
||||
TeacherBanner.defaultProps = {
|
||||
messages: {
|
||||
'teacherbanner.greeting': 'Hi',
|
||||
'teacherbanner.subgreeting': 'Teacher Account',
|
||||
'teacherbanner.classesButton': 'My Classes',
|
||||
'teacherbanner.resourcesButton': 'Educator Resources',
|
||||
'teacherbanner.faqButton': 'Teacher Account FAQ'
|
||||
},
|
||||
user: {}
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
sessionStatus: state.session.status,
|
||||
user: state.session.session.user
|
||||
});
|
||||
|
||||
const ConnectedTeacherBanner = connect(mapStateToProps)(TeacherBanner);
|
||||
|
||||
module.exports = ConnectedTeacherBanner;
|
||||
|
|
|
@ -1,186 +1,168 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./thumbnail.scss');
|
||||
|
||||
var Thumbnail = React.createClass({
|
||||
type: 'Thumbnail',
|
||||
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 = [];
|
||||
const Thumbnail = props => {
|
||||
const extra = [];
|
||||
const info = [];
|
||||
|
||||
if (this.props.loves && this.props.showLoves) {
|
||||
extra.push(
|
||||
<div
|
||||
key="loves"
|
||||
className="thumbnail-loves"
|
||||
title={this.props.loves + ' loves'}>
|
||||
{this.props.loves}
|
||||
</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}
|
||||
if (props.loves && props.showLoves) {
|
||||
extra.push(
|
||||
<div
|
||||
className="thumbnail-loves"
|
||||
key="loves"
|
||||
title={`${props.loves} 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>
|
||||
{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;
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./title-banner.scss');
|
||||
|
||||
var TitleBanner = React.createClass({
|
||||
type: 'TitleBanner',
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'title-banner',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<div className={classes}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const TitleBanner = props => (
|
||||
<div className={classNames('title-banner', props.className)}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
|
||||
TitleBanner.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = TitleBanner;
|
||||
|
|
|
@ -1,33 +1,39 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./tooltip.scss');
|
||||
|
||||
var Tooltip = React.createClass({
|
||||
type: 'Tooltip',
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
title: '',
|
||||
tipContent: ''
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
const Tooltip = props => (
|
||||
<span
|
||||
className={classNames(
|
||||
'tooltip',
|
||||
this.props.className,
|
||||
{overmax: (this.props.currentCharacters > this.props.maxCharacters)}
|
||||
);
|
||||
return (
|
||||
<span className={classes}>
|
||||
<span className="tip">
|
||||
<img src="/svgs/tooltip/info.svg" alt="info icon" />
|
||||
</span>
|
||||
<span className="expand">
|
||||
{this.props.tipContent}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
||||
props.className,
|
||||
{overmax: (props.currentCharacters > props.maxCharacters)}
|
||||
)}
|
||||
>
|
||||
<span className="tip">
|
||||
<img
|
||||
alt="info icon"
|
||||
src="/svgs/tooltip/info.svg"
|
||||
/>
|
||||
</span>
|
||||
<span className="expand">
|
||||
{props.tipContent}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
Tooltip.propTypes = {
|
||||
className: PropTypes.string,
|
||||
currentCharacters: PropTypes.number,
|
||||
maxCharacters: PropTypes.number,
|
||||
tipContent: PropTypes.node
|
||||
};
|
||||
|
||||
Tooltip.defaultProps = {
|
||||
title: '',
|
||||
tipContent: ''
|
||||
};
|
||||
|
||||
module.exports = Tooltip;
|
||||
|
|
|
@ -1,58 +1,68 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const classNames = require('classnames');
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('../forms/button.scss');
|
||||
require('./ttt-tile.scss');
|
||||
|
||||
var TTTTile = React.createClass({
|
||||
type: 'TTTTile',
|
||||
propTypes: {
|
||||
title: React.PropTypes.string.isRequired,
|
||||
description: React.PropTypes.string,
|
||||
thumbUrl: React.PropTypes.string.isRequired,
|
||||
tutorialLoc: React.PropTypes.string.isRequired,
|
||||
onGuideClick: React.PropTypes.func
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'ttt-tile',
|
||||
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>
|
||||
const TTTTile = props => (
|
||||
<div className={classNames('ttt-tile', props.className)}>
|
||||
<a href={props.tutorialLoc}>
|
||||
<div className="ttt-tile-tutorial">
|
||||
<div className="ttt-tile-image">
|
||||
<img
|
||||
alt=""
|
||||
className="ttt-tile-image-img"
|
||||
src={props.thumbUrl}
|
||||
/>
|
||||
<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">
|
||||
|
||||
</a>
|
||||
{this.props.onGuideClick && (
|
||||
<div className="ttt-tile-guides" onClick={this.props.onGuideClick}>
|
||||
<FormattedMessage id='tile.guides' defaultMessage='See Cards and Guides'/>
|
||||
<img className="ttt-tile-open-modal" src="/svgs/modal/open-blue.svg" />
|
||||
<div className="ttt-tile-tag">
|
||||
<FormattedMessage
|
||||
defaultMessage="Tutorial"
|
||||
id="ttt.tutorial"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<h4 className="ttt-tile-title">{props.title}</h4>
|
||||
<p className="ttt-tile-description">
|
||||
{props.description}
|
||||
</p>
|
||||
</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;
|
||||
|
|
|
@ -1,69 +1,81 @@
|
|||
var React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
var Box = require('../box/box.jsx');
|
||||
const Box = require('../box/box.jsx');
|
||||
|
||||
require('./welcome.scss');
|
||||
|
||||
var Welcome = React.createClass({
|
||||
type: 'Welcome',
|
||||
propTypes: {
|
||||
onDismiss: React.PropTypes.func
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
messages: {
|
||||
'welcome.welcomeToScratch': 'Welcome to Scratch!',
|
||||
'welcome.learn': 'Learn how to make a project in Scratch',
|
||||
'welcome.tryOut': 'Try out starter projects',
|
||||
'welcome.connect': 'Connect with other Scratchers'
|
||||
}
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Box title={this.props.messages['welcome.welcomeToScratch']}
|
||||
className="welcome"
|
||||
moreTitle="x"
|
||||
moreHref="#"
|
||||
moreProps={{
|
||||
className: 'close',
|
||||
title: 'Dismiss',
|
||||
onClick: this.props.onDismiss
|
||||
}}>
|
||||
const Welcome = props => (
|
||||
<Box
|
||||
className="welcome"
|
||||
moreHref="#"
|
||||
moreProps={{
|
||||
className: 'close',
|
||||
title: 'Dismiss',
|
||||
onClick: props.onDismiss
|
||||
}}
|
||||
moreTitle="x"
|
||||
title={props.messages['welcome.welcomeToScratch']}
|
||||
>
|
||||
<div className="welcome-col blue">
|
||||
<h4>
|
||||
<a href="/projects/editor/?tip_bar=getStarted">
|
||||
{props.messages['welcome.learn']}
|
||||
</a>
|
||||
</h4>
|
||||
<a href="/projects/editor/?tip_bar=getStarted">
|
||||
<img
|
||||
alt="Get Started"
|
||||
src="/images/welcome-learn.png"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div className="welcome-col green">
|
||||
<h4>
|
||||
<a href="/starter_projects/">
|
||||
{props.messages['welcome.tryOut']}
|
||||
</a>
|
||||
</h4>
|
||||
<a href="/starter_projects/">
|
||||
<img
|
||||
alt="Starter Projects"
|
||||
src="/images/welcome-try.png"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div className="welcome-col pink">
|
||||
<h4>
|
||||
<a href="/studios/146521/">
|
||||
{props.messages['welcome.connect']}
|
||||
</a>
|
||||
</h4>
|
||||
<a href="/studios/146521/">
|
||||
<img
|
||||
alt="Connect"
|
||||
src="/images/welcome-connect.png"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
|
||||
<div className="welcome-col blue">
|
||||
<h4>
|
||||
<a href="/projects/editor/?tip_bar=getStarted">
|
||||
{this.props.messages['welcome.learn']}
|
||||
</a>
|
||||
</h4>
|
||||
<a href="/projects/editor/?tip_bar=getStarted">
|
||||
<img src="/images/welcome-learn.png" alt="Get Started" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="welcome-col green">
|
||||
<h4>
|
||||
<a href="/starter_projects/">
|
||||
{this.props.messages['welcome.tryOut']}
|
||||
</a>
|
||||
</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>
|
||||
);
|
||||
Welcome.propTypes = {
|
||||
messages: PropTypes.shape({
|
||||
'welcome.welcomeToScratch': PropTypes.string,
|
||||
'welcome.learn': PropTypes.string,
|
||||
'welcome.tryOut': PropTypes.string,
|
||||
'welcome.connect': PropTypes.string
|
||||
}),
|
||||
onDismiss: PropTypes.func
|
||||
};
|
||||
|
||||
Welcome.defaultProps = {
|
||||
messages: {
|
||||
'welcome.welcomeToScratch': 'Welcome to Scratch!',
|
||||
'welcome.learn': 'Learn how to make a project in Scratch',
|
||||
'welcome.tryOut': 'Try out starter projects',
|
||||
'welcome.connect': 'Connect with other Scratchers'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Welcome;
|
||||
|
|
18
src/init.js
18
src/init.js
|
@ -1,12 +1,12 @@
|
|||
var jar = require('./lib/jar');
|
||||
var Raven = require('raven-js');
|
||||
const jar = require('./lib/jar');
|
||||
const Raven = require('raven-js');
|
||||
|
||||
/**
|
||||
* -----------------------------------------------------------------------------
|
||||
* Error handling
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
(function () {
|
||||
(() => {
|
||||
if (process.env.SENTRY_DSN !== '') {
|
||||
Raven.config(process.env.SENTRY_DSN).install();
|
||||
}
|
||||
|
@ -17,22 +17,22 @@ var Raven = require('raven-js');
|
|||
* L10N
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
(function () {
|
||||
/**
|
||||
(() => {
|
||||
/*
|
||||
* Bind locale code from cookie if available. Uses navigator language API as a fallback.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
function updateLocale () {
|
||||
var obj = jar.get('scratchlanguage');
|
||||
const updateLocale = () => {
|
||||
let obj = jar.get('scratchlanguage');
|
||||
if (typeof obj === 'undefined') {
|
||||
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.
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
window._locale = updateLocale();
|
||||
})();
|
||||
|
|
|
@ -47,6 +47,9 @@
|
|||
"general.myClass": "My Class",
|
||||
"general.myClasses": "My Classes",
|
||||
"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 didn’t 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.other": "Other",
|
||||
"general.offlineEditor": "Offline Editor",
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
var defaults = require('lodash.defaults');
|
||||
var xhr = require('xhr');
|
||||
const defaults = require('lodash.defaults');
|
||||
const xhr = require('xhr');
|
||||
|
||||
var jar = require('./jar');
|
||||
var log = require('./log');
|
||||
var urlParams = require('./url-params');
|
||||
const jar = require('./jar');
|
||||
const log = require('./log');
|
||||
const urlParams = require('./url-params');
|
||||
|
||||
/**
|
||||
* Helper method that constructs requests to the scratch api.
|
||||
|
@ -12,9 +12,11 @@ var urlParams = require('./url-params');
|
|||
* CSRF forgeries (see: https://www.squarefree.com/securitytips/web-developers.html#CSRF)
|
||||
*
|
||||
* 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 = function (opts, callback) {
|
||||
module.exports = (opts, callback) => {
|
||||
defaults(opts, {
|
||||
host: process.env.API_HOST,
|
||||
headers: {},
|
||||
|
@ -40,26 +42,29 @@ module.exports = function (opts, callback) {
|
|||
opts.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
|
||||
var apiRequest = function (opts) {
|
||||
if (opts.host !== '') {
|
||||
const apiRequest = options => {
|
||||
if (options.host !== '') {
|
||||
if ('withCredentials' in new XMLHttpRequest()) {
|
||||
opts.useXDR = false;
|
||||
options.useXDR = false;
|
||||
} else {
|
||||
// For IE < 10, we must use XDR for cross-domain requests. XDR does not support
|
||||
// custom headers.
|
||||
opts.useXDR = true;
|
||||
delete opts.headers;
|
||||
if (opts.authentication) {
|
||||
var authenticationParams = ['x-token=' + opts.authentication];
|
||||
var parts = opts.uri.split('?');
|
||||
var qs = (parts[1] || '').split('&').concat(authenticationParams).join('&');
|
||||
opts.uri = parts[0] + '?' + qs;
|
||||
options.useXDR = true;
|
||||
delete options.headers;
|
||||
if (options.authentication) {
|
||||
const authenticationParams = [`x-token=${options.authentication}`];
|
||||
const parts = options.uri.split('?');
|
||||
const qs = (parts[1] || '')
|
||||
.split('&')
|
||||
.concat(authenticationParams)
|
||||
.join('&');
|
||||
options.uri = `${parts[0]}?${qs}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
xhr(opts, function (err, res, body) {
|
||||
xhr(options, (err, res, body) => {
|
||||
if (err) log.error(err);
|
||||
if (opts.responseType === 'json' && typeof body === 'string') {
|
||||
if (options.responseType === 'json' && typeof body === 'string') {
|
||||
// IE doesn't parse responses as JSON without the json attribute,
|
||||
// even with responseType: 'json'.
|
||||
// See https://github.com/Raynos/xhr/issues/123
|
||||
|
@ -73,25 +78,25 @@ module.exports = function (opts, callback) {
|
|||
// [{success: true, redirect: "/location/to/redirect"}]
|
||||
try {
|
||||
if ('redirect' in body[0]) window.location = body[0].redirect;
|
||||
} catch (err) {
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
callback(err, body, res);
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
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) {
|
||||
opts.headers['X-Token'] = opts.authentication;
|
||||
}
|
||||
if (opts.useCsrf) {
|
||||
jar.use('scratchcsrftoken', '/csrf_token/', function (err, csrftoken) {
|
||||
jar.use('scratchcsrftoken', '/csrf_token/', (err, csrftoken) => {
|
||||
if (err) return log.error('Error while retrieving CSRF token', err);
|
||||
opts.headers['X-CSRFToken'] = csrftoken;
|
||||
apiRequest(opts);
|
||||
}.bind(this));
|
||||
});
|
||||
} else {
|
||||
apiRequest(opts);
|
||||
}
|
||||
|
|
|
@ -1,22 +1,28 @@
|
|||
module.exports = {};
|
||||
var countries = module.exports.data = require('iso-3166-2').data;
|
||||
const countries = module.exports.data = require('iso-3166-2').data;
|
||||
|
||||
module.exports.countryOptions = Object.keys(countries).map(function (code) {
|
||||
return {value: code.toLowerCase(), label: countries[code].name};
|
||||
}).sort(function (a, b) {
|
||||
return a.label < b.label ? -1 : 1;
|
||||
});
|
||||
|
||||
module.exports.subdivisionOptions =
|
||||
Object.keys(countries).reduce(function (subByCountry, code) {
|
||||
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.countryOptions = Object.keys(countries).map(code => ({
|
||||
value: code.toLowerCase(),
|
||||
label: countries[code].name
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
if (a.label < b.label) {
|
||||
return -1;
|
||||
}
|
||||
return 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;
|
||||
}, {});
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
/**
|
||||
* Takes an ISO time and returns a string representing how long ago the date
|
||||
* represents. For example, "2015-01-01T00:00:00" becomes "1 minute ago".
|
||||
*
|
||||
* Based on "JavaScript Pretty Date"
|
||||
* Copyright (c) 2011 John Resig (ejohn.org)
|
||||
* Licensed under the MIT and GPL licenses.
|
||||
*/
|
||||
|
||||
var Format = {
|
||||
date: function (stamp) {
|
||||
stamp = (stamp || '').replace(/-/g,'/').replace(/[TZ]/g,' ');
|
||||
|
||||
var date = new Date(stamp);
|
||||
var diff = (((new Date()).getTime() - date.getTime()) / 1000);
|
||||
var day_diff = Math.floor(diff / 86400);
|
||||
|
||||
if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
|
||||
return 'A while ago';
|
||||
}
|
||||
|
||||
return day_diff == 0 && (
|
||||
diff < 60 && 'Just now' ||
|
||||
diff < 120 && '1 minute ago' ||
|
||||
diff < 3600 && Math.floor( diff / 60 ) + ' minutes ago' ||
|
||||
diff < 7200 && '1 hour ago' ||
|
||||
diff < 86400 && Math.floor( diff / 3600 ) + ' hours ago') ||
|
||||
day_diff == 1 && 'Yesterday' ||
|
||||
day_diff < 7 && day_diff + ' days ago' ||
|
||||
day_diff < 31 && Math.ceil( day_diff / 7 ) + ' weeks ago';
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Format;
|
|
@ -1,9 +1,10 @@
|
|||
/* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
const frameless = {
|
||||
desktop: 942,
|
||||
tablet: 640,
|
||||
mobile: 480
|
||||
};
|
||||
|
||||
module.exports = frameless;
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
var requireAll = require('./require-all');
|
||||
var ReactIntl = require('react-intl');
|
||||
const requireAll = require('./require-all');
|
||||
const ReactIntl = require('react-intl');
|
||||
|
||||
var allLocaleData = requireAll(require.context('react-intl/locale-data', true, /^\.\/.*\.js$/));
|
||||
var customLocaleData = require('../../custom-locales.json');
|
||||
const allLocaleData = requireAll(require.context('react-intl/locale-data', true, /^\.\/.*\.js$/));
|
||||
const customLocaleData = require('../../custom-locales.json');
|
||||
|
||||
/**
|
||||
* Add all locales
|
||||
*/
|
||||
for (var locale in allLocaleData) {
|
||||
for (const locale in allLocaleData) {
|
||||
ReactIntl.addLocaleData(allLocaleData[locale]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom locales to react-intl if it doesn't have them.
|
||||
*/
|
||||
for (var customLocale in customLocaleData) {
|
||||
for (const customLocale in customLocaleData) {
|
||||
ReactIntl.addLocaleData(customLocaleData[customLocale]);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
var cookie = require('cookie');
|
||||
var defaults = require('lodash.defaults');
|
||||
var xhr = require('xhr');
|
||||
var pako = require('pako');
|
||||
const cookie = require('cookie');
|
||||
const defaults = require('lodash.defaults');
|
||||
const xhr = require('xhr');
|
||||
const pako = require('pako');
|
||||
|
||||
/**
|
||||
* Module that handles coookie interactions.
|
||||
|
@ -11,16 +11,16 @@ var pako = require('pako');
|
|||
* set(name, value) – synchronously sets the cookie
|
||||
* use(name, uri, callback) – can by sync or async, gets cookie from the uri if not there.
|
||||
*/
|
||||
var Jar = {
|
||||
unsign: function (value, callback) {
|
||||
const Jar = {
|
||||
unsign: (value, callback) => {
|
||||
// Return the usable content portion of a signed, compressed cookie generated by
|
||||
// Django's signing module
|
||||
// https://github.com/django/django/blob/stable/1.8.x/django/core/signing.py
|
||||
if (typeof value === 'undefined') return callback(null, value);
|
||||
|
||||
try {
|
||||
var b64Data = value.split(':')[0];
|
||||
var decompress = false;
|
||||
let b64Data = value.split(':')[0];
|
||||
let decompress = false;
|
||||
if (b64Data[0] === '.') {
|
||||
decompress = true;
|
||||
b64Data = b64Data.substring(1);
|
||||
|
@ -29,24 +29,29 @@ var Jar = {
|
|||
// Django makes its base64 strings url safe by replacing + and / with - and _ respectively
|
||||
// using base64.urlsafe_b64encode
|
||||
// https://docs.python.org/2/library/base64.html#base64.b64encode
|
||||
b64Data = b64Data.replace(/[-_]/g, function (c) {return {'-':'+', '_':'/'}[c]; });
|
||||
var strData = atob(b64Data);
|
||||
b64Data = b64Data.replace(
|
||||
/[-_]/g,
|
||||
c => ({
|
||||
'-': '+',
|
||||
'_': '/'
|
||||
}[c])
|
||||
);
|
||||
let strData = atob(b64Data);
|
||||
|
||||
if (decompress) {
|
||||
var charData = strData.split('').map(function (c) { return c.charCodeAt(0); });
|
||||
var binData = new Uint8Array(charData);
|
||||
var data = pako.inflate(binData);
|
||||
const charData = strData.split('').map(c => (c.charCodeAt(0)));
|
||||
const binData = new Uint8Array(charData);
|
||||
const data = pako.inflate(binData);
|
||||
strData = String.fromCharCode.apply(null, new Uint16Array(data));
|
||||
}
|
||||
|
||||
return callback(null, strData);
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
},
|
||||
get: function (name, callback) {
|
||||
get: (name, callback) => {
|
||||
// Get cookie by name
|
||||
var obj = cookie.parse(document.cookie) || {};
|
||||
const obj = cookie.parse(document.cookie) || {};
|
||||
|
||||
// Handle optional callback
|
||||
if (typeof callback === 'function') {
|
||||
|
@ -56,44 +61,44 @@ var Jar = {
|
|||
|
||||
return obj[name];
|
||||
},
|
||||
use: function (name, uri, callback) {
|
||||
use: (name, uri, callback) => {
|
||||
// Attempt to get cookie
|
||||
Jar.get(name, function (err, obj) {
|
||||
Jar.get(name, (err, obj) => {
|
||||
if (typeof obj !== 'undefined') return callback(null, obj);
|
||||
|
||||
// Make XHR request to cookie setter uri
|
||||
xhr({
|
||||
uri: uri
|
||||
}, function (err) {
|
||||
if (err) return callback(err);
|
||||
}, e => {
|
||||
if (e) return callback(e);
|
||||
Jar.get(name, callback);
|
||||
});
|
||||
});
|
||||
},
|
||||
set: function (name, value, opts) {
|
||||
set: (name, value, opts) => {
|
||||
opts = opts || {};
|
||||
defaults(opts, {
|
||||
expires: new Date(new Date().setYear(new Date().getFullYear() + 1))
|
||||
});
|
||||
opts.path = '/';
|
||||
var obj = cookie.serialize(name, value, opts);
|
||||
const obj = cookie.serialize(name, value, opts);
|
||||
document.cookie = obj;
|
||||
},
|
||||
getUnsignedValue: function (cookieName, signedValue, callback) {
|
||||
getUnsignedValue: (cookieName, signedValue, callback) => {
|
||||
// Get a value from a signed object
|
||||
Jar.get(cookieName, function (err, value) {
|
||||
Jar.get(cookieName, (err, value) => {
|
||||
if (err) return callback(err);
|
||||
if (typeof value === 'undefined') return callback(null, value);
|
||||
|
||||
Jar.unsign(value, function (err, contents) {
|
||||
if (err) return callback(err);
|
||||
Jar.unsign(value, (e, contents) => {
|
||||
if (e) return callback(e);
|
||||
|
||||
try {
|
||||
var data = JSON.parse(contents);
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
const data = JSON.parse(contents);
|
||||
return callback(null, data[signedValue]);
|
||||
} catch (error) {
|
||||
return callback(error);
|
||||
}
|
||||
return callback(null, data[signedValue]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var minilog = require('minilog');
|
||||
const minilog = require('minilog');
|
||||
minilog.enable();
|
||||
|
||||
module.exports = minilog('www');
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
var redux = require('redux');
|
||||
var thunk = require('redux-thunk').default;
|
||||
const redux = require('redux');
|
||||
const thunk = require('redux-thunk').default;
|
||||
// JSX syntax transforms to React.createElement
|
||||
var React = require('react'); // eslint-disable-line
|
||||
var ReactDOM = require('react-dom');
|
||||
var StoreProvider = require('react-redux').Provider;
|
||||
const React = require('react'); // eslint-disable-line
|
||||
const ReactDOM = require('react-dom');
|
||||
const StoreProvider = require('react-redux').Provider;
|
||||
|
||||
var IntlProvider = require('./intl.jsx').IntlProvider;
|
||||
var permissionsActions = require('../redux/permissions.js');
|
||||
var sessionActions = require('../redux/session.js');
|
||||
var reducer = require('../redux/reducer.js');
|
||||
const IntlProvider = require('./intl.jsx').IntlProvider;
|
||||
const permissionsActions = require('../redux/permissions.js');
|
||||
const sessionActions = require('../redux/session.js');
|
||||
const reducer = require('../redux/reducer.js');
|
||||
|
||||
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")
|
||||
var locale = window._locale || 'en';
|
||||
var messages = {};
|
||||
let locale = window._locale || 'en';
|
||||
let messages = {};
|
||||
if (typeof window._messages !== 'undefined') {
|
||||
if (typeof window._messages[locale] === 'undefined') {
|
||||
// Fall back on the split
|
||||
|
@ -28,8 +34,8 @@ var render = function (jsx, element, reducers) {
|
|||
messages = window._messages[locale];
|
||||
}
|
||||
|
||||
var allReducers = reducer(reducers);
|
||||
var store = redux.createStore(
|
||||
const allReducers = reducer(reducers);
|
||||
const store = redux.createStore(
|
||||
allReducers,
|
||||
redux.applyMiddleware(thunk)
|
||||
);
|
||||
|
@ -37,7 +43,10 @@ var render = function (jsx, element, reducers) {
|
|||
// Render view component
|
||||
ReactDOM.render(
|
||||
<StoreProvider store={store}>
|
||||
<IntlProvider locale={locale} messages={messages}>
|
||||
<IntlProvider
|
||||
locale={locale}
|
||||
messages={messages}
|
||||
>
|
||||
{jsx}
|
||||
</IntlProvider>
|
||||
</StoreProvider>,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
var requireAll = function (requireContext) {
|
||||
return requireContext.keys().map(requireContext);
|
||||
};
|
||||
const requireAll = requireContext => (
|
||||
requireContext.keys().map(requireContext)
|
||||
);
|
||||
|
||||
module.exports = requireAll;
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
/*
|
||||
* Function that shuffles an array using a Fisher-Yates shuffle.
|
||||
*/
|
||||
|
||||
module.exports.shuffle = function (arr) {
|
||||
var i, j = 0;
|
||||
var temp = null;
|
||||
module.exports.shuffle = arr => {
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
let temp = null;
|
||||
if (arr) {
|
||||
var tempArray = arr.slice(0);
|
||||
} else {
|
||||
return arr;
|
||||
const tempArray = arr.slice(0);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
return arr;
|
||||
};
|
||||
|
|
|
@ -2,21 +2,18 @@
|
|||
* urlParams({a: 1, b: 2, c: 3})
|
||||
* // a=1&b=2&c=3
|
||||
*/
|
||||
module.exports = function urlParams (values) {
|
||||
return Object
|
||||
.keys(values)
|
||||
.map(function (key) {
|
||||
var value = typeof values[key] === 'undefined' ? '' : values[key];
|
||||
function encodeKeyValuePair (value) {
|
||||
return [key, value]
|
||||
.map(encodeURIComponent)
|
||||
.join('=');
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(encodeKeyValuePair).join('&');
|
||||
} else {
|
||||
return encodeKeyValuePair(value);
|
||||
}
|
||||
})
|
||||
.join('&');
|
||||
};
|
||||
const params = values => (
|
||||
Object.keys(values).map(key => {
|
||||
const value = typeof values[key] === 'undefined' ? '' : values[key];
|
||||
const encodeKeyValuePair = val => (
|
||||
[key, val].map(encodeURIComponent).join('=')
|
||||
);
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(encodeKeyValuePair).join('&');
|
||||
}
|
||||
return encodeKeyValuePair(value);
|
||||
})
|
||||
.join('&')
|
||||
);
|
||||
|
||||
module.exports = params;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
var keyMirror = require('keymirror');
|
||||
var api = require('../lib/api');
|
||||
const keyMirror = require('keymirror');
|
||||
const api = require('../lib/api');
|
||||
|
||||
var Types = keyMirror({
|
||||
const Types = keyMirror({
|
||||
SET_DETAILS: null,
|
||||
SET_DETAILS_FETCHING: null,
|
||||
SET_DETAILS_ERROR: null
|
||||
});
|
||||
|
||||
module.exports.detailsReducer = function (state, action) {
|
||||
module.exports.detailsReducer = (state, action) => {
|
||||
if (typeof state === 'undefined') {
|
||||
state = {};
|
||||
}
|
||||
|
@ -23,61 +23,50 @@ module.exports.detailsReducer = function (state, action) {
|
|||
}
|
||||
};
|
||||
|
||||
module.exports.setDetailsError = function (error) {
|
||||
return {
|
||||
type: Types.SET_DETAILS_ERROR,
|
||||
error: error
|
||||
};
|
||||
};
|
||||
module.exports.setDetailsError = error => ({
|
||||
type: Types.SET_DETAILS_ERROR,
|
||||
error: error
|
||||
});
|
||||
|
||||
module.exports.setDetails = function (details) {
|
||||
return {
|
||||
type: Types.SET_DETAILS,
|
||||
details: details
|
||||
};
|
||||
};
|
||||
module.exports.setDetails = details => ({
|
||||
type: Types.SET_DETAILS,
|
||||
details: details
|
||||
});
|
||||
|
||||
module.exports.setDetailsFetching = function () {
|
||||
return {
|
||||
type: Types.SET_DETAILS_FETCHING,
|
||||
fetching: true
|
||||
};
|
||||
};
|
||||
module.exports.setDetailsFetching = () => ({
|
||||
type: Types.SET_DETAILS_FETCHING,
|
||||
fetching: true
|
||||
});
|
||||
|
||||
module.exports.startGetDetails = function (id) {
|
||||
return function (dispatch) {
|
||||
dispatch(module.exports.setDetailsFetching());
|
||||
dispatch(module.exports.getDetails(id));
|
||||
};
|
||||
};
|
||||
module.exports.getDetails = id => (dispatch => {
|
||||
api({
|
||||
uri: `/conference/${id}/details`
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setDetailsError(err));
|
||||
return;
|
||||
}
|
||||
|
||||
module.exports.getDetails = function (id) {
|
||||
return function (dispatch) {
|
||||
api({
|
||||
uri: '/conference/' + id + '/details'
|
||||
}, function (err, body) {
|
||||
if (err) {
|
||||
dispatch(module.exports.setDetailsError(err));
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof body !== 'undefined') {
|
||||
var columns = body.columns;
|
||||
if (body.rows) {
|
||||
var details = body.rows[0];
|
||||
var detailsObject = details.reduce(function (prev, cur, index) {
|
||||
prev[columns[index]] = cur;
|
||||
return prev;
|
||||
}, {});
|
||||
dispatch(module.exports.setDetails(detailsObject));
|
||||
} else {
|
||||
dispatch(module.exports.setDetailsError('Not Found'));
|
||||
}
|
||||
return;
|
||||
if (typeof body !== 'undefined') {
|
||||
const columns = body.columns;
|
||||
if (body.rows) {
|
||||
const details = body.rows[0];
|
||||
const detailsObject = details.reduce((prev, cur, index) => {
|
||||
prev[columns[index]] = cur;
|
||||
return prev;
|
||||
}, {});
|
||||
dispatch(module.exports.setDetails(detailsObject));
|
||||
} else {
|
||||
dispatch(module.exports.setDetailsError('An unexpected error occurred'));
|
||||
return;
|
||||
dispatch(module.exports.setDetailsError('Not Found'));
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setDetailsError('An unexpected error occurred'));
|
||||
return;
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.startGetDetails = id => (dispatch => {
|
||||
dispatch(module.exports.setDetailsFetching());
|
||||
dispatch(module.exports.getDetails(id));
|
||||
});
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
var keyMirror = require('keymirror');
|
||||
var api = require('../lib/api');
|
||||
const keyMirror = require('keymirror');
|
||||
const api = require('../lib/api');
|
||||
|
||||
var Types = keyMirror({
|
||||
const Types = keyMirror({
|
||||
SET_SCHEDULE: null,
|
||||
SET_SCHEDULE_FETCHING: null,
|
||||
SET_SCHEDULE_ERROR: null
|
||||
});
|
||||
|
||||
module.exports.scheduleReducer = function (state, action) {
|
||||
module.exports.scheduleReducer = (state, action) => {
|
||||
if (typeof state === 'undefined') {
|
||||
state = {
|
||||
timeSlots: [],
|
||||
|
@ -26,40 +26,27 @@ module.exports.scheduleReducer = function (state, action) {
|
|||
}
|
||||
};
|
||||
|
||||
module.exports.setSchedule = function (schedule) {
|
||||
return {
|
||||
type: Types.SET_SCHEDULE,
|
||||
schedule: schedule
|
||||
};
|
||||
};
|
||||
module.exports.setSchedule = schedule => ({
|
||||
type: Types.SET_SCHEDULE,
|
||||
schedule: schedule
|
||||
});
|
||||
|
||||
module.exports.setScheduleFetching = function () {
|
||||
return {
|
||||
type: Types.SET_SCHEDULE_FETCHING,
|
||||
fetching: true
|
||||
};
|
||||
};
|
||||
module.exports.setScheduleFetching = () => ({
|
||||
type: Types.SET_SCHEDULE_FETCHING,
|
||||
fetching: true
|
||||
});
|
||||
|
||||
module.exports.setScheduleError = function (error) {
|
||||
return {
|
||||
type: Types.SET_SCHEDULE_ERROR,
|
||||
error: error
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.startGetSchedule = function (day) {
|
||||
return function (dispatch) {
|
||||
dispatch(module.exports.setScheduleFetching());
|
||||
dispatch(module.exports.getDaySchedule(day));
|
||||
};
|
||||
};
|
||||
module.exports.setScheduleError = error => ({
|
||||
type: Types.SET_SCHEDULE_ERROR,
|
||||
error: error
|
||||
});
|
||||
|
||||
// group periods of time by start time
|
||||
module.exports.sortTimeSlots = function (timeSlot1, timeSlot2) {
|
||||
var timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a') ? true : false;
|
||||
var timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a') ? true : false;
|
||||
var timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1));
|
||||
var timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1));
|
||||
module.exports.sortTimeSlots = (timeSlot1, timeSlot2) => {
|
||||
const timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a');
|
||||
const timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a');
|
||||
let timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1), 10);
|
||||
let timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1), 10);
|
||||
|
||||
// convert to 24-hour for sorting
|
||||
if (timeSlot1Time !== 12 && !timeSlot1Am) {
|
||||
|
@ -71,68 +58,67 @@ module.exports.sortTimeSlots = function (timeSlot1, timeSlot2) {
|
|||
|
||||
if (timeSlot1Time < timeSlot2Time) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the schedule for the given day from the api
|
||||
* @param {String} day Day of the conference (Thursday, Friday or Satrurday)
|
||||
* @param {string} day Day of the conference (Thursday, Friday or Satrurday)
|
||||
*
|
||||
* @return {Object} Schedule for the day, broken into chunks
|
||||
* @return {object} Schedule for the day, broken into chunks
|
||||
*/
|
||||
module.exports.getDaySchedule = function (day) {
|
||||
return function (dispatch) {
|
||||
api({
|
||||
uri: '/conference/schedule/' + day
|
||||
}, function (err, body) {
|
||||
if (err) {
|
||||
dispatch(module.exports.setScheduleError(err));
|
||||
return;
|
||||
}
|
||||
module.exports.getDaySchedule = day => (dispatch => {
|
||||
api({
|
||||
uri: `/conference/schedule/${day}`
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setScheduleError(err));
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof body !== 'undefined') {
|
||||
var columns = body.columns;
|
||||
var rows = body.rows || [];
|
||||
// Group events by the time period in which they occur (for presentation)
|
||||
var scheduleByTimeSlot = rows.reduce(function (prev, cur) {
|
||||
var cleanedRow = {};
|
||||
for (var i = 0; i < columns.length; i++) {
|
||||
if (cur[i].length > 0) {
|
||||
cleanedRow[columns[i]] = cur[i];
|
||||
}
|
||||
if (typeof body !== 'undefined') {
|
||||
const columns = body.columns;
|
||||
const rows = body.rows || [];
|
||||
// Group events by the time period in which they occur (for presentation)
|
||||
const scheduleByTimeSlot = rows.reduce((prev, cur) => {
|
||||
const cleanedRow = {};
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
if (cur[i].length > 0) {
|
||||
cleanedRow[columns[i]] = cur[i];
|
||||
}
|
||||
cleanedRow['uri'] = '/conference/2016/' + cleanedRow.rowid + '/details';
|
||||
var timeSlot = cleanedRow.Chunk + cleanedRow.Start;
|
||||
if (typeof prev.timeSlots[timeSlot] === 'undefined') {
|
||||
prev.timeSlots[timeSlot] = [cleanedRow];
|
||||
prev.info.push({
|
||||
name: cleanedRow.Chunk,
|
||||
time: cleanedRow.Start
|
||||
});
|
||||
} else {
|
||||
prev.timeSlots[timeSlot].push(cleanedRow);
|
||||
}
|
||||
return prev;
|
||||
}, {timeSlots: [], info: []});
|
||||
}
|
||||
cleanedRow.uri = `/conference/2016/${cleanedRow.rowid}/details`;
|
||||
const timeSlot = cleanedRow.Chunk + cleanedRow.Start;
|
||||
if (typeof prev.timeSlots[timeSlot] === 'undefined') {
|
||||
prev.timeSlots[timeSlot] = [cleanedRow];
|
||||
prev.info.push({
|
||||
name: cleanedRow.Chunk,
|
||||
time: cleanedRow.Start
|
||||
});
|
||||
} else {
|
||||
prev.timeSlots[timeSlot].push(cleanedRow);
|
||||
}
|
||||
return prev;
|
||||
}, {timeSlots: [], info: []});
|
||||
|
||||
scheduleByTimeSlot.info.sort(module.exports.sortTimeSlots);
|
||||
var schedule = scheduleByTimeSlot.info.map(function (timeSlot) {
|
||||
return {
|
||||
info: timeSlot,
|
||||
items: scheduleByTimeSlot.timeSlots[timeSlot.name + timeSlot.time]
|
||||
};
|
||||
});
|
||||
dispatch(module.exports.setSchedule({
|
||||
timeSlots: schedule,
|
||||
day: day
|
||||
}));
|
||||
return;
|
||||
} else {
|
||||
dispatch(module.exports.setScheduleError('An unexpected error occurred'));
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
scheduleByTimeSlot.info.sort(module.exports.sortTimeSlots);
|
||||
const schedule = scheduleByTimeSlot.info.map(timeSlot => ({
|
||||
info: timeSlot,
|
||||
items: scheduleByTimeSlot.timeSlots[timeSlot.name + timeSlot.time]
|
||||
}));
|
||||
dispatch(module.exports.setSchedule({
|
||||
timeSlots: schedule,
|
||||
day: day
|
||||
}));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setScheduleError('An unexpected error occurred'));
|
||||
return;
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.startGetSchedule = day => (dispatch => {
|
||||
dispatch(module.exports.setScheduleFetching());
|
||||
dispatch(module.exports.getDaySchedule(day));
|
||||
});
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
var keyMirror = require('keymirror');
|
||||
var defaults = require('lodash.defaults');
|
||||
const keyMirror = require('keymirror');
|
||||
const defaults = require('lodash.defaults');
|
||||
|
||||
var api = require('../lib/api');
|
||||
const api = require('../lib/api');
|
||||
|
||||
var Types = keyMirror({
|
||||
const Types = keyMirror({
|
||||
SET_MESSAGE_COUNT: null,
|
||||
SET_MESSAGE_COUNT_ERROR: null,
|
||||
SET_STATUS: null
|
||||
});
|
||||
|
||||
module.exports.getInitialState = function (){
|
||||
return {messageCount: 0};
|
||||
};
|
||||
const getInitialState = () => ({
|
||||
messageCount: 0
|
||||
});
|
||||
|
||||
module.exports.messageCountReducer = function (state, action) {
|
||||
module.exports.messageCountReducer = (state, action) => {
|
||||
// Reducer for handling changes to session state
|
||||
if (typeof state === 'undefined') {
|
||||
state = module.exports.getInitialState();
|
||||
state = getInitialState();
|
||||
}
|
||||
switch (action.type) {
|
||||
case Types.SET_MESSAGE_COUNT:
|
||||
|
@ -31,40 +31,32 @@ module.exports.messageCountReducer = function (state, action) {
|
|||
}
|
||||
};
|
||||
|
||||
module.exports.setSessionError = function (error) {
|
||||
return {
|
||||
type: Types.SET_MESSAGE_COUNT_ERROR,
|
||||
error: error
|
||||
};
|
||||
};
|
||||
module.exports.setSessionError = error => ({
|
||||
type: Types.SET_MESSAGE_COUNT_ERROR,
|
||||
error: error
|
||||
});
|
||||
|
||||
module.exports.setCount = function (count) {
|
||||
return {
|
||||
type: Types.SET_MESSAGE_COUNT,
|
||||
count: count
|
||||
};
|
||||
};
|
||||
module.exports.setCount = count => ({
|
||||
type: Types.SET_MESSAGE_COUNT,
|
||||
count: count
|
||||
});
|
||||
|
||||
module.exports.setStatus = function (status){
|
||||
return {
|
||||
type: Types.SET_STATUS,
|
||||
status: status
|
||||
};
|
||||
};
|
||||
module.exports.setStatus = status => ({
|
||||
type: Types.SET_STATUS,
|
||||
status: status
|
||||
});
|
||||
|
||||
module.exports.getCount = function (username) {
|
||||
return function (dispatch) {
|
||||
api({
|
||||
method: 'get',
|
||||
uri: '/users/' + username + '/messages/count'
|
||||
}, function (err, body) {
|
||||
if (err) {
|
||||
dispatch(module.exports.setCount(0));
|
||||
dispatch(module.exports.setSessionError(err));
|
||||
return;
|
||||
}
|
||||
var count = parseInt(body.count, 10);
|
||||
dispatch(module.exports.setCount(count));
|
||||
});
|
||||
};
|
||||
};
|
||||
module.exports.getCount = username => (dispatch => {
|
||||
api({
|
||||
method: 'get',
|
||||
uri: `/users/${username}/messages/count`
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setCount(0));
|
||||
dispatch(module.exports.setSessionError(err));
|
||||
return;
|
||||
}
|
||||
const count = parseInt(body.count, 10);
|
||||
dispatch(module.exports.setCount(count));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
var defaults = require('lodash.defaults');
|
||||
var defaultsDeep = require('lodash.defaultsdeep');
|
||||
var keyMirror = require('keymirror');
|
||||
const defaults = require('lodash.defaults');
|
||||
const defaultsDeep = require('lodash.defaultsdeep');
|
||||
const keyMirror = require('keymirror');
|
||||
|
||||
var api = require('../lib/api');
|
||||
var log = require('../lib/log');
|
||||
var messageCountActions = require('./message-count.js');
|
||||
const api = require('../lib/api');
|
||||
const log = require('../lib/log');
|
||||
const messageCountActions = require('./message-count.js');
|
||||
|
||||
module.exports.Status = keyMirror({
|
||||
FETCHED: null,
|
||||
|
@ -17,23 +17,21 @@ module.exports.Status = keyMirror({
|
|||
DELETE_ERROR: null
|
||||
});
|
||||
|
||||
module.exports.getInitialState = function () {
|
||||
return {
|
||||
status: {
|
||||
admin: module.exports.Status.NOT_FETCHED,
|
||||
message: module.exports.Status.NOT_FETCHED,
|
||||
clear: module.exports.Status.NOT_FETCHED,
|
||||
delete: module.exports.Status.NOT_FETCHED
|
||||
},
|
||||
messages: {
|
||||
admin: [],
|
||||
social: [],
|
||||
invite: {}
|
||||
}
|
||||
};
|
||||
};
|
||||
module.exports.getInitialState = () => ({
|
||||
status: {
|
||||
admin: module.exports.Status.NOT_FETCHED,
|
||||
message: module.exports.Status.NOT_FETCHED,
|
||||
clear: module.exports.Status.NOT_FETCHED,
|
||||
delete: module.exports.Status.NOT_FETCHED
|
||||
},
|
||||
messages: {
|
||||
admin: [],
|
||||
social: [],
|
||||
invite: {}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.messagesReducer = function (state, action) {
|
||||
module.exports.messagesReducer = (state, action) => {
|
||||
if (typeof state === 'undefined') {
|
||||
state = module.exports.getInitialState();
|
||||
}
|
||||
|
@ -68,75 +66,61 @@ module.exports.messagesReducer = function (state, action) {
|
|||
}
|
||||
};
|
||||
|
||||
module.exports.setMessagesError = function (error) {
|
||||
return {
|
||||
type: 'ERROR',
|
||||
error: error
|
||||
};
|
||||
};
|
||||
module.exports.setMessagesError = error => ({
|
||||
type: 'ERROR',
|
||||
error: error
|
||||
});
|
||||
|
||||
module.exports.setMessages = function (messages) {
|
||||
return {
|
||||
type: 'SET_MESSAGES',
|
||||
messages: messages
|
||||
};
|
||||
};
|
||||
module.exports.setMessages = messages => ({
|
||||
type: 'SET_MESSAGES',
|
||||
messages: messages
|
||||
});
|
||||
|
||||
module.exports.setMessagesOffset = function (offset) {
|
||||
return {
|
||||
type: 'SET_MESSAGES_OFFSET',
|
||||
offset: offset
|
||||
};
|
||||
};
|
||||
module.exports.setMessagesOffset = offset => ({
|
||||
type: 'SET_MESSAGES_OFFSET',
|
||||
offset: offset
|
||||
});
|
||||
|
||||
module.exports.setAdminMessages = function (messages) {
|
||||
return {
|
||||
type: 'SET_ADMIN_MESSAGES',
|
||||
messages: messages
|
||||
};
|
||||
};
|
||||
module.exports.setAdminMessages = messages => ({
|
||||
type: 'SET_ADMIN_MESSAGES',
|
||||
messages: messages
|
||||
});
|
||||
|
||||
module.exports.setScratcherInvite = function (invite) {
|
||||
return {
|
||||
type: 'SET_SCRATCHER_INVITE',
|
||||
invite: invite
|
||||
};
|
||||
};
|
||||
module.exports.setScratcherInvite = invite => ({
|
||||
type: 'SET_SCRATCHER_INVITE',
|
||||
invite: invite
|
||||
});
|
||||
|
||||
module.exports.setStatus = function (type, status){
|
||||
return {
|
||||
type: type,
|
||||
status: status
|
||||
};
|
||||
};
|
||||
module.exports.setStatus = (type, status) => ({
|
||||
type: type,
|
||||
status: status
|
||||
});
|
||||
|
||||
/**
|
||||
* Sends a request to mark one's unread messages count as cleared.
|
||||
* @return {null} returns nothing
|
||||
*/
|
||||
module.exports.clearMessageCount = function () {
|
||||
return function (dispatch) {
|
||||
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING));
|
||||
api({
|
||||
host: '',
|
||||
uri: '/site-api/messages/messages-clear/',
|
||||
method: 'POST',
|
||||
useCsrf: true
|
||||
}, function (err, body) {
|
||||
if (err) {
|
||||
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR));
|
||||
dispatch(module.exports.setMessagesError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body !== 'undefined' && !body.success) {
|
||||
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR));
|
||||
dispatch(module.exports.setMessagesError('messages not cleared'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHED));
|
||||
});
|
||||
};
|
||||
};
|
||||
module.exports.clearMessageCount = () => (dispatch => {
|
||||
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING));
|
||||
api({
|
||||
host: '',
|
||||
uri: '/site-api/messages/messages-clear/',
|
||||
method: 'POST',
|
||||
useCsrf: true
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR));
|
||||
dispatch(module.exports.setMessagesError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body !== 'undefined' && !body.success) {
|
||||
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR));
|
||||
dispatch(module.exports.setMessagesError('messages not cleared'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHED));
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Marks an admin message as read, dismissing it from the page
|
||||
|
@ -146,8 +130,8 @@ module.exports.clearMessageCount = function () {
|
|||
* @param {object[]} adminMessages current list of admin messages retrieved
|
||||
* @return {null} returns nothing
|
||||
*/
|
||||
module.exports.clearAdminMessage = function (messageType, messageId, messageCount, adminMessages) {
|
||||
return function (dispatch) {
|
||||
module.exports.clearAdminMessage = (messageType, messageId, messageCount, adminMessages) => (
|
||||
dispatch => {
|
||||
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING));
|
||||
api({
|
||||
host: '',
|
||||
|
@ -158,7 +142,7 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
|
|||
alertType: messageType,
|
||||
alertId: messageId
|
||||
}
|
||||
}, function (err, body) {
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.DELETE_ERROR));
|
||||
dispatch(module.exports.setMessagesError(err));
|
||||
|
@ -175,9 +159,9 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
|
|||
dispatch(module.exports.setScratcherInvite({}));
|
||||
} else {
|
||||
// find the admin message and remove it
|
||||
var toRemove = -1;
|
||||
for (var i in adminMessages) {
|
||||
if (adminMessages[i].id === messageId) {
|
||||
let toRemove = -1;
|
||||
for (const i of adminMessages) {
|
||||
if (i.id === messageId) {
|
||||
toRemove = i;
|
||||
break;
|
||||
}
|
||||
|
@ -188,8 +172,8 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
|
|||
dispatch(messageCountActions.setCount(messageCount - 1));
|
||||
dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.FETCHED));
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets a user's messages to be displayed on the /messages page
|
||||
|
@ -201,7 +185,7 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
|
|||
* @param {string} [opts.filter] type of messages to return
|
||||
* @return {null} returns nothing
|
||||
*/
|
||||
module.exports.getMessages = function (username, token, opts) {
|
||||
module.exports.getMessages = (username, token, opts) => {
|
||||
opts = defaults(opts, {
|
||||
messages: [],
|
||||
offset: 0,
|
||||
|
@ -209,17 +193,17 @@ module.exports.getMessages = function (username, token, opts) {
|
|||
clearCount: true
|
||||
});
|
||||
|
||||
var filterArg = '';
|
||||
let filterArg = '';
|
||||
if (opts.filter.length > 0) {
|
||||
filterArg = '&filter=' + opts.filter;
|
||||
filterArg = `&filter=${opts.filter}`;
|
||||
}
|
||||
|
||||
return function (dispatch) {
|
||||
return dispatch => {
|
||||
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: '/users/' + username + '/messages?limit=40&offset=' + opts.offset + filterArg,
|
||||
uri: `/users/${username}/messages?limit=40&offset=${opts.offset}${filterArg}`,
|
||||
authentication: token
|
||||
}, function (err, body) {
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.MESSAGES_ERROR));
|
||||
dispatch(module.exports.setMessagesError(err));
|
||||
|
@ -246,30 +230,28 @@ module.exports.getMessages = function (username, token, opts) {
|
|||
* @param {string} token the user's unique token for auth
|
||||
* @return {null} returns nothing
|
||||
*/
|
||||
module.exports.getAdminMessages = function (username, token) {
|
||||
return function (dispatch) {
|
||||
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: '/users/' + username + '/messages/admin',
|
||||
authentication: token
|
||||
}, function (err, body) {
|
||||
if (err) {
|
||||
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR));
|
||||
dispatch(module.exports.setMessagesError(err));
|
||||
dispatch(module.exports.setAdminMessages([]));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR));
|
||||
dispatch(module.exports.setMessagesError('No session content'));
|
||||
dispatch(module.exports.setAdminMessages([]));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setAdminMessages(body));
|
||||
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHED));
|
||||
});
|
||||
};
|
||||
};
|
||||
module.exports.getAdminMessages = (username, token) => (dispatch => {
|
||||
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `/users/${username}/messages/admin`,
|
||||
authentication: token
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR));
|
||||
dispatch(module.exports.setMessagesError(err));
|
||||
dispatch(module.exports.setAdminMessages([]));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR));
|
||||
dispatch(module.exports.setMessagesError('No session content'));
|
||||
dispatch(module.exports.setAdminMessages([]));
|
||||
return;
|
||||
}
|
||||
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
|
||||
|
@ -277,20 +259,18 @@ module.exports.getAdminMessages = function (username, token) {
|
|||
* @param {string} token the user's unique token for auth
|
||||
* @return {null} returns nothing
|
||||
*/
|
||||
module.exports.getScratcherInvite = function (username, token) {
|
||||
return function (dispatch) {
|
||||
api({
|
||||
uri: '/users/' + username + '/invites',
|
||||
authentication: token
|
||||
}, function (err, body) {
|
||||
if (err) {
|
||||
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.INVITE_ERROR));
|
||||
dispatch(module.exports.setMessagesError(err));
|
||||
dispatch(module.exports.setScratcherInvite({}));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') return dispatch(module.exports.setMessagesError('No session content'));
|
||||
dispatch(module.exports.setScratcherInvite(body));
|
||||
});
|
||||
};
|
||||
};
|
||||
module.exports.getScratcherInvite = (username, token) => (dispatch => {
|
||||
api({
|
||||
uri: `/users/${username}/invites`,
|
||||
authentication: token
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.INVITE_ERROR));
|
||||
dispatch(module.exports.setMessagesError(err));
|
||||
dispatch(module.exports.setScratcherInvite({}));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') return dispatch(module.exports.setMessagesError('No session content'));
|
||||
dispatch(module.exports.setScratcherInvite(body));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
var keyMirror = require('keymirror');
|
||||
const keyMirror = require('keymirror');
|
||||
|
||||
var Types = keyMirror({
|
||||
const Types = keyMirror({
|
||||
SET_SEARCH_TERM: null
|
||||
});
|
||||
|
||||
module.exports.navigationReducer = function (state, action) {
|
||||
if(typeof state === 'undefined') {
|
||||
module.exports.navigationReducer = (state, action) => {
|
||||
if (typeof state === 'undefined') {
|
||||
state = '';
|
||||
}
|
||||
switch (action.type) {
|
||||
|
@ -16,9 +16,7 @@ module.exports.navigationReducer = function (state, action) {
|
|||
}
|
||||
};
|
||||
|
||||
module.exports.setSearchTerm = function (searchTerm) {
|
||||
return {
|
||||
type: Types.SET_SEARCH_TERM,
|
||||
searchTerm: searchTerm
|
||||
};
|
||||
};
|
||||
module.exports.setSearchTerm = searchTerm => ({
|
||||
type: Types.SET_SEARCH_TERM,
|
||||
searchTerm: searchTerm
|
||||
});
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
var keyMirror = require('keymirror');
|
||||
var jar = require('../lib/jar.js');
|
||||
const keyMirror = require('keymirror');
|
||||
const jar = require('../lib/jar.js');
|
||||
|
||||
var Types = keyMirror({
|
||||
const Types = keyMirror({
|
||||
SET_PERMISSIONS: null,
|
||||
SET_PERMISSIONS_ERROR: null
|
||||
});
|
||||
|
||||
module.exports.permissionsReducer = function (state, action) {
|
||||
module.exports.permissionsReducer = (state, action) => {
|
||||
if (typeof state === 'undefined') {
|
||||
state = '';
|
||||
state = {};
|
||||
}
|
||||
switch (action.type) {
|
||||
case Types.SET_PERMISSIONS:
|
||||
|
@ -20,43 +20,37 @@ module.exports.permissionsReducer = function (state, action) {
|
|||
}
|
||||
};
|
||||
|
||||
module.exports.storePermissions = function (permissions) {
|
||||
module.exports.storePermissions = permissions => {
|
||||
permissions = permissions || {};
|
||||
return function (dispatch) {
|
||||
return dispatch => {
|
||||
jar.set('permissions', permissions, {
|
||||
encode: function (value) {
|
||||
return encodeURIComponent(JSON.stringify(value));
|
||||
}
|
||||
encode: value => (
|
||||
encodeURIComponent(JSON.stringify(value))
|
||||
)
|
||||
});
|
||||
return dispatch(module.exports.setPermissions(permissions));
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.getPermissions = function () {
|
||||
return function (dispatch) {
|
||||
jar.get('permissions', function (err, value) {
|
||||
if (err) return dispatch(module.exports.setPermissionsError(err));
|
||||
module.exports.getPermissions = () => (dispatch => {
|
||||
jar.get('permissions', (err, value) => {
|
||||
if (err) return dispatch(module.exports.setPermissionsError(err));
|
||||
|
||||
try {
|
||||
value = JSON.parse(decodeURIComponent(value)) || {};
|
||||
} catch (e) {
|
||||
value = {};
|
||||
}
|
||||
return dispatch(module.exports.setPermissions(value));
|
||||
});
|
||||
};
|
||||
};
|
||||
try {
|
||||
value = JSON.parse(decodeURIComponent(value)) || {};
|
||||
} catch (e) {
|
||||
value = {};
|
||||
}
|
||||
return dispatch(module.exports.setPermissions(value));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.setPermissions = function (permissions) {
|
||||
return {
|
||||
type: Types.SET_PERMISSIONS,
|
||||
permissions: permissions
|
||||
};
|
||||
};
|
||||
module.exports.setPermissions = permissions => ({
|
||||
type: Types.SET_PERMISSIONS,
|
||||
permissions: permissions
|
||||
});
|
||||
|
||||
module.exports.setPermissionsError = function (error) {
|
||||
return {
|
||||
type: Types.SET_PERMISSIONS_ERROR,
|
||||
error: error
|
||||
};
|
||||
};
|
||||
module.exports.setPermissionsError = error => ({
|
||||
type: Types.SET_PERMISSIONS_ERROR,
|
||||
error: error
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
var combineReducers = require('redux').combineReducers;
|
||||
var defaults = require('lodash.defaults');
|
||||
const combineReducers = require('redux').combineReducers;
|
||||
const defaults = require('lodash.defaults');
|
||||
|
||||
var messageCountReducer = require('./message-count.js').messageCountReducer;
|
||||
var permissionsReducer = require('./permissions.js').permissionsReducer;
|
||||
var sessionReducer = require('./session.js').sessionReducer;
|
||||
const messageCountReducer = require('./message-count.js').messageCountReducer;
|
||||
const permissionsReducer = require('./permissions.js').permissionsReducer;
|
||||
const sessionReducer = require('./session.js').sessionReducer;
|
||||
|
||||
/**
|
||||
* Returns a combined reducer to be used for a page in `render.jsx`.
|
||||
|
@ -11,11 +11,11 @@ var sessionReducer = require('./session.js').sessionReducer;
|
|||
* - and any reducers specific to the page should be passed into
|
||||
* `render()` as an object (which will then be passed to the function
|
||||
* 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.
|
||||
* @return {Object} combined reducer to be used in the redux store
|
||||
* @return {object} combined reducer to be used in the redux store
|
||||
*/
|
||||
module.exports = function (opts) {
|
||||
module.exports = opts => {
|
||||
opts = opts || {};
|
||||
return combineReducers(defaults(opts, {
|
||||
session: sessionReducer,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue