diff --git a/.eslintrc b/.eslintrc index e84581005..6f9c2e96d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,11 +1,15 @@ { "rules": { "curly": [2, "multi-line"], + "eol-last": [2], "indent": [2, 4], - "quotes": [2, "single"], "linebreak-style": [2, "unix"], "max-len": [2, 120, 4], + "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": { diff --git a/.gitignore b/.gitignore index a7bef3917..5751771c3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ npm-* # Build /build + +# Elastic Beanstalk Files +.elasticbeanstalk/* +!.elasticbeanstalk/*.cfg.yml +!.elasticbeanstalk/*.global.yml diff --git a/Makefile b/Makefile index 1e200a439..2f1255b66 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,25 @@ webpack: # ------------------------------------ +watch: + $(WATCH) "make clean && make static" ./static & + $(WEBPACK) -d --watch & + wait + +stop: + pkill -f "node $(WEBPACK) -d --watch" + pkill -f "node $(WATCH) make clean && make static ./static" + +start: + $(NODE) ./server/index.js + +# ------------------------------------ + +nginx_conf: + node server/nginx.js + +# ------------------------------------ + test: @make lint @@ -35,14 +54,4 @@ lint: # ------------------------------------ -watch: - $(WATCH) "make clean && make static" ./static & - $(WEBPACK) -d --watch & - wait - -start: - $(NODE) ./server/index.js - -# ------------------------------------ - -.PHONY: build clean static webpack test lint watch start +.PHONY: build clean static webpack watch stop start nginx_conf test lint diff --git a/package.json b/package.json index 715caa635..f0ad612da 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,13 @@ "name": "www", "version": "1.0.0", "description": "Standalone WWW client for Scratch", - "main": "index.js", "scripts": { "start": "make start", "test": "make test", "watch": "make watch", - "build": "make build" + "stop-watch": "make stop-watch", + "build": "make build", + "prestart": "make build" }, "repository": { "type": "git", @@ -28,16 +29,24 @@ }, "devDependencies": { "autoprefixer-loader": "2.1.0", + "classnames": "2.1.3", "css-loader": "0.17.0", "eslint": "1.3.1", "eslint-plugin-react": "3.3.1", + "file-loader": "0.8.4", "json-loader": "0.5.2", "jsx-loader": "0.13.2", "node-sass": "3.3.2", "react": "0.13.3", + "react-modal": "0.3.0", + "react-onclickoutside": "0.3.1", + "react-slick": "0.7.0", + "routes-to-nginx-conf": "0.0.4", "sass-loader": "2.0.1", + "slick-carousel": "1.5.8", "style-loader": "0.12.3", "tape": "4.2.0", + "url-loader": "0.5.6", "watch": "0.16.0", "webpack": "1.12.0", "xhr": "2.0.4" diff --git a/server/index.js b/server/index.js index b12576a5d..24ef86e42 100644 --- a/server/index.js +++ b/server/index.js @@ -1,6 +1,6 @@ var compression = require('compression'); var express = require('express'); -var path = require('path'); +var _path = require('path'); var handler = require('./handler'); var log = require('./log'); @@ -11,15 +11,15 @@ var app = express(); app.disable('x-powered-by'); app.use(log()); app.use(compression()); -app.use(express.static(path.resolve(__dirname, '../build'), { - lastModified: true, - maxAge: '1y' -})); // Bind routes for (var item in routes) { var route = routes[item]; - app.get(route.pattern, handler(route)); + if ( route.static ) { + app.use( express.static( eval( route.resolve ), route.attributes ) ); + } else { + app.get(route.pattern, handler(route)); + } } // Start listening diff --git a/server/nginx.js b/server/nginx.js new file mode 100644 index 000000000..2ab89a172 --- /dev/null +++ b/server/nginx.js @@ -0,0 +1,5 @@ +var routes = require('./routes.json'); +var nginx_conf = require('routes-to-nginx-conf'); + +nginx_conf.generate_nginx_conf( routes, function ( v ) { process.stdout.write(v); } ); + diff --git a/server/routes.json b/server/routes.json index 1e07b5438..73030da8d 100644 --- a/server/routes.json +++ b/server/routes.json @@ -1,15 +1,24 @@ [ { "pattern": "/", - "view": "splash" + "view": "splash", + "static": false }, { "pattern": "/about", "view": "about", - "title": "About" + "title": "About", + "static": false }, { "pattern": "/components", - "view": "components" + "view": "components", + "static": false + }, + { + "static": true, + "resolve": "_path.resolve(__dirname, '../build')", + "attributes": { "lastModified": true, "maxAge": "1y" }, + "_todo": " TODO: Define a specification for how each entry is used/expected to look like, given the nginx conf generator's needs and stand-alone run-time needs. An outline of this so far: static requires resolve/attributes but could use pattern too. ..." } ] diff --git a/src/components/carousel/carousel.json b/src/components/carousel/carousel.json new file mode 100644 index 000000000..4100b69f3 --- /dev/null +++ b/src/components/carousel/carousel.json @@ -0,0 +1,49 @@ +[ + { + "id": 1, + "type": "project", + "title": "Example Project", + "thumbnailUrl": "http://www.lorempixel.com/144/108/", + "creator": "raimondious", + "href": "/projects/1000/" + }, + { + "id": 2, + "type": "project", + "title": "Example Project", + "thumbnailUrl": "http://www.lorempixel.com/144/108/", + "href": "/projects/1000/" + }, + { + "id": 3, + "type": "project", + "title": "Example Project", + "thumbnailUrl": "http://www.lorempixel.com/144/108/", + "creator": "raimondious", + "href": "/projects/1000/" + }, + { + "id": 4, + "type": "project", + "title": "Example Project", + "thumbnailUrl": "http://www.lorempixel.com/144/108/", + "creator": "raimondious", + "href": "/projects/1000/" + }, + { + "id": 5, + "type": "project", + "title": "Example Project", + "thumbnailUrl": "http://www.lorempixel.com/144/108/", + "creator": "raimondious", + "href": "/projects/1000/" + }, + { + "id": 6, + "type": "project", + "title": "Example Project", + "thumbnailUrl": "http://www.lorempixel.com/144/108/", + "creator": "raimondious", + "href": "/projects/1000/" + } +] diff --git a/src/components/carousel/carousel.jsx b/src/components/carousel/carousel.jsx new file mode 100644 index 000000000..089ad04d7 --- /dev/null +++ b/src/components/carousel/carousel.jsx @@ -0,0 +1,42 @@ +var React = require('react'); +var Slider = require('react-slick'); +var Thumbnail = require('../thumbnail/thumbnail.jsx'); + +require('slick-carousel/slick/slick.scss'); +require('slick-carousel/slick/slick-theme.scss'); +require('./carousel.scss'); + +module.exports = React.createClass({ + propTypes: { + items: React.PropTypes.array + }, + getDefaultProps: function () { + return { + items: require('./carousel.json'), + settings: { + arrows: true, + dots: false, + infinite: false, + lazyLoad: true, + slidesToShow: 5, + slidesToScroll: 5, + variableWidth: true + } + }; + }, + render: function () { + return ( + + {this.props.items.map(function (item) { + return ( + + ); + })} + + ); + } +}); diff --git a/src/components/carousel/carousel.scss b/src/components/carousel/carousel.scss new file mode 100644 index 000000000..0d8c78723 --- /dev/null +++ b/src/components/carousel/carousel.scss @@ -0,0 +1,47 @@ +.carousel { + $icon-size: 40px; + $button-offset: $icon-size + 5px; + $box-content-offset: 20px; + + padding: 0 $button-offset; + + .box-content & { + padding: 0 $button-offset - 20px; + } + + .slick-next, .slick-prev { + width: $icon-size; + height: $icon-size; + margin-top: -$icon-size/2; + + &:before { + font-size: $icon-size; + color: #ddd; + } + + &.slick-disabled:before{ + opacity: 1; + } + } + + .slick-prev { + left: 0; + + .box-content & { + left: -$box-content-offset; + } + } + + .slick-next { + right: 0; + + .box-content & { + right: -$box-content-offset; + } + } + + .slick-slide { + padding-right: 30px; + } + +} diff --git a/src/components/intro/intro.jsx b/src/components/intro/intro.jsx new file mode 100644 index 000000000..01993933d --- /dev/null +++ b/src/components/intro/intro.jsx @@ -0,0 +1,93 @@ +var React = require('react'); +var Modal = require('../modal/modal.jsx'); + +require('./intro.scss'); + +Modal.setAppElement(document.getElementById('view')); + +module.exports = React.createClass({ + propTypes: { + projectCount: React.PropTypes.number + }, + getDefaultProps: function () { + return { + projectCount: 10569070 + }; + }, + getInitialState: function () { + return { + videoOpen: false + }; + }, + showVideo: function () { + this.setState({videoOpen: true}); + }, + closeVideo: function () { + this.setState({videoOpen: false}); + }, + render: function () { + return ( +
+
+

+ Create stories, games, and animations
+ Share with others around the world +

+
+ + + +
+
TRY IT OUT
+
+ + + +
+
SEE EXAMPLES
+
+ + + +
+
JOIN SCRATCH
+
( it’s free )
+
+
+
+ A creative learning community with + {this.props.projectCount.toLocaleString()} + projects shared +
+
+ ABOUT SCRATCH + FOR EDUCATORS + FOR PARENTS +
+
+
+
+ +
+ +