|
@ -46,6 +46,7 @@
|
||||||
"lodash.clone": "3.0.3",
|
"lodash.clone": "3.0.3",
|
||||||
"lodash.defaultsdeep": "3.10.0",
|
"lodash.defaultsdeep": "3.10.0",
|
||||||
"lodash.omit": "3.1.0",
|
"lodash.omit": "3.1.0",
|
||||||
|
"lodash.range": "3.0.1",
|
||||||
"minilog": "2.0.8",
|
"minilog": "2.0.8",
|
||||||
"node-sass": "3.3.3",
|
"node-sass": "3.3.3",
|
||||||
"po2icu": "git://github.com/LLK/po2icu.git#develop",
|
"po2icu": "git://github.com/LLK/po2icu.git#develop",
|
||||||
|
|
|
@ -18,6 +18,9 @@ module.exports = {
|
||||||
// Analytics & Monitoring
|
// Analytics & Monitoring
|
||||||
ga_tracker: process.env.GA_TRACKER || '',
|
ga_tracker: process.env.GA_TRACKER || '',
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
sentry_dsn: process.env.SENTRY_DSN || '',
|
||||||
|
|
||||||
// Use minified JS libraries
|
// Use minified JS libraries
|
||||||
min: (process.env.NODE_ENV === 'production') ? '.min' : ''
|
min: (process.env.NODE_ENV === 'production') ? '.min' : ''
|
||||||
};
|
};
|
||||||
|
|
|
@ -48,10 +48,16 @@
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script src="/js/lib/react{{min}}.js"></script>
|
<script src="/js/lib/react{{min}}.js"></script>
|
||||||
<script src="/js/lib/react-dom{{min}}.js"></script>
|
<script src="/js/lib/react-dom{{min}}.js"></script>
|
||||||
|
<script src="/js/lib/raven.min.js"></script>
|
||||||
|
|
||||||
<script src="/js/main.bundle.js"></script>
|
<script src="/js/main.bundle.js"></script>
|
||||||
<script src="/js/{{view}}.bundle.js"></script>
|
<script src="/js/{{view}}.bundle.js"></script>
|
||||||
|
|
||||||
|
<!-- Error logging (Sentry) -->
|
||||||
|
<script>
|
||||||
|
Raven.config('{{&sentry_dsn}}').install()
|
||||||
|
</script>
|
||||||
|
|
||||||
<!-- Analytics (GA) -->
|
<!-- Analytics (GA) -->
|
||||||
<script>
|
<script>
|
||||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||||
|
|
|
@ -9,6 +9,7 @@ $background-color: hsla(0, 0, 99, 1); //#FDFDFD
|
||||||
|
|
||||||
/* UI Secondary Colors */
|
/* UI Secondary Colors */
|
||||||
$ui-aqua: hsla(170, 70, 50, 1); //#26D9BB
|
$ui-aqua: hsla(170, 70, 50, 1); //#26D9BB
|
||||||
|
$ui-purple: hsla(265, 55, 55, 1); //#824DCB
|
||||||
$ui-white: #fff;
|
$ui-white: #fff;
|
||||||
|
|
||||||
$ui-border: hsla(0, 0, 85, 1); //#D9D9D9
|
$ui-border: hsla(0, 0, 85, 1); //#D9D9D9
|
||||||
|
|
|
@ -34,7 +34,7 @@ var Activity = React.createClass({
|
||||||
title={formatMessage(defaultMessages.whatsHappening)}>
|
title={formatMessage(defaultMessages.whatsHappening)}>
|
||||||
|
|
||||||
{this.props.items && this.props.items.length > 0 ? [
|
{this.props.items && this.props.items.length > 0 ? [
|
||||||
<ul>
|
<ul key="activity-ul">
|
||||||
{this.props.items.map(function (item) {
|
{this.props.items.map(function (item) {
|
||||||
if (item.message.replace(/\s/g, '')) {
|
if (item.message.replace(/\s/g, '')) {
|
||||||
var actorProfileUrl = '/users/' + item.actor.username + '/';
|
var actorProfileUrl = '/users/' + item.actor.username + '/';
|
||||||
|
@ -60,7 +60,7 @@ var Activity = React.createClass({
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
] : [
|
] : [
|
||||||
<div className="empty">
|
<div className="empty" key="activity-empty">
|
||||||
<h4>
|
<h4>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="activity.seeUpdates"
|
id="activity.seeUpdates"
|
||||||
|
|
|
@ -2,8 +2,11 @@ var React = require('react');
|
||||||
var ReactDOM = require('react-dom');
|
var ReactDOM = require('react-dom');
|
||||||
var FormattedMessage = require('react-intl').FormattedMessage;
|
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
|
|
||||||
|
var log = require('../../lib/log.js');
|
||||||
|
|
||||||
var Input = require('../forms/input.jsx');
|
var Input = require('../forms/input.jsx');
|
||||||
var Button = require('../forms/button.jsx');
|
var Button = require('../forms/button.jsx');
|
||||||
|
var Spinner = require('../spinner/spinner.jsx');
|
||||||
|
|
||||||
require('./login.scss');
|
require('./login.scss');
|
||||||
|
|
||||||
|
@ -13,12 +16,21 @@ var Login = React.createClass({
|
||||||
onLogIn: React.PropTypes.func,
|
onLogIn: React.PropTypes.func,
|
||||||
error: React.PropTypes.string
|
error: React.PropTypes.string
|
||||||
},
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
waiting: false
|
||||||
|
};
|
||||||
|
},
|
||||||
handleSubmit: function (event) {
|
handleSubmit: function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
this.setState({waiting: true});
|
||||||
this.props.onLogIn({
|
this.props.onLogIn({
|
||||||
'username': ReactDOM.findDOMNode(this.refs.username).value,
|
'username': ReactDOM.findDOMNode(this.refs.username).value,
|
||||||
'password': ReactDOM.findDOMNode(this.refs.password).value
|
'password': ReactDOM.findDOMNode(this.refs.password).value
|
||||||
});
|
}, function (err) {
|
||||||
|
if (err) log.error(err);
|
||||||
|
this.setState({waiting: false});
|
||||||
|
}.bind(this));
|
||||||
},
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
var error;
|
var error;
|
||||||
|
@ -40,11 +52,17 @@ var Login = React.createClass({
|
||||||
defaultMessage={'Password'} />
|
defaultMessage={'Password'} />
|
||||||
</label>
|
</label>
|
||||||
<Input type="password" ref="password" name="password" />
|
<Input type="password" ref="password" name="password" />
|
||||||
<Button className="submit-button white" type="submit">
|
{this.state.waiting ? [
|
||||||
<FormattedMessage
|
<Button className="submit-button white" type="submit" disabled="disabled">
|
||||||
id='general.signIn'
|
<Spinner />
|
||||||
defaultMessage={'Sign in'} />
|
</Button>
|
||||||
</Button>
|
] : [
|
||||||
|
<Button className="submit-button white" type="submit">
|
||||||
|
<FormattedMessage
|
||||||
|
id='general.signIn'
|
||||||
|
defaultMessage={'Sign in'} />
|
||||||
|
</Button>
|
||||||
|
]}
|
||||||
<a className="right" href="/accounts/password_reset/">
|
<a className="right" href="/accounts/password_reset/">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='login.forgotPassword'
|
id='login.forgotPassword'
|
||||||
|
|
|
@ -8,6 +8,12 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
margin: 0 .8rem;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.submit-button {
|
.submit-button {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ var Navigation = React.createClass({
|
||||||
closeLogin: function () {
|
closeLogin: function () {
|
||||||
this.setState({'loginOpen': false});
|
this.setState({'loginOpen': false});
|
||||||
},
|
},
|
||||||
handleLogIn: function (formData) {
|
handleLogIn: function (formData, callback) {
|
||||||
this.setState({'loginError': null});
|
this.setState({'loginError': null});
|
||||||
formData['useMessages'] = true;
|
formData['useMessages'] = true;
|
||||||
this.api({
|
this.api({
|
||||||
|
@ -126,12 +126,14 @@ var Navigation = React.createClass({
|
||||||
json: formData,
|
json: formData,
|
||||||
useCsrf: true
|
useCsrf: true
|
||||||
}, function (err, body) {
|
}, function (err, body) {
|
||||||
|
if (err) this.setState({'loginError': err.message});
|
||||||
if (body) {
|
if (body) {
|
||||||
body = body[0];
|
body = body[0];
|
||||||
if (!body.success) {
|
if (!body.success) {
|
||||||
if (body.redirect) {
|
if (body.redirect) {
|
||||||
window.location = body.redirect;
|
window.location = body.redirect;
|
||||||
}
|
}
|
||||||
|
// Update login error message to a friendlier one if it exists
|
||||||
this.setState({'loginError': body.msg});
|
this.setState({'loginError': body.msg});
|
||||||
} else {
|
} else {
|
||||||
this.closeLogin();
|
this.closeLogin();
|
||||||
|
@ -143,6 +145,8 @@ var Navigation = React.createClass({
|
||||||
window.refreshSession();
|
window.refreshSession();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// JS error already logged by api mixin
|
||||||
|
callback();
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
handleLogOut: function (e) {
|
handleLogOut: function (e) {
|
||||||
|
|
20
src/components/spinner/spinner.jsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
var range = require('lodash.range');
|
||||||
|
var 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Spinner;
|
47
src/components/spinner/spinner.scss
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
@import "../../colors";
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
position: relative;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
display: block;
|
||||||
|
animation: circleFadeDelay 1.2s infinite ease-in-out both;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 100%;
|
||||||
|
background-color: darken($ui-blue, 8%);
|
||||||
|
width: 15%;
|
||||||
|
height: 15%;
|
||||||
|
content: "";
|
||||||
|
-webkit-animation: circleFadeDelay 1.2s infinite ease-in-out both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@for $i from 1 through 12 {
|
||||||
|
$rotation: 30deg * ($i - 1);
|
||||||
|
$delay: -1.3s + $i * .1;
|
||||||
|
.circle#{$i} {
|
||||||
|
transform: rotate($rotation);
|
||||||
|
-ms-transform: rotate($rotation);
|
||||||
|
-webkit-transform: rotate($rotation);
|
||||||
|
}
|
||||||
|
.circle#{$i}:before {
|
||||||
|
animation-delay: $delay;
|
||||||
|
-webkit-animation-delay: $delay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes circleFadeDelay {
|
||||||
|
0%, 39%, 100% { opacity: 0; }
|
||||||
|
40% { opacity: 1; }
|
||||||
|
}
|
|
@ -26,6 +26,12 @@ var Api = {
|
||||||
opts.uri = opts.host + opts.uri;
|
opts.uri = opts.host + opts.uri;
|
||||||
|
|
||||||
var apiRequest = function (opts) {
|
var apiRequest = function (opts) {
|
||||||
|
if (opts.host !== '') {
|
||||||
|
// For IE < 10, we must use XDR for cross-domain requests. XDR does not support
|
||||||
|
// custom headers.
|
||||||
|
defaults(opts, {useXDR: true});
|
||||||
|
delete opts.headers;
|
||||||
|
}
|
||||||
xhr(opts, function (err, res, body) {
|
xhr(opts, function (err, res, body) {
|
||||||
if (err) log.error(err);
|
if (err) log.error(err);
|
||||||
callback(err, body);
|
callback(err, body);
|
||||||
|
|
|
@ -6,6 +6,7 @@ var Box = require('../../components/box/box.jsx');
|
||||||
var Button = require('../../components/forms/button.jsx');
|
var Button = require('../../components/forms/button.jsx');
|
||||||
var Carousel = require('../../components/carousel/carousel.jsx');
|
var Carousel = require('../../components/carousel/carousel.jsx');
|
||||||
var Input = require('../../components/forms/input.jsx');
|
var Input = require('../../components/forms/input.jsx');
|
||||||
|
var Spinner = require('../../components/spinner/spinner.jsx');
|
||||||
|
|
||||||
|
|
||||||
require('./components.scss');
|
require('./components.scss');
|
||||||
|
@ -37,6 +38,8 @@ var Components = React.createClass({
|
||||||
<Activity />
|
<Activity />
|
||||||
<h1>{'Nothing!!!'}</h1>
|
<h1>{'Nothing!!!'}</h1>
|
||||||
<Activity items={[]} />
|
<Activity items={[]} />
|
||||||
|
<h1>This is a Spinner</h1>
|
||||||
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,7 @@ var Hoc = React.createClass({
|
||||||
|
|
||||||
<div className="inner">
|
<div className="inner">
|
||||||
<Box title={''}>
|
<Box title={''}>
|
||||||
<section className="one-up">
|
<section id="teacher" className="one-up">
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<h3>
|
<h3>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -272,13 +272,13 @@ var Hoc = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<a href="/projects/editor/?tip_bar=pong">
|
<a href="/hoops">
|
||||||
<div className="card-info">
|
<div className="card-info">
|
||||||
<img src="/images/hoc2015/pong-tutorial.jpg" />
|
<img src="/images/hoc2015/bball-tutorial.jpg" />
|
||||||
<Button>
|
<Button>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='general.tipsPongGame'
|
id='general.tipsBBallHoops'
|
||||||
defaultMessage={'Create a Pong Game'} />
|
defaultMessage={'B-Ball Hoops'} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
BIN
static/images/hoc2015/bball-tutorial.jpg
Normal file
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 94 KiB |