|
@ -16,6 +16,9 @@
|
|||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"globals": {
|
||||
"formatMessage": true
|
||||
},
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
|
|
3
.gitignore
vendored
|
@ -8,6 +8,9 @@ npm-*
|
|||
# Build
|
||||
/build
|
||||
|
||||
# Locales
|
||||
/locales
|
||||
|
||||
# Elastic Beanstalk Files
|
||||
.elasticbeanstalk/*
|
||||
!.elasticbeanstalk/*.cfg.yml
|
||||
|
|
24
.sass-lint.yml
Normal file
|
@ -0,0 +1,24 @@
|
|||
rules:
|
||||
color-literals: 2
|
||||
final-newline: 2
|
||||
hex-notation: 2
|
||||
indentation:
|
||||
- 2
|
||||
-
|
||||
size: 4
|
||||
leading-zero: 2
|
||||
nesting-depth:
|
||||
- 1
|
||||
-
|
||||
max-depth: 4
|
||||
no-css-comments: 0
|
||||
no-ids: 0
|
||||
property-sort-order:
|
||||
- 2
|
||||
-
|
||||
order: concentric
|
||||
quotes:
|
||||
- 2
|
||||
-
|
||||
style: double
|
||||
zero-unit: 2
|
38
.travis.yml
|
@ -7,3 +7,41 @@ cache:
|
|||
notifications:
|
||||
slack:
|
||||
secure: ezESiG7JnuSLZc2/PPhOvWUv5BHBCr+g86MsuLLw+S+zz3DUfzWHMQ1g5tUvkeSDTPmfEIX41EnPkaoWtsD3OGO0PGXgseAfA8+6Z4N1rICNZZrhXZB2s6UdwRK1e+0Jol4W3kHmt96BHyN2scLNgJYeWMgSJllVsuPhMTlKBZIXI9u540NH8Nxjl3f2WvoIg64Q1mZvMxkpPbw4xssx6U4HSFE8kTTE6+EFsSxzombFX0cLGjPiJ9QZgGVUk4UkIjyiFLQQDfQlLllCaUpqJ9+qbuCNoMSKA2yty/qyZ8Y+r4OlMberjmBzR9GRLLyXWWcaAfMIgwlRhjtLYIDAUSsGM1iwUWCgyB9maG2IiXuYLSueuMx8DcDwbpUepoDgnqBYnM2AJmT8gcsxqlKYzJpYpHDgZgBlLZQgMXqjrVJHs/Tf9XVcLS6HAn1Ww0OOT01jThfy4gClpAuqLayYexsXOoL+RaFg25E2NzuTtaFWgRfWZgcAeqYNDiUzwun2D4vZ5I+NtdRP0gzpbG2fxhFz05vAqyf1Kp6ZYb17Li3A38dIm6Lsvv3qawAIAgNaZpIZX3f89+uq6jHU8kJy1Iv823JK2Xac3vEz3SHUKJnuXFF0LO07om9AcNEXhP/JrJ617S8nfvDtZRJODMFhz8qQwie+65Ql1I871goBpVs=
|
||||
env:
|
||||
global:
|
||||
- secure: kXRyOECCfmTmIyibSKyHFz9cC5YGDsLIZJyiSpepvjRvuuJErxpD3yokp++JCJXdj/CRfJKazeMPkgek78zGiI/xnHR2aVxuQraU5ELIVNBCaFDtM4lKxtTVvEAtErwhWrH9zMiJkgXGF/MwID0QgZVlD/hpKI3MoS8sS1dmvDlqlregTvUZWBnlqMnrQuOXFNLPT+/QPgO6myd+nJn+XogSw8HceUo76cOADBphLtxFvE+R3FEbkHOwgJzUR3p8FstNXjmXZocSAYlGEgf1QIAN7M+3fH3wBHUBL2XELlr3w6eFr0qPCT5GCIxc4DNYsNt1360nmhSUqcm+k30HcbGmM5oWiRTmo2NrNpKhCUyF9wKHKmS4JYqGBEBjLxkTZe/zCv18gNVy0s9x/IXP3qP9SoRnlNEt9H6MjaxBc3lWD47UmcDJoLrs7OUdM4HDxgmPJyTzJsg059GEWgHuEMGIGGCYBGdpNlu4ZH6yEgsji73+kAkYbnVzhz4QtfhGNgQv3kEhTmDHW5muca5EMuSyOLW5v4ffpLJgirJQi9lvjZ/pZ+XJU0DSfIHdViqop6hRrsPxo2ewle3RcZrlQuw7lJJp9IoDT5Ku2PU1m8+705CR8S96DrMP8UtbC1Plcv91MMGmwgPwYAQwEcTnj7Fsq9QKReus+CTUXYqaMQM=
|
||||
- secure: D7TVtzhDPvSsipXB9jiokA00rUAENWjK5Lrv+JOgdNe35D9j3tSSgT+iKj2Et5LcSeKXpvC2gMDXakHMflo2tT8uYPx7NI6J9XZMro2VP+ebTHlG57Fuoves9XxwCvHDFk2yW/K7uma8a92rs4PNydJRB6SPm3pWjL29Ih2n9ZFy37ZHCdL26R61EJ9SZh5siOVuXhqB36mu0Z9ANjXeXcLrKzpRf8mmORsK9NT/0A9kg4a0Q9ZKiHhUp3Wh3VKfDlDvqYszdofBNSpUGSyj/J4IlpYld8q+o+husxr3yLbV+FR1xdJ8NS04iXEmd1yOhJKy7ienpNQJ7NLwSOgDQ+Y8VZJUf0ZvSX/acHqNFQC86tW15KTAEVfnY8Js7mqmZrsWoY6+jzC8RyaQoZiD1HQfJLHkG+uqrfPYhWy1BNz+4QtBwnnQO+E/B2CM+fGAmjoJ+UjquWQo+sHWwoatNrG85JumA3GsA1FSlkzEVy3AAcST/CFZ1IyGCDVTar++2VwYCH691DuJy1gyeqSukSbRQIhGTSktArv0FjIiVsoMTCB/Ntg8HcfL6ADTfsijZVL9v5hN2VUXg3BjuF4TEBsrN78WMNI+U3g1+W1UW+036eP09Z7QDxIvLoQdIaQncGBny2KnR2j/Gmgz9eG0eg4dlV+2W+9DqE4y+tmU4Jw=
|
||||
- EB_REGION=us-east-1
|
||||
- EB_APP=scratch-www
|
||||
- EB_AWS_BUCKET_NAME=elasticbeanstalk-us-east-1-307680192167
|
||||
- SKIP_CLEANUP=true
|
||||
- BUILD_ARCHIVE=$TRAVIS_BUILD_ID.zip
|
||||
before_deploy:
|
||||
- zip -r $BUILD_ARCHIVE .
|
||||
deploy:
|
||||
- provider: elasticbeanstalk
|
||||
access_key_id: $EB_AWS_ACCESS_KEY_ID
|
||||
secret_access_key: $EB_AWS_SECRET_ACCESS_KEY
|
||||
bucket_name: $EB_AWS_BUCKET_NAME
|
||||
bucket_path: $EB_APP
|
||||
zip_file: $BUILD_ARCHIVE
|
||||
skip_cleanup: $SKIP_CLEANUP
|
||||
region: $EB_REGION
|
||||
app: $EB_APP
|
||||
env: scratch-www-staging
|
||||
on:
|
||||
repo: LLK/scratch-www
|
||||
branch: develop
|
||||
- provider: elasticbeanstalk
|
||||
access_key_id: $EB_AWS_ACCESS_KEY_ID
|
||||
secret_access_key: $EB_AWS_SECRET_ACCESS_KEY
|
||||
bucket_name: $EB_AWS_BUCKET_NAME
|
||||
bucket_path: $EB_APP
|
||||
zip_file: $BUILD_ARCHIVE
|
||||
skip_cleanup: $SKIP_CLEANUP
|
||||
region: $EB_REGION
|
||||
app: $EB_APP
|
||||
env: scratch-www-production
|
||||
on:
|
||||
repo: LLK/scratch-www
|
||||
branch: master
|
||||
|
|
25
Makefile
|
@ -1,5 +1,6 @@
|
|||
ESLINT=./node_modules/.bin/eslint
|
||||
NODE=node
|
||||
SASSLINT=./node_modules/.bin/sass-lint -v
|
||||
WATCH=./node_modules/.bin/watch
|
||||
WEBPACK=./node_modules/.bin/webpack
|
||||
|
||||
|
@ -8,17 +9,22 @@ WEBPACK=./node_modules/.bin/webpack
|
|||
build:
|
||||
@make clean
|
||||
@make static
|
||||
@make translations
|
||||
@make webpack
|
||||
|
||||
clean:
|
||||
rm -rf ./build
|
||||
mkdir -p build
|
||||
mkdir -p locales
|
||||
|
||||
static:
|
||||
cp -a ./static/. ./build/
|
||||
|
||||
translations:
|
||||
./src/scripts/build-locales locales/translations.json
|
||||
|
||||
webpack:
|
||||
$(WEBPACK)
|
||||
$(WEBPACK) --bail
|
||||
|
||||
# ------------------------------------
|
||||
|
||||
|
@ -28,30 +34,31 @@ watch:
|
|||
wait
|
||||
|
||||
stop:
|
||||
pkill -f "node $(WEBPACK) -d --watch"
|
||||
pkill -f "node $(WATCH) make clean && make static ./static"
|
||||
-pkill -f "$(WEBPACK) -d --watch"
|
||||
-pkill -f "$(WATCH) make clean && make static ./static"
|
||||
-pkill -f "$(NODE) ./server/index.js"
|
||||
|
||||
start:
|
||||
$(NODE) ./server/index.js
|
||||
|
||||
# ------------------------------------
|
||||
|
||||
nginx_conf:
|
||||
node server/nginx.js
|
||||
|
||||
# ------------------------------------
|
||||
|
||||
test:
|
||||
@make lint
|
||||
@make build
|
||||
|
||||
lint:
|
||||
$(ESLINT) ./*.js
|
||||
$(ESLINT) ./server/*.js
|
||||
$(ESLINT) ./src/*.js
|
||||
$(ESLINT) ./src/*.jsx
|
||||
$(ESLINT) ./src/mixins/*.jsx
|
||||
$(ESLINT) ./src/views/**/*.jsx
|
||||
$(ESLINT) ./src/components/**/*.jsx
|
||||
$(SASSLINT) ./src/*.scss
|
||||
$(SASSLINT) ./src/views/**/*.scss
|
||||
$(SASSLINT) ./src/components/**/*.scss
|
||||
|
||||
# ------------------------------------
|
||||
|
||||
.PHONY: build clean static webpack watch stop start nginx_conf test lint
|
||||
.PHONY: build clean static translations webpack watch stop start test lint
|
||||
|
|
24
README.md
|
@ -16,7 +16,29 @@ During development, you can use `npm run watch` to cause any update you make to
|
|||
npm start
|
||||
```
|
||||
|
||||
Once running, open `http://localhost:8888` in your browser. If you wish to have the server reload automatically, you can install either [nodemon](https://github.com/remy/nodemon) or [forever](https://github.com/foreverjs/forever).
|
||||
or to start and watch at once
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Once running, open `http://localhost:8333` in your browser. If you wish to have the server reload automatically, you can install either [nodemon](https://github.com/remy/nodemon) or [forever](https://github.com/foreverjs/forever).
|
||||
|
||||
### To stop
|
||||
```bash
|
||||
# Stops all `start` and `watch` processes
|
||||
npm stop
|
||||
```
|
||||
|
||||
#### Configuration
|
||||
|
||||
`npm start` and `npm run watch` can be configured with the following environment variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
| ------------- | ------------------------------------- | ---------------------------------------------- |
|
||||
| `API_HOST` | `https://api-staging.scratch.mit.edu` | Hostname for API requests |
|
||||
| `NODE_ENV` | `null` | If not `production`, app acts like development |
|
||||
| `PORT` | `8333` | Port for devserver (http://localhost:XXXX) |
|
||||
| `PROXY_HOST` | `https://staging.scratch.mit.edu` | Pass-through location for scratchr2 |
|
||||
|
||||
### To Test
|
||||
```bash
|
||||
|
|
69
en.json
Normal file
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"general.accountSettings": "Account settings",
|
||||
"general.about": "About",
|
||||
"general.donate": "Donate",
|
||||
"general.community": "Community",
|
||||
"general.contactUs": "Contact Us",
|
||||
"general.copyright": "Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab",
|
||||
"general.create": "Create",
|
||||
"general.credits": "Credits",
|
||||
"general.discuss": "Discuss",
|
||||
"general.dmca": "DMCA",
|
||||
"general.explore": "Explore",
|
||||
"general.faq": "FAQ",
|
||||
"general.forParents": "For Parents",
|
||||
"general.forEducators": "For Educators",
|
||||
"general.guidelines": "Community Guidelines",
|
||||
"general.jobs": "Jobs",
|
||||
"general.joinScratch": "Join Scratch",
|
||||
"general.legal": "Legal",
|
||||
"general.learnMore": "Learn More",
|
||||
"general.messages": "Messages",
|
||||
"general.myStuff": "My Stuff",
|
||||
"general.offlineEditor": "Offline Editor",
|
||||
"general.profile": "Profile",
|
||||
"general.scratchConference": "Scratch Conference",
|
||||
"general.scratchday": "Scratch Day",
|
||||
"general.scratchEd": "ScratchEd",
|
||||
"general.scratchFoundation": "Scratch Foundation",
|
||||
"general.scratchJr": "ScratchJr",
|
||||
"general.signIn": "Sign in",
|
||||
"general.statistics": "Statistics",
|
||||
"general.support": "Support",
|
||||
"general.termsOfUse": "Terms of Use",
|
||||
"general.username": "Username",
|
||||
"general.whatsHappening": "What's Happening?",
|
||||
"general.wiki": "Scratch Wiki",
|
||||
"footer.about": "About Scratch",
|
||||
"footer.discuss": "Discussion Forums",
|
||||
"footer.help": "Help Page",
|
||||
"footer.scratchFamily": "Scratch Family",
|
||||
"intro.aboutScratch": "ABOUT SCRATCH",
|
||||
"intro.forEducators": "FOR EDUCATORS",
|
||||
"infro.forParents": "FOR PARENTS",
|
||||
"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",
|
||||
"login.forgotPassword": "Forgot Password?",
|
||||
"navigation.signOut": "Sign out",
|
||||
"parents.FaqAgeRangeA": "While Scratch is primarily designed for 8 to 16 year olds, it is also used by people of all ages, including younger children with their parents.",
|
||||
"parents.FaqAgeRangeQ": "What is the age range for Scratch?",
|
||||
"parents.FaqResourcesQ": "What resources are available for learning Scratch?",
|
||||
"parents.introDescription": "Scratch is a programming language and an online community where children can program and share interactive media such as stories, games, and animation with people from all over the world. As children create with Scratch, they learn to think creatively, work collaboratively, and reason systematically. Scratch is designed and maintained by the Lifelong Kindergarten group at the MIT Media Lab.",
|
||||
"splash.featuredProjects": "Featured Projects",
|
||||
"splash.featuredStudios": "Featured Studios",
|
||||
"splash.projectsCuratedBy": "Projects Curated by",
|
||||
"splash.scratchDesignStudioTitle": "Scratch Design Studio",
|
||||
"splash.visitTheStudio": "Visit the studio",
|
||||
"splash.recentlySharedProjects": "Recently Shared Projects",
|
||||
"splash.projectsByScratchersFollowing": "Projects by Scratchers I'm Following",
|
||||
"splash.projectsLovedByScratchersFollowing": "Projects Loved by Scratchers I'm Following",
|
||||
"splash.projectsInStudiosFollowing": "Projects in Studios I'm Following",
|
||||
"splash.communityRemixing": "What the Community is Remixing",
|
||||
"splash.communityLoving": "What the Community is Loving",
|
||||
"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"
|
||||
}
|
72
languages.json
Normal file
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"en": "English",
|
||||
"an": "Aragonés",
|
||||
"ast": "Asturianu",
|
||||
"id": "Bahasa Indonesia",
|
||||
"ms": "Bahasa Melayu",
|
||||
"ca": "Català",
|
||||
"cs": "Česky",
|
||||
"cy": "Cymraeg",
|
||||
"da": "Dansk",
|
||||
"fa-af": "Dari",
|
||||
"de": "Deutsch",
|
||||
"yum": "Edible Scratch",
|
||||
"et": "Eesti",
|
||||
"eo": "Esperanto",
|
||||
"es": "Español",
|
||||
"eu": "Euskara",
|
||||
"fr": "Français",
|
||||
"fr-ca": "Français (Canada)",
|
||||
"ga": "Gaeilge",
|
||||
"gd": "Gàidhlig",
|
||||
"gl": "Galego",
|
||||
"hr": "Hrvatski",
|
||||
"is": "Íslenska",
|
||||
"it": "Italiano",
|
||||
"rw": "Kinyarwanda",
|
||||
"ku": "Kurdî",
|
||||
"la": "Latina",
|
||||
"lv": "Latviešu",
|
||||
"lt": "Lietuvių",
|
||||
"hu": "Magyar",
|
||||
"mt": "Malti",
|
||||
"cat": "Meow",
|
||||
"nl": "Nederlands",
|
||||
"nb": "Norsk Bokmål",
|
||||
"pl": "Polski",
|
||||
"pt": "Português",
|
||||
"pt-br": "Português Brasileiro",
|
||||
"ro": "Română",
|
||||
"sc": "Sardu",
|
||||
"sk": "Slovenčina",
|
||||
"sl": "Slovenščina",
|
||||
"fi": "suomi",
|
||||
"sv": "Svenska",
|
||||
"nai": "Tepehuan",
|
||||
"vi": "Tiếng Việt",
|
||||
"tr": "Türkçe",
|
||||
"ab": "Аҧсшәа",
|
||||
"ar": "العربية",
|
||||
"bg": "Български",
|
||||
"el": "Ελληνικά",
|
||||
"fa": "فارسی",
|
||||
"he": "עִבְרִית",
|
||||
"hi": "हिन्दी",
|
||||
"hy": "Հայերեն",
|
||||
"ja": "日本語",
|
||||
"ja-hr": "にほんご",
|
||||
"km": "សំលៀកបំពាក",
|
||||
"kn": "ಭಾಷೆ-ಹೆಸರು",
|
||||
"ko": "한국어",
|
||||
"mk": "Македонски",
|
||||
"ml": "മലയാളം",
|
||||
"mn": "Монгол хэл",
|
||||
"mr": "मराठी",
|
||||
"my": "မြန်မာဘာသာ",
|
||||
"ru": "Русский",
|
||||
"sr": "Српски",
|
||||
"th": "ไทย",
|
||||
"uk": "Українська",
|
||||
"zh-cn": "简体中文",
|
||||
"zh-tw": "正體中文"
|
||||
}
|
37
package.json
|
@ -4,11 +4,11 @@
|
|||
"description": "Standalone WWW client for Scratch",
|
||||
"scripts": {
|
||||
"start": "make start",
|
||||
"stop": "make stop",
|
||||
"test": "make test",
|
||||
"watch": "make watch",
|
||||
"stop-watch": "make stop-watch",
|
||||
"build": "make build",
|
||||
"prestart": "make build"
|
||||
"dev": "make watch && make start &"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -21,34 +21,53 @@
|
|||
},
|
||||
"homepage": "https://github.com/llk/scratch-www#readme",
|
||||
"dependencies": {
|
||||
"bunyan": "1.4.0",
|
||||
"bunyan": "1.5.1",
|
||||
"compression": "1.5.2",
|
||||
"express": "4.13.3",
|
||||
"express-http-proxy": "0.6.0",
|
||||
"lodash.defaults": "3.1.2",
|
||||
"mustache": "2.1.3"
|
||||
"mustache": "2.1.3",
|
||||
"newrelic": "1.22.1",
|
||||
"raven": "0.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer-loader": "2.1.0",
|
||||
"classnames": "2.1.3",
|
||||
"cookie": "0.2.2",
|
||||
"css-loader": "0.17.0",
|
||||
"eslint": "1.3.1",
|
||||
"eslint-plugin-react": "3.3.1",
|
||||
"exenv": "1.2.0",
|
||||
"file-loader": "0.8.4",
|
||||
"glob": "5.0.15",
|
||||
"json-loader": "0.5.2",
|
||||
"json2po-stream": "1.0.3",
|
||||
"jsx-loader": "0.13.2",
|
||||
"node-sass": "3.3.2",
|
||||
"react": "0.13.3",
|
||||
"react-modal": "0.3.0",
|
||||
"lodash.clone": "3.0.3",
|
||||
"lodash.defaultsdeep": "3.10.0",
|
||||
"lodash.omit": "3.1.0",
|
||||
"minilog": "2.0.8",
|
||||
"node-sass": "3.3.3",
|
||||
"po2icu": "git://github.com/LLK/po2icu.git#develop",
|
||||
"react": "0.14.0",
|
||||
"react-addons-test-utils": "0.14.0",
|
||||
"react-dom": "0.14.0",
|
||||
"react-intl": "2.0.0-pr-3",
|
||||
"react-modal": "git://github.com/mewtaylor/react-modal.git#react-14",
|
||||
"react-onclickoutside": "0.3.1",
|
||||
"react-slick": "0.7.0",
|
||||
"react-slick": "git://github.com/mewtaylor/react-slick.git#remove-deprecation-warnings",
|
||||
"routes-to-nginx-conf": "0.0.4",
|
||||
"sass-lint": "1.2.0",
|
||||
"sass-loader": "2.0.1",
|
||||
"scratchr2_translations": "git://github.com/mewtaylor/scratchr2_translations.git#feature/packageify",
|
||||
"slick-carousel": "1.5.8",
|
||||
"source-map-support": "0.3.2",
|
||||
"style-loader": "0.12.3",
|
||||
"tap": "2.0.0",
|
||||
"tape": "4.2.0",
|
||||
"url-loader": "0.5.6",
|
||||
"watch": "0.16.0",
|
||||
"webpack": "1.12.0",
|
||||
"webpack": "1.12.2",
|
||||
"xhr": "2.0.4"
|
||||
}
|
||||
}
|
||||
|
|
17
server/config.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
module.exports = {
|
||||
// Search and metadata
|
||||
title: 'Imagine, Program, Share',
|
||||
description:
|
||||
'Scratch is a free programming language and online community ' +
|
||||
'where you can create your own interactive stories, games, ' +
|
||||
'and animations.',
|
||||
|
||||
// Open graph
|
||||
og_image: 'https://scratch.mit.edu/images/og_image.jpg',
|
||||
og_image_type: 'image/jpeg',
|
||||
og_image_width: 986,
|
||||
og_image_height: 860,
|
||||
|
||||
// Analytics & Monitoring
|
||||
ga_tracker: process.env.GA_TRACKER || ''
|
||||
};
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"title": "Scratch - Imagine, Program, Share",
|
||||
"description": "Scratch is a free programming language and online community where you can create your own interactive stories, games, and animations."
|
||||
}
|
|
@ -4,12 +4,14 @@ var fs = require('fs');
|
|||
var mustache = require('mustache');
|
||||
var path = require('path');
|
||||
|
||||
var config = require('./config');
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
function Handler (route) {
|
||||
// Route definition
|
||||
defaults(route, require('./defaults.json'));
|
||||
defaults(route, config);
|
||||
|
||||
// Render template
|
||||
var location = path.resolve(__dirname, './template.html');
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
if (typeof process.env.NEW_RELIC_LICENSE_KEY === 'string') {
|
||||
require('newrelic');
|
||||
}
|
||||
|
||||
var compression = require('compression');
|
||||
var express = require('express');
|
||||
var _path = require('path');
|
||||
var path = require('path');
|
||||
var proxy = require('express-http-proxy');
|
||||
var url = require('url');
|
||||
|
||||
var handler = require('./handler');
|
||||
var log = require('./log');
|
||||
var proxies = require('./proxies.json');
|
||||
var routes = require('./routes.json');
|
||||
|
||||
// Server setup
|
||||
|
@ -11,19 +18,61 @@ 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'
|
||||
}));
|
||||
app.use(function (req, res, next) {
|
||||
req._path = url.parse(req.url).path;
|
||||
next();
|
||||
});
|
||||
|
||||
// Bind routes
|
||||
for (var item in routes) {
|
||||
var route = routes[item];
|
||||
if ( route.static ) {
|
||||
app.use( express.static( eval( route.resolve ), route.attributes ) );
|
||||
} else {
|
||||
app.get(route.pattern, handler(route));
|
||||
}
|
||||
for (var routeId in routes) {
|
||||
var route = routes[routeId];
|
||||
app.get(route.pattern, handler(route));
|
||||
}
|
||||
|
||||
if (typeof process.env.SENTRY_DSN === 'string') {
|
||||
var raven = require('raven');
|
||||
app.get('/sentrythrow', function mainHandler () { throw new Error('Sentry Test'); });
|
||||
|
||||
// These handlers must be applied _AFTER_ other routes have been applied
|
||||
app.use(raven.middleware.express.requestHandler(process.env.SENTRY_DSN));
|
||||
app.use(raven.middleware.express.errorHandler(process.env.SENTRY_DSN));
|
||||
app.use(function errorHandler (err, req, res, next) {
|
||||
res.append('X-Sentry-ID:' + res.sentry);
|
||||
res.status(500);
|
||||
next(err);
|
||||
});
|
||||
|
||||
raven.patchGlobal(process.env.SENTRY_DSN, function () {
|
||||
process.exit(-1);
|
||||
});
|
||||
}
|
||||
|
||||
// Bind proxies in development
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
var proxyHost = process.env.PROXY_HOST || 'https://staging.scratch.mit.edu';
|
||||
|
||||
app.use('/', proxy(proxyHost, {
|
||||
filter: function (req) {
|
||||
for (var i in proxies) {
|
||||
if (req._path.indexOf(proxies[i]) === 0) return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
forwardPath: function (req) {
|
||||
return req._path;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Start listening
|
||||
var port = process.env.PORT || 8888;
|
||||
var port = process.env.PORT || 8333;
|
||||
app.listen(port, function () {
|
||||
process.stdout.write('Server listening on port ' + port + '\n');
|
||||
if (proxyHost) {
|
||||
process.stdout.write('Proxy host: ' + proxyHost + '\n');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
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); } );
|
||||
|
12
server/proxies.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
[
|
||||
"/accounts/",
|
||||
"/csrf_token/",
|
||||
"/fragment/",
|
||||
"/get_image/",
|
||||
"/i18n/setlang/",
|
||||
"/login_retry/",
|
||||
"/media/",
|
||||
"/session/",
|
||||
"/site-api",
|
||||
"/static/"
|
||||
]
|
|
@ -2,23 +2,21 @@
|
|||
{
|
||||
"pattern": "/",
|
||||
"view": "splash",
|
||||
"static": false
|
||||
"title": "Imagine, Program, Share"
|
||||
},
|
||||
{
|
||||
"pattern": "/about",
|
||||
"view": "about",
|
||||
"title": "About",
|
||||
"static": false
|
||||
"title": "About"
|
||||
},
|
||||
{
|
||||
"pattern": "/components",
|
||||
"view": "components",
|
||||
"static": false
|
||||
"title": "Components"
|
||||
},
|
||||
{
|
||||
"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. ..."
|
||||
"pattern": "/hoc",
|
||||
"view": "hoc",
|
||||
"title": "Hour of Code"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -6,23 +6,33 @@
|
|||
<meta charset="UTF-8" />
|
||||
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<meta http-equiv="x-frame-options" content="deny">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>Scratch - {{title}}</title>
|
||||
<meta name="description" content="{{description}}" />
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:type" content="website" />
|
||||
<!-- Search & Open Graph-->
|
||||
<meta name="description" content="{{description}}" />
|
||||
<meta name="google-site-verification" content="m_3TAXDreGTFyoYnEmU9mcKB4Xtw5mw6yRkuJtXRKxM" />
|
||||
|
||||
<meta property="og:url" content="https://scratch.mit.edu/" />
|
||||
<meta property="og:title" content="Scratch - Imagine, Program, Share" />
|
||||
<meta property="og:description" content="Make games, stories and interactive art with Scratch. (scratch.mit.edu)" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="Scratch - {{title}}" />
|
||||
<meta property="og:description" content="{{description}}" />
|
||||
<meta property="og:image" content="{{&og_image}}" />
|
||||
<meta property="og:image:type" content="{{&og_image_type}}" />
|
||||
<meta property="og:image:width" content="{{&og_image_width}}" />
|
||||
<meta property="og:image:height" content="{{&og_image_height}}" />
|
||||
|
||||
<!-- Favicon & CSS normalize -->
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/css/lib/normalize.min.css" />
|
||||
|
||||
<!-- Shim/Sham ES5 polyfill for older browsers -->
|
||||
<!-- Polyfill -->
|
||||
<script src="/js/lib/polyfill.min.js"></script>
|
||||
|
||||
<!-- Initialize (Session & Localization) -->
|
||||
<script src="/js/init.bundle.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -32,11 +42,24 @@
|
|||
|
||||
<!-- Scripts -->
|
||||
<script src="/js/lib/react.js"></script>
|
||||
<script src="/js/lib/react-dom.js"></script>
|
||||
|
||||
<script src="/js/main.bundle.js"></script>
|
||||
<script src="/js/{{view}}.bundle.js"></script>
|
||||
|
||||
<!-- @todo Analytics (GA) -->
|
||||
<!-- @todo Monitoring (Raven) -->
|
||||
<!-- Analytics (GA) -->
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', '{{&ga_tracker}}', {
|
||||
'sampleRate': 10
|
||||
});
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
|
||||
<!-- @todo New Relic -->
|
||||
</body>
|
||||
</html>
|
||||
|
|
33
src/_colors.scss
Normal file
|
@ -0,0 +1,33 @@
|
|||
/* UI Primary Colors */
|
||||
$ui-blue: hsla(200, 90, 55, 1); // #25AFF4
|
||||
$ui-orange: hsla(35, 90, 55, 1); // #F49D25
|
||||
$ui-gray: hsla(0, 0, 95, 1); //#F2F2F2
|
||||
$ui-dark-gray: hsla(0, 0, 70, 1); //#B3B3B3
|
||||
|
||||
$background-color: hsla(0, 0, 99, 1); //#FDFDFD
|
||||
|
||||
|
||||
/* UI Secondary Colors */
|
||||
$ui-aqua: hsla(170, 70, 50, 1); //#26D9BB
|
||||
$ui-white: #fff;
|
||||
|
||||
$ui-border: hsla(0, 0, 85, 1); //#D9D9D9
|
||||
|
||||
|
||||
/* Overlay UI Gray Colors */
|
||||
$active-gray: hsla(0, 0, 0, .1);
|
||||
$active-dark-gray: hsla(0, 0, 0, .2);
|
||||
$box-shadow-gray: hsla(0, 0, 0, .25);
|
||||
$overlay-gray: hsla(0, 0, 0, .75);
|
||||
|
||||
/* Typography Colors */
|
||||
$header-gray: hsla(0, 0, 42, 1); //#6B6B6B
|
||||
$type-gray: hsla(0, 0, 42, 1); //#6B6B6B
|
||||
$type-white: #fff;
|
||||
|
||||
$link-blue: $ui-blue;
|
||||
|
||||
/* Component colors */
|
||||
$splash-green: #9c0;
|
||||
$splash-pink: #c2479d;
|
||||
$splash-blue: #199ed7;
|
|
@ -1,57 +1,66 @@
|
|||
[
|
||||
{
|
||||
"id": 5,
|
||||
"author": {
|
||||
"id": "lynxkitten101",
|
||||
"username": "lynxkitten101",
|
||||
"avatar": "https://cdn2.scratch.mit.edu/get_image/user/10122882_32x32.png?v=1438781835.82"
|
||||
"obj_id": 82475328,
|
||||
"datetime_created": "2015-10-20T15:13:36",
|
||||
"actor": {
|
||||
"username": "ceebee",
|
||||
"pk": 2755634,
|
||||
"thumbnail_url": "//cdn.scratch.mit.edu/static/site/users/avatars/275/5634.png",
|
||||
"admin": true
|
||||
},
|
||||
"stamp": "2015-09-08T19:40:00Z",
|
||||
"message": "became a curator of A bit of everything",
|
||||
"url": "/studios/1511220/"
|
||||
"pk": 186757838,
|
||||
"message": "\nfavorited\n <a href=\"/projects/82475328/\">miner man</a>",
|
||||
"extra_data": {
|
||||
"project_title": "miner man"
|
||||
},
|
||||
"type": 3
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"author": {
|
||||
"id": "sheep_tester",
|
||||
"username": "Sheep_tester",
|
||||
"avatar": "https://cdn2.scratch.mit.edu/get_image/user/3290075_32x32.png?v=1439575340.77"
|
||||
"obj_id": 82475328,
|
||||
"datetime_created": "2015-10-20T15:13:36",
|
||||
"actor": {
|
||||
"username": "ceebee",
|
||||
"pk": 2755634,
|
||||
"thumbnail_url": "//cdn.scratch.mit.edu/static/site/users/avatars/275/5634.png",
|
||||
"admin": true
|
||||
},
|
||||
"stamp": "2015-09-08T03:20:00Z",
|
||||
"message": "became a curator of To All My Great Followers & People",
|
||||
"url": "/studios/1511360/"
|
||||
"pk": 186757836,
|
||||
"message": "\nloved\n <a href=\"/projects/82475328/\">miner man</a>",
|
||||
"extra_data": {
|
||||
"project_title": "miner man"
|
||||
},
|
||||
"type": 2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"author": {
|
||||
"id": "-vexvorlo-",
|
||||
"username": "-VexVorlo-",
|
||||
"avatar": "https://cdn2.scratch.mit.edu/get_image/user/10930092_32x32.png?v=1439735067.99"
|
||||
"obj_id": 82475328,
|
||||
"datetime_created": "2015-10-20T15:12:39",
|
||||
"actor": {
|
||||
"username": "speakvisually",
|
||||
"pk": 3484484,
|
||||
"thumbnail_url": "//cdn.scratch.mit.edu/static/site/users/avatars/348/4484.png",
|
||||
"admin": true
|
||||
},
|
||||
"stamp": "2015-09-01T03:20:00Z",
|
||||
"message": "loved Virtual Chicken Coop",
|
||||
"url": "/projects/73311484/"
|
||||
"pk": 186757510,
|
||||
"message": "\nfavorited\n <a href=\"/projects/82475328/\">miner man</a>",
|
||||
"extra_data": {
|
||||
"project_title": "miner man"
|
||||
},
|
||||
"type": 3
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"author": {
|
||||
"id": "-vexvorlo-",
|
||||
"username": "-VexVorlo-",
|
||||
"avatar": "https://cdn2.scratch.mit.edu/get_image/user/10930092_32x32.png?v=1439735067.99"
|
||||
"obj_id": 82475328,
|
||||
"datetime_created": "2015-10-20T15:12:37",
|
||||
"actor": {
|
||||
"username": "speakvisually",
|
||||
"pk": 3484484,
|
||||
"thumbnail_url": "//cdn.scratch.mit.edu/static/site/users/avatars/348/4484.png",
|
||||
"admin": true
|
||||
},
|
||||
"stamp": "2015-08-22T02:00:00Z",
|
||||
"message": "loved Bread bread bread..",
|
||||
"url": "/projects/75677832/"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"author": {
|
||||
"id": "getbent",
|
||||
"username": "getbent",
|
||||
"avatar": "https://cdn2.scratch.mit.edu/get_image/user/676422_32x32.png?v=1436728562.25"
|
||||
"pk": 186757500,
|
||||
"message": "\nloved\n <a href=\"/projects/82475328/\">miner man</a>",
|
||||
"extra_data": {
|
||||
"project_title": "miner man"
|
||||
},
|
||||
"stamp": "2014-09-08T00:00:00Z",
|
||||
"message": "loved Is ______ An Instrurment?",
|
||||
"url": "/projects/75347968/"
|
||||
"type": 2
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
var React = require('react');
|
||||
var ReactIntl = require('react-intl');
|
||||
var defineMessages = ReactIntl.defineMessages;
|
||||
var FormattedMessage = ReactIntl.FormattedMessage;
|
||||
var FormattedRelative = ReactIntl.FormattedRelative;
|
||||
var injectIntl = ReactIntl.injectIntl;
|
||||
|
||||
var Box = require('../box/box.jsx');
|
||||
var Format = require('../../lib/format.js');
|
||||
|
||||
require('./activity.scss');
|
||||
|
||||
module.exports = React.createClass({
|
||||
var defaultMessages = defineMessages({
|
||||
whatsHappening: {
|
||||
id: 'general.whatsHappening',
|
||||
defaultMessage: 'What\'s Happening?'
|
||||
}
|
||||
});
|
||||
|
||||
var Activity = React.createClass({
|
||||
type: 'Activity',
|
||||
propTypes: {
|
||||
items: React.PropTypes.array
|
||||
},
|
||||
|
@ -15,25 +27,55 @@ module.exports = React.createClass({
|
|||
};
|
||||
},
|
||||
render: function () {
|
||||
var formatMessage = this.props.intl.formatMessage;
|
||||
return (
|
||||
<Box
|
||||
className="activity"
|
||||
title="What's Happening?">
|
||||
title={formatMessage(defaultMessages.whatsHappening)}>
|
||||
|
||||
<ul>
|
||||
{this.props.items.map(function (item) {
|
||||
return (
|
||||
<li key={item.id}>
|
||||
<a href={item.url}>
|
||||
<img src={item.author.avatar} width="34" height="34" />
|
||||
<p>{item.author.username} {item.message}</p>
|
||||
<p><span className="stamp">{Format.date(item.stamp)}</span></p>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
{this.props.items && this.props.items.length > 0 ? [
|
||||
<ul>
|
||||
{this.props.items.map(function (item) {
|
||||
if (item.message.replace(/\s/g, '')) {
|
||||
var actorProfileUrl = '/users/' + item.actor.username + '/';
|
||||
var actionDate = new Date(item.datetime_created + 'Z');
|
||||
var activityMessageHTML = (
|
||||
'<a href=' + actorProfileUrl + '>' + item.actor.username + '</a>' +
|
||||
item.message
|
||||
);
|
||||
return (
|
||||
<li key={item.pk}>
|
||||
<a href={actorProfileUrl}>
|
||||
<img src={item.actor.thumbnail_url} width="34" height="34" />
|
||||
<p dangerouslySetInnerHTML={{__html: activityMessageHTML}}></p>
|
||||
<p>
|
||||
<span className="stamp">
|
||||
<FormattedRelative value={actionDate} />
|
||||
</span>
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
] : [
|
||||
<div className="empty">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="activity.seeUpdates"
|
||||
defaultMessage="This is where you will see updates from Scratchers you follow" />
|
||||
</h4>
|
||||
<a href="/studios/146521/">
|
||||
<FormattedMessage
|
||||
id="activity.checkOutScratchers"
|
||||
defaultMessage="Check out some Scratchers you might like to follow" />
|
||||
</a>
|
||||
</div>
|
||||
]}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = injectIntl(Activity);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import "../../colors";
|
||||
|
||||
.activity {
|
||||
ul {
|
||||
display: block;
|
||||
|
@ -29,14 +31,14 @@
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-size: 0.9rem;
|
||||
white-space: nowrap;
|
||||
font-size: .9rem;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.stamp {
|
||||
color: #999;
|
||||
font-size: 0.65rem;
|
||||
color: $ui-dark-gray;
|
||||
font-size: .65rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
81
src/components/adminpanel/adminpanel.jsx
Normal file
|
@ -0,0 +1,81 @@
|
|||
var React = require('react');
|
||||
|
||||
var Button = require('../../components/forms/button.jsx');
|
||||
var Session = require('../../mixins/session.jsx');
|
||||
|
||||
require('./adminpanel.scss');
|
||||
|
||||
var AdminPanel = React.createClass({
|
||||
type: 'AdminPanel',
|
||||
mixins: [
|
||||
Session
|
||||
],
|
||||
getInitialState: function () {
|
||||
return {
|
||||
showPanel: false
|
||||
};
|
||||
},
|
||||
handleToggleVisibility: function (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.state.session.user) {
|
||||
showAdmin = this.state.session.permissions.admin;
|
||||
}
|
||||
|
||||
if (!showAdmin) return false;
|
||||
|
||||
if (this.state.showPanel) {
|
||||
return (
|
||||
<div id="admin-panel" className="visible">
|
||||
<span
|
||||
className="toggle"
|
||||
onClick={this.handleToggleVisibility}>
|
||||
|
||||
x
|
||||
</span>
|
||||
<div className="admin-header">
|
||||
<h3>Admin Panel</h3>
|
||||
</div>
|
||||
<div className="admin-content">
|
||||
<dl>
|
||||
{this.props.children}
|
||||
<dt>Page Cache</dt>
|
||||
<dd>
|
||||
<ul className="cache-list">
|
||||
<li>
|
||||
<form method="post" action="/scratch_admin/page/clear-anon-cache/">
|
||||
<input type="hidden" name="path" value="/" />
|
||||
<div className="button-row">
|
||||
<span>For anonymous users:</span>
|
||||
<Button type="submit">
|
||||
<span>Clear</span>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div id="admin-panel" className="hidden">
|
||||
<span
|
||||
className="toggle"
|
||||
onClick={this.handleToggleVisibility}>
|
||||
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = AdminPanel;
|
68
src/components/adminpanel/adminpanel.scss
Normal file
|
@ -0,0 +1,68 @@
|
|||
@import "../../colors";
|
||||
|
||||
#admin-panel {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 99;
|
||||
border: 1px solid $ui-gray;
|
||||
box-shadow: 0 2px 5px $box-shadow-gray;
|
||||
background-color: $ui-gray;
|
||||
padding: 1rem;
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
text-shadow: none;
|
||||
|
||||
&.visible {
|
||||
width: 20%;
|
||||
min-width: 180px;
|
||||
max-width: 230px;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.admin-content {
|
||||
dl {
|
||||
list-style: none;
|
||||
|
||||
dt {
|
||||
margin: 2rem 0 1rem 0;
|
||||
border-bottom: 1px solid $ui-dark-gray;
|
||||
font-size: large;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
font-size: small;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.button {
|
||||
background-color: $ui-dark-gray;
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
src/components/avatar/avatar.jsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
|
||||
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} />;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Avatar;
|
3
src/components/avatar/avatar.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
.avatar {
|
||||
border: 1px solid $ui-border;
|
||||
}
|
29
src/components/banner/banner.jsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
|
||||
require('./banner.scss');
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Banner;
|
41
src/components/banner/banner.scss
Normal file
|
@ -0,0 +1,41 @@
|
|||
@import "../../colors";
|
||||
|
||||
$navigation-height: 50px;
|
||||
|
||||
.banner {
|
||||
position: fixed;
|
||||
top: $navigation-height;
|
||||
z-index: 99;
|
||||
box-shadow: 0 1px 1px $ui-dark-gray;
|
||||
background-color: $ui-orange;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
line-height: $navigation-height;
|
||||
|
||||
&, a {
|
||||
color: $ui-white;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.close {
|
||||
float: right;
|
||||
margin-top: $navigation-height/4;
|
||||
border-radius: $navigation-height/4;
|
||||
background-color: $box-shadow-gray;
|
||||
width: $navigation-height/2;
|
||||
height: $navigation-height/2;
|
||||
text-decoration: none;
|
||||
text-shadow: none;
|
||||
line-height: $navigation-height/2;
|
||||
color: $ui-white;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: $ui-orange;
|
||||
}
|
||||
}
|
|
@ -1,20 +1,27 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
|
||||
require('./box.scss');
|
||||
|
||||
module.exports = React.createClass({
|
||||
var Box = React.createClass({
|
||||
type: 'Box',
|
||||
propTypes: {
|
||||
title: React.PropTypes.string.isRequired,
|
||||
moreTitle: React.PropTypes.string,
|
||||
moreHref: React.PropTypes.string
|
||||
moreHref: React.PropTypes.string,
|
||||
moreProps: React.PropTypes.object
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'box',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<div className={'box ' + this.props.className}>
|
||||
<div className={classes}>
|
||||
<div className="box-header">
|
||||
<h4>{this.props.title}</h4>
|
||||
<p>
|
||||
<a href={this.props.moreHref}>
|
||||
<a href={this.props.moreHref} {...this.props.moreProps}>
|
||||
{this.props.moreTitle}
|
||||
</a>
|
||||
</p>
|
||||
|
@ -27,3 +34,5 @@ module.exports = React.createClass({
|
|||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Box;
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
@import "../../colors";
|
||||
|
||||
$base-bg: $ui-white;
|
||||
|
||||
.box {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
border: 1px solid #e0e0e0;
|
||||
border: 1px solid $ui-border;
|
||||
border-radius: 10px 10px 0 0;
|
||||
box-shadow: 0 2px 3px rgba(34, 25, 25, 0.3);
|
||||
background-color: $ui-white;
|
||||
width: 100%;
|
||||
|
||||
.box-header {
|
||||
display: block;
|
||||
margin: 0;
|
||||
height: 20px;
|
||||
padding: 8px 20px;
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
|
||||
background-color: #efefef;
|
||||
margin: 0;
|
||||
border-top: 1px solid $ui-white;
|
||||
border-bottom: 1px solid $ui-border;
|
||||
border-radius: 10px 10px 0 0;
|
||||
border-top: 1px solid white;
|
||||
border-bottom: 1px solid #ccc;
|
||||
background-color: $ui-gray;
|
||||
|
||||
padding: 8px 20px;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
|
||||
h4 {
|
||||
display: inline-block;
|
||||
|
@ -32,15 +35,18 @@
|
|||
margin: 1px 0 0 0;
|
||||
padding: 0;
|
||||
|
||||
font-size: 0.85rem;
|
||||
font-size: .85rem;
|
||||
}
|
||||
}
|
||||
|
||||
.box-content {
|
||||
display: block;
|
||||
padding: 8px 20px;
|
||||
clear: both;
|
||||
background-color: $base-bg;
|
||||
padding: 8px 20px;
|
||||
}
|
||||
|
||||
background-color: white;
|
||||
.empty {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,48 +2,49 @@
|
|||
{
|
||||
"id": 1,
|
||||
"type": "project",
|
||||
"title": "Example Project",
|
||||
"thumbnailUrl": "http://www.lorempixel.com/144/108/",
|
||||
"creator": "raimondious",
|
||||
"href": "/projects/1000/"
|
||||
"title": "Project",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "project",
|
||||
"title": "Example Project",
|
||||
"thumbnailUrl": "http://www.lorempixel.com/144/108/",
|
||||
"href": "/projects/1000/"
|
||||
"title": "Project",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "project",
|
||||
"title": "Example Project",
|
||||
"thumbnailUrl": "http://www.lorempixel.com/144/108/",
|
||||
"creator": "raimondious",
|
||||
"href": "/projects/1000/"
|
||||
"title": "Project",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "project",
|
||||
"title": "Example Project",
|
||||
"thumbnailUrl": "http://www.lorempixel.com/144/108/",
|
||||
"creator": "raimondious",
|
||||
"href": "/projects/1000/"
|
||||
"title": "Project",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "project",
|
||||
"title": "Example Project",
|
||||
"thumbnailUrl": "http://www.lorempixel.com/144/108/",
|
||||
"creator": "raimondious",
|
||||
"href": "/projects/1000/"
|
||||
"title": "Project",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "project",
|
||||
"title": "Example Project",
|
||||
"thumbnailUrl": "http://www.lorempixel.com/144/108/",
|
||||
"creator": "raimondious",
|
||||
"href": "/projects/1000/"
|
||||
"title": "Project",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,42 +1,72 @@
|
|||
var classNames = require('classnames');
|
||||
var defaults = require('lodash.defaults');
|
||||
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({
|
||||
var Carousel = React.createClass({
|
||||
type: 'Carousel',
|
||||
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
|
||||
}
|
||||
showRemixes: false,
|
||||
showLoves: false
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
var settings = this.props.settings || {};
|
||||
defaults(settings, {
|
||||
dots: false,
|
||||
infinite: false,
|
||||
lazyLoad: true,
|
||||
slidesToShow: 5,
|
||||
slidesToScroll: 5,
|
||||
variableWidth: true
|
||||
});
|
||||
var arrows = this.props.items.length > settings.slidesToShow;
|
||||
var classes = classNames(
|
||||
'carousel',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<Slider className={'carousel ' + this.props.className} {... this.props.settings}>
|
||||
<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 + '/';
|
||||
}
|
||||
|
||||
return (
|
||||
<Thumbnail key={item.id}
|
||||
href={item.href}
|
||||
showLoves={this.props.showLoves}
|
||||
showRemixes={this.props.showRemixes}
|
||||
type={item.type}
|
||||
href={href}
|
||||
title={item.title}
|
||||
src={item.thumbnailUrl}
|
||||
extra={item.creator ? 'by ' + item.creator:null} />
|
||||
src={item.thumbnail_url}
|
||||
creator={item.creator}
|
||||
remixes={item.remixers_count}
|
||||
loves={item.love_count} />
|
||||
);
|
||||
})}
|
||||
}.bind(this))}
|
||||
</Slider>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Carousel;
|
||||
|
|
|
@ -1,25 +1,34 @@
|
|||
@import "../../colors";
|
||||
|
||||
.carousel {
|
||||
$icon-size: 40px;
|
||||
$button-offset: $icon-size + 5px;
|
||||
$box-content-offset: 20px;
|
||||
|
||||
padding: 0 $button-offset;
|
||||
padding: 12px $button-offset;
|
||||
|
||||
.box-content & {
|
||||
padding: 0 $button-offset - 20px;
|
||||
padding: 12px $button-offset - 20px;
|
||||
}
|
||||
|
||||
.slick-next, .slick-prev {
|
||||
.slick-next,
|
||||
.slick-prev {
|
||||
margin-top: -$icon-size/2;
|
||||
width: $icon-size;
|
||||
height: $icon-size;
|
||||
margin-top: -$icon-size/2;
|
||||
|
||||
&:before {
|
||||
display: block;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 70%;
|
||||
width: $icon-size;
|
||||
height: $icon-size;
|
||||
font-size: $icon-size;
|
||||
color: #ddd;
|
||||
content: "";
|
||||
}
|
||||
|
||||
&.slick-disabled:before{
|
||||
&.slick-disabled:before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +36,16 @@
|
|||
.slick-prev {
|
||||
left: 0;
|
||||
|
||||
&:before {
|
||||
background-image: url("/svgs/carousel/prev_ui-dark-gray.svg");
|
||||
}
|
||||
|
||||
&:hover:before {
|
||||
background-image: url("/svgs/carousel/prev_ui-blue.svg");
|
||||
background-size: 90%;
|
||||
}
|
||||
|
||||
|
||||
.box-content & {
|
||||
left: -$box-content-offset;
|
||||
}
|
||||
|
@ -35,6 +54,15 @@
|
|||
.slick-next {
|
||||
right: 0;
|
||||
|
||||
&:before {
|
||||
background-image: url("/svgs/carousel/next_ui-dark-gray.svg");
|
||||
}
|
||||
|
||||
&:hover:before {
|
||||
background-image: url("/svgs/carousel/next_ui-blue.svg");
|
||||
background-size: 90%;
|
||||
}
|
||||
|
||||
.box-content & {
|
||||
right: -$box-content-offset;
|
||||
}
|
||||
|
|
|
@ -1,60 +1,232 @@
|
|||
var React = require('react');
|
||||
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||
|
||||
var LanguageChooser = require('../languagechooser/languagechooser.jsx');
|
||||
|
||||
require('./footer.scss');
|
||||
|
||||
module.exports = React.createClass({
|
||||
var Footer = React.createClass({
|
||||
type: 'Footer',
|
||||
render: function () {
|
||||
return (
|
||||
<div className="inner">
|
||||
<div className="lists">
|
||||
<dl>
|
||||
<dt>About</dt>
|
||||
<dd><a href="/about/">About Scratch</a></dd>
|
||||
<dd><a href="/parents/">For Parents</a></dd>
|
||||
<dd><a href="/educators/">For Educators</a></dd>
|
||||
<dd><a href="/info/credits/">Credits</a></dd>
|
||||
<dd><a href="/jobs/">Jobs</a></dd>
|
||||
<dd><a href="http://wiki.scratch.mit.edu/wiki/Scratch_Press">Press</a></dd>
|
||||
<dt>
|
||||
<FormattedMessage
|
||||
id='general.about'
|
||||
defaultMessage={'About'} />
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="/about/">
|
||||
<FormattedMessage
|
||||
id='footer.about'
|
||||
defaultMessage={'About Scratch'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/parents/">
|
||||
<FormattedMessage
|
||||
id='general.forParents'
|
||||
defaultMessage={'For Parents'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/educators/">
|
||||
<FormattedMessage
|
||||
id='general.forEducators'
|
||||
defaultMessage={'For Educators'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/info/credits/">
|
||||
<FormattedMessage
|
||||
id='general.credits'
|
||||
defaultMessage={'Credits'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/jobs/">
|
||||
<FormattedMessage
|
||||
id='general.jobs'
|
||||
defaultMessage={'Jobs'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="http://wiki.scratch.mit.edu/wiki/Scratch_Press">
|
||||
<FormattedMessage
|
||||
id='general.press'
|
||||
defaultMessage={'Press'} />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl>
|
||||
<dt>Community</dt>
|
||||
<dd><a href="/community_guidelines/">Community Guidelines</a></dd>
|
||||
<dd><a href="/discuss/">Discussion Forums</a></dd>
|
||||
<dd><a href="https://wiki.scratch.mit.edu/">Scratch Wiki</a></dd>
|
||||
<dd><a href="/statistics/">Statistics</a></dd>
|
||||
<dt>
|
||||
<FormattedMessage
|
||||
id='general.communityHeader'
|
||||
defaultMessage={'Community'} />
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="/community_guidelines/">
|
||||
<FormattedMessage
|
||||
id='general.guidelines'
|
||||
defaultMessage={'Community Guidelines'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/discuss/">
|
||||
<FormattedMessage
|
||||
id='footer.discuss'
|
||||
defaultMessage={'Discussion Forums'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="https://wiki.scratch.mit.edu/">
|
||||
<FormattedMessage
|
||||
id='general.wiki'
|
||||
defaultMessage={'Scratch Wiki'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/statistics/">
|
||||
<FormattedMessage
|
||||
id='general.statistics'
|
||||
defaultMessage={'Statistics'} />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl>
|
||||
<dt>Support</dt>
|
||||
<dd><a href="/help/">Help Page</a></dd>
|
||||
<dd><a href="/info/faq/">FAQ</a></dd>
|
||||
<dd><a href="/scratch2download/">Offline Editor</a></dd>
|
||||
<dd><a href="/contact-us/">Contact Us</a></dd>
|
||||
<dd><a href="https://secure.donationpay.org/codetolearn/">Donate</a></dd>
|
||||
<dt>
|
||||
<FormattedMessage
|
||||
id='general.support'
|
||||
defaultMessage={'Support'} />
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="/help/">
|
||||
<FormattedMessage
|
||||
id='footer.help'
|
||||
defaultMessage={'Help Page'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/info/faq/">
|
||||
<FormattedMessage
|
||||
id='general.faq'
|
||||
defaultMessage={'FAQ'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/scratch2download/">
|
||||
<FormattedMessage
|
||||
id='general.offlineEditor'
|
||||
defaultMessage={'Offline Editor'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/contact-us/">
|
||||
<FormattedMessage
|
||||
id='general.contactUs'
|
||||
defaultMessage={'Contact Us'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="https://secure.donationpay.org/codetolearn/">
|
||||
<FormattedMessage
|
||||
id='general.donate'
|
||||
defaultMessage={'Donate'} />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl>
|
||||
<dt>Legal</dt>
|
||||
<dd><a href="/terms_of_use/">Terms of Use</a></dd>
|
||||
<dd><a href="/privacy_policy/">Privacy Policy</a></dd>
|
||||
<dd><a href="/DMCA/">DMCA</a></dd>
|
||||
<dt>
|
||||
<FormattedMessage
|
||||
id='general.legal'
|
||||
defaultMessage={'Legal'} />
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="/terms_of_use/">
|
||||
<FormattedMessage
|
||||
id='general.termsOfUse'
|
||||
defaultMessage={'Terms of Use'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/privacy_policy/">
|
||||
<FormattedMessage
|
||||
id='privacyPolicy'
|
||||
defaultMessage={'Privacy Policy'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/DMCA/">
|
||||
<FormattedMessage
|
||||
id='general.dmca'
|
||||
defaultMessage={'DMCA'} />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl>
|
||||
<dt>Scratch Family</dt>
|
||||
<dd><a href="http://scratched.gse.harvard.edu/">ScratchEd</a></dd>
|
||||
<dd><a href="http://www.scratchjr.org/">ScratchJr</a></dd>
|
||||
<dd><a href="http://day.scratch.mit.edu/">Scratch Day</a></dd>
|
||||
<dd><a href="/conference/">Scratch Conference</a></dd>
|
||||
<dd><a href="http://www.scratchfoundation.org/">Scratch Foundation</a></dd>
|
||||
<dt>
|
||||
<FormattedMessage
|
||||
id='footer.scratchFamily'
|
||||
defaultMessage={'Scratch Family'} />
|
||||
</dt>
|
||||
<dd>
|
||||
<a href="http://scratched.gse.harvard.edu/">
|
||||
<FormattedMessage
|
||||
id='general.scratchEd'
|
||||
defaultMessage={'ScratchEd'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="http://www.scratchjr.org/">
|
||||
<FormattedMessage
|
||||
id='general.scratchJr'
|
||||
defaultMessage={'ScratchJr'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="http://day.scratch.mit.edu/">
|
||||
<FormattedMessage
|
||||
id='general.scratchday'
|
||||
defaultMessage={'Scratch Day'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="/conference/">
|
||||
<FormattedMessage
|
||||
id='general.scratchConference'
|
||||
defaultMessage={'Scratch Conference'} />
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="http://www.scratchfoundation.org/">
|
||||
<FormattedMessage
|
||||
id='general.scratchFoundation'
|
||||
defaultMessage={'Scratch Foundation'} />
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<LanguageChooser />
|
||||
|
||||
<div className="copyright">
|
||||
<p>Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='general.copyright'
|
||||
defaultMessage={
|
||||
'Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab'
|
||||
} />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Footer;
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
@import "../../colors";
|
||||
|
||||
#footer {
|
||||
display: block;
|
||||
background-color: $ui-gray;
|
||||
padding: 10px 0;
|
||||
color: #666;
|
||||
background-color: #ececec;
|
||||
color: $type-gray;
|
||||
font-size: .85rem;
|
||||
|
||||
.lists {
|
||||
display: flex;
|
||||
text-align: center;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
text-align: center;
|
||||
|
||||
dl {
|
||||
display: inline-block;
|
||||
width: 130pt;
|
||||
font-size: 0.85rem;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
dt {
|
||||
|
@ -37,7 +39,11 @@
|
|||
text-align: center;
|
||||
|
||||
p {
|
||||
font-size: 0.7rem;
|
||||
font-size: .7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.language-chooser {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@ var classNames = require('classnames');
|
|||
|
||||
require('./button.scss');
|
||||
|
||||
module.exports = React.createClass({
|
||||
var Button = React.createClass({
|
||||
type: 'Button',
|
||||
propTypes: {
|
||||
|
||||
},
|
||||
|
@ -17,3 +18,5 @@ module.exports = React.createClass({
|
|||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Button;
|
||||
|
|
|
@ -1,27 +1,35 @@
|
|||
@import "../../colors";
|
||||
|
||||
$base-bg: $ui-white;
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
font-size: .8rem;
|
||||
padding: .75em 1em;
|
||||
margin: .5em 0;
|
||||
background-color: #24a3ec;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: 0 1px 1px $box-shadow-gray;
|
||||
background-color: $ui-blue;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
padding: .75em 1em;
|
||||
color: $type-white;
|
||||
font-size: .8rem;
|
||||
font-weight: bold;
|
||||
|
||||
&.white {
|
||||
background-color: white;
|
||||
border-top: 1px inset rgba(0, 0, 0, 0.1);
|
||||
color: #24a3ec;
|
||||
border-top: 1px inset $active-gray;
|
||||
background-color: $base-bg;
|
||||
color: $ui-blue;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: 0 2px 2px $box-shadow-gray;
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: inset 0 1px 2px $box-shadow-gray;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,8 @@ var classNames = require('classnames');
|
|||
|
||||
require('./input.scss');
|
||||
|
||||
module.exports = React.createClass({
|
||||
var Input = React.createClass({
|
||||
type: 'Input',
|
||||
propTypes: {
|
||||
|
||||
},
|
||||
|
@ -17,3 +18,5 @@ module.exports = React.createClass({
|
|||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Input;
|
||||
|
|
|
@ -1,28 +1,34 @@
|
|||
@import "../../colors";
|
||||
|
||||
$base-bg: $ui-white;
|
||||
$focus-bg: lighten($ui-blue, 35%);
|
||||
$fail-bg: lighten($ui-orange, 35%);
|
||||
$pass-bg: lighten($ui-aqua, 35%);
|
||||
|
||||
.input {
|
||||
color:black;
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
font-size: .8rem;
|
||||
padding: .75em 1em;
|
||||
transition: all 1s ease;
|
||||
margin: .5em 0;
|
||||
background-color: #f7f7f7;
|
||||
transition:all 1s ease;
|
||||
border: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
background-color: $base-bg;
|
||||
padding: .75em 1em;
|
||||
color: $type-gray;
|
||||
font-size: .8rem;
|
||||
|
||||
&:focus {
|
||||
background-color: #d3eaf8;
|
||||
outline:none;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
transition:all 1s ease;
|
||||
transition: all 1s ease;
|
||||
outline: none;
|
||||
border: 1px solid $active-dark-gray;
|
||||
background-color: $focus-bg;
|
||||
}
|
||||
|
||||
&.fail {
|
||||
border: 1px solid #eab012;
|
||||
background-color: #fff7df;
|
||||
border: 1px solid $active-dark-gray;
|
||||
background-color: $fail-bg;
|
||||
}
|
||||
|
||||
&.pass {
|
||||
border: 1px solid #55db58;
|
||||
background-color: #eafdea;
|
||||
border: 1px solid $active-dark-gray;
|
||||
background-color: $pass-bg;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
24
src/components/forms/select.jsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
|
||||
require('./select.scss');
|
||||
|
||||
var Select = React.createClass({
|
||||
type: 'Select',
|
||||
propTypes: {
|
||||
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'select',
|
||||
this.props.className
|
||||
);
|
||||
return (
|
||||
<select {... this.props} className={classes}>
|
||||
{this.props.children}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Select;
|
9
src/components/forms/select.scss
Normal file
|
@ -0,0 +1,9 @@
|
|||
@import "../../colors";
|
||||
|
||||
.select {
|
||||
background-color: $ui-white;
|
||||
width: 220px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
font-size: 1em;
|
||||
}
|
|
@ -1,11 +1,18 @@
|
|||
var omit = require('lodash.omit');
|
||||
var React = require('react');
|
||||
var ReactIntl = require('react-intl');
|
||||
var FormattedMessage = ReactIntl.FormattedMessage;
|
||||
var FormattedHTMLMessage = ReactIntl.FormattedHTMLMessage;
|
||||
|
||||
var Modal = require('../modal/modal.jsx');
|
||||
var Registration = require('../registration/registration.jsx');
|
||||
|
||||
require('./intro.scss');
|
||||
|
||||
Modal.setAppElement(document.getElementById('view'));
|
||||
|
||||
module.exports = React.createClass({
|
||||
var Intro = React.createClass({
|
||||
type: 'Intro',
|
||||
propTypes: {
|
||||
projectCount: React.PropTypes.number
|
||||
},
|
||||
|
@ -25,13 +32,33 @@ module.exports = React.createClass({
|
|||
closeVideo: function () {
|
||||
this.setState({videoOpen: false});
|
||||
},
|
||||
handleJoinClick: function (e) {
|
||||
e.preventDefault();
|
||||
this.setState({'registrationOpen': true});
|
||||
},
|
||||
closeRegistration: function () {
|
||||
this.setState({'registrationOpen': false});
|
||||
},
|
||||
completeRegistration: function () {
|
||||
window.refreshSession();
|
||||
this.closeRegistration();
|
||||
},
|
||||
render: function () {
|
||||
var frameProps = {
|
||||
width: 570,
|
||||
height: 357,
|
||||
padding: 15
|
||||
};
|
||||
return (
|
||||
<div className="intro">
|
||||
<div className="content">
|
||||
<h1>
|
||||
Create stories, games, and animations<br />
|
||||
Share with others around the world
|
||||
<FormattedHTMLMessage
|
||||
id='intro.tagLine'
|
||||
defaultMessage={
|
||||
'Create stories, games, and animations<br /> ' +
|
||||
'Share with others around the world'
|
||||
} />
|
||||
</h1>
|
||||
<div className="sprites">
|
||||
<a className="sprite sprite-1" href="/projects/editor/?tip_bar=getStarted">
|
||||
|
@ -42,7 +69,11 @@ module.exports = React.createClass({
|
|||
className="costume costume-2"
|
||||
src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-b.png" />
|
||||
<div className="circle"></div>
|
||||
<div className="text">TRY IT OUT</div>
|
||||
<div className="text">
|
||||
<FormattedMessage
|
||||
id='intro.tryItOut'
|
||||
defaultMessage='TRY IT OUT' />
|
||||
</div>
|
||||
</a>
|
||||
<a className="sprite sprite-2" href="/starter_projects/">
|
||||
<img
|
||||
|
@ -52,9 +83,13 @@ module.exports = React.createClass({
|
|||
className="costume costume-2"
|
||||
src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-b.png" />
|
||||
<div className="circle"></div>
|
||||
<div className="text">SEE EXAMPLES</div>
|
||||
<div className="text">
|
||||
<FormattedMessage
|
||||
id='intro.seeExamples'
|
||||
defaultMessage='SEE EXAMPLES' />
|
||||
</div>
|
||||
</a>
|
||||
<a className="sprite sprite-3" href="#">
|
||||
<a className="sprite sprite-3" href="#" onClick={this.handleJoinClick}>
|
||||
<img
|
||||
className="costume costume-1"
|
||||
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-a.png" />
|
||||
|
@ -62,9 +97,17 @@ module.exports = React.createClass({
|
|||
className="costume costume-2"
|
||||
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-b.png" />
|
||||
<div className="circle"></div>
|
||||
<div className="text">JOIN SCRATCH</div>
|
||||
<div className="text">
|
||||
<FormattedMessage
|
||||
id='intro.joinScratch'
|
||||
defaultMessage='JOIN SCRATCH' />
|
||||
</div>
|
||||
<div className="text subtext">( it’s free )</div>
|
||||
</a>
|
||||
<Registration key="registration"
|
||||
isOpen={this.state.registrationOpen}
|
||||
onRequestClose={this.closeRegistration}
|
||||
onRegistrationDone={this.completeRegistration} />
|
||||
</div>
|
||||
<div className="description">
|
||||
A creative learning community with
|
||||
|
@ -72,9 +115,21 @@ module.exports = React.createClass({
|
|||
projects shared
|
||||
</div>
|
||||
<div className="links">
|
||||
<a href="/about/">ABOUT SCRATCH</a>
|
||||
<a href="/educators/">FOR EDUCATORS</a>
|
||||
<a className="last" href="/parents/">FOR PARENTS</a>
|
||||
<a href="/about/">
|
||||
<FormattedMessage
|
||||
id='intro.aboutScratch'
|
||||
defaultMessage='ABOUT SCRATCH' />
|
||||
</a>
|
||||
<a href="/educators/">
|
||||
<FormattedMessage
|
||||
id='intro.forEducators'
|
||||
defaultMessage='FOR EDUCATORS' />
|
||||
</a>
|
||||
<a className="last" href="/parents/">
|
||||
<FormattedMessage
|
||||
id='intro.forParents'
|
||||
defaultMessage='FOR PARENTS' />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="video">
|
||||
|
@ -82,12 +137,17 @@ module.exports = React.createClass({
|
|||
<img src="//cdn.scratch.mit.edu/scratchr2/static/images/hp-video-screenshot.png" />
|
||||
</div>
|
||||
<Modal
|
||||
className="video-modal"
|
||||
isOpen={this.state.videoOpen}
|
||||
onRequestClose={this.closeVideo}>
|
||||
<iframe src="//player.vimeo.com/video/65583694?title=0&byline=0&portrait=0" />
|
||||
className="video-modal"
|
||||
isOpen={this.state.videoOpen}
|
||||
onRequestClose={this.closeVideo}
|
||||
style={{content:frameProps}}>
|
||||
<iframe
|
||||
src="//player.vimeo.com/video/65583694?title=0&byline=0&portrait=0"
|
||||
{...omit(frameProps, 'padding')} />
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Intro;
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
@import "../../colors";
|
||||
|
||||
.intro {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 30px;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
align-content: flex-start;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.content {
|
||||
display: inline-block;
|
||||
width: calc(66% - 20px);
|
||||
|
||||
h1 {
|
||||
color: #F9A739;
|
||||
color: $ui-orange;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
@ -20,13 +22,15 @@
|
|||
.sprites {
|
||||
position: relative;
|
||||
clear: both;
|
||||
margin: 20px 0;
|
||||
overflow: hidden;
|
||||
|
||||
&:after {
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
content: " ";
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
height: 0;
|
||||
content: " ";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,13 +41,15 @@
|
|||
height: 136px;
|
||||
overflow: hidden;
|
||||
|
||||
.costume, .circle, .text {
|
||||
.costume,
|
||||
.circle,
|
||||
.text {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.costume {
|
||||
z-index: 2;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.costume-2 {
|
||||
|
@ -59,144 +65,162 @@
|
|||
}
|
||||
|
||||
.circle {
|
||||
z-index: 0;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
width: 112px;
|
||||
height: 112px;
|
||||
box-shadow: 0px 0px 5px #fff;
|
||||
top: 15px;
|
||||
left: 43px;
|
||||
z-index: 0;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 5px $ui-white;
|
||||
width: 112px;
|
||||
height: 112px;
|
||||
}
|
||||
|
||||
$text-bg-color: #F1F3F4;
|
||||
.text {
|
||||
$text-bg-color: $background-color;
|
||||
left: 35px;
|
||||
z-index: 1;
|
||||
border: 2px solid $text-bg-color;
|
||||
background-color: $text-bg-color;
|
||||
padding-right: 10px;
|
||||
padding-left: 40px;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
left: 35px;
|
||||
padding-left: 40px;
|
||||
padding-right: 10px;
|
||||
white-space: nowrap;
|
||||
background-color: $text-bg-color;
|
||||
border: 2px solid $text-bg-color;
|
||||
}
|
||||
|
||||
.subtext {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
font-weight: 400;
|
||||
text-shadow: 0;
|
||||
font-size: 12px;
|
||||
text-shadow: none;
|
||||
border: none;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
|
||||
&.sprite-1 .circle {
|
||||
background-color: $splash-green;
|
||||
}
|
||||
|
||||
&.sprite-2 .circle {
|
||||
background-color: $splash-pink;
|
||||
}
|
||||
|
||||
&.sprite-3 .circle {
|
||||
background-color: $splash-blue;
|
||||
}
|
||||
|
||||
&:hover.sprite-1 .circle {
|
||||
box-shadow: 0 0 10px 2px $splash-green;
|
||||
}
|
||||
|
||||
&:hover.sprite-2 .circle {
|
||||
box-shadow: 0 0 10px 2px $splash-pink;
|
||||
}
|
||||
|
||||
&:hover.sprite-3 .circle {
|
||||
box-shadow: 0 0 10px 2px $splash-blue;
|
||||
}
|
||||
|
||||
&.sprite-1 .text {
|
||||
top: 60px;
|
||||
left: 50px;
|
||||
color: $splash-green;
|
||||
}
|
||||
|
||||
&.sprite-2 .text {
|
||||
top: 77px;
|
||||
left: 50px;
|
||||
color: $splash-pink;
|
||||
}
|
||||
|
||||
&.sprite-3 .text {
|
||||
top: 37px;
|
||||
left: 45px;
|
||||
color: $splash-blue;
|
||||
}
|
||||
|
||||
$sprite-1-bgcolor: #9C0;
|
||||
$sprite-2-bgcolor: #C2479D;
|
||||
$sprite-3-bgcolor: #199ED7;
|
||||
&.sprite-1 .circle { background-color: $sprite-1-bgcolor; }
|
||||
&.sprite-2 .circle { background-color: $sprite-2-bgcolor; }
|
||||
&.sprite-3 .circle { background-color: $sprite-3-bgcolor; }
|
||||
&:hover.sprite-1 .circle { box-shadow: 0 0 10px 2px $sprite-1-bgcolor; }
|
||||
&:hover.sprite-2 .circle { box-shadow: 0 0 10px 2px $sprite-2-bgcolor; }
|
||||
&:hover.sprite-3 .circle { box-shadow: 0 0 10px 2px $sprite-3-bgcolor; }
|
||||
&.sprite-1 .text { color: $sprite-1-bgcolor; top: 60px; left: 50px; }
|
||||
&.sprite-2 .text { color: $sprite-2-bgcolor; top: 77px; left: 50px; }
|
||||
&.sprite-3 .text { color: $sprite-3-bgcolor; top: 37px; left: 45px; }
|
||||
&.sprite-3 .subtext {
|
||||
top: 63px;
|
||||
left: 60px;
|
||||
color: #fff;
|
||||
color: $ui-white;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 17px;
|
||||
margin-top: 10px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.project-count {
|
||||
color: hsl(318, 50%, 52%);
|
||||
font-weight: 700;
|
||||
$project-count-color: hsl(318, 50%, 52%);
|
||||
color: $project-count-color;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.links {
|
||||
font-size: 12px;
|
||||
margin-top: 20px;
|
||||
letter-spacing: .5px;
|
||||
font-size: 12px;
|
||||
|
||||
a {
|
||||
border-right: 1px solid #000;
|
||||
border-right: 1px solid $type-gray;
|
||||
padding: 0 5px;
|
||||
|
||||
&:last-child { border-right: 0; }
|
||||
|
||||
&:first-child { padding-left: 0; }
|
||||
}
|
||||
}
|
||||
|
||||
.video {
|
||||
display: inline-block;
|
||||
height: 208px;
|
||||
width: 34%;
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 5px;
|
||||
background-color: #f7f7f7;
|
||||
border: 1px solid $ui-border;
|
||||
border-radius: 10px;
|
||||
background-color: $ui-white;
|
||||
padding: 14px 10px;
|
||||
width: 34%;
|
||||
height: 208px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 3px;
|
||||
|
||||
img {
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.play-button {
|
||||
border-radius: 20px;
|
||||
display: block;
|
||||
top: calc(50% - 25px);
|
||||
left: calc(50% - 35px);
|
||||
opacity: .8;
|
||||
border: 5px solid $ui-border;
|
||||
border-radius: 20px;
|
||||
background-color: $type-gray;
|
||||
width: 70px;
|
||||
height: 50px;
|
||||
left: calc(50% - 35px);
|
||||
top: calc(50% - 25px);
|
||||
background-color: #666;
|
||||
border: 5px solid #ccc;
|
||||
opacity: 0.8;
|
||||
|
||||
&, &:after {
|
||||
&,
|
||||
&:after {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&:after {
|
||||
left: 28px;
|
||||
border: solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
pointer-events: none;
|
||||
border-color: rgba(255, 255, 255, 0);
|
||||
border-left-color: #fff;
|
||||
border-width: 18px;
|
||||
$play-arrow: rgba(255, 255, 255, 0);
|
||||
top: 37px;
|
||||
left: 28px;
|
||||
margin-top: -30px;
|
||||
border: solid transparent;
|
||||
border-width: 18px;
|
||||
border-color: $play-arrow;
|
||||
border-left-color: $ui-white;
|
||||
width: 0;
|
||||
height: 0;
|
||||
content: " ";
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.video-modal {
|
||||
$video-width: 570px;
|
||||
$video-height: 357px;
|
||||
$padding: 15px;
|
||||
width: $video-width;
|
||||
height: $video-height;
|
||||
padding: $padding;
|
||||
top: 50%;
|
||||
bottom: auto;
|
||||
left: 50%;
|
||||
right: auto;
|
||||
margin-left: -($video-width + $padding * 2)/2;
|
||||
margin-top: -($video-height + $padding * 2)/2;
|
||||
|
||||
iframe {
|
||||
width: $video-width;
|
||||
height: $video-height;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
|
51
src/components/languagechooser/languagechooser.jsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
|
||||
var Api = require('../../mixins/api.jsx');
|
||||
var languages = require('../../../languages.json');
|
||||
var Select = require('../forms/select.jsx');
|
||||
|
||||
require('./languagechooser.scss');
|
||||
|
||||
var LanguageChooser = React.createClass({
|
||||
type: 'LanguageChooser',
|
||||
mixins: [
|
||||
Api
|
||||
],
|
||||
getInitialState: function () {
|
||||
return {
|
||||
choice: window._locale
|
||||
};
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
languages: languages
|
||||
};
|
||||
},
|
||||
onSetLanguage: function (e) {
|
||||
e.preventDefault();
|
||||
this.setState({'choice': e.target.value});
|
||||
ReactDOM.findDOMNode(this.refs.languageForm).submit();
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'language-chooser',
|
||||
this.props.className
|
||||
);
|
||||
|
||||
return (
|
||||
<form ref="languageForm" className={classes} action="/i18n/setlang/" method="POST">
|
||||
<Select name="language" defaultValue={this.state.choice} onChange={this.onSetLanguage}>
|
||||
{Object.keys(this.props.languages).map(function (value) {
|
||||
return <option value={value} key={value}>
|
||||
{this.props.languages[value]}
|
||||
</option>;
|
||||
}.bind(this))}
|
||||
</Select>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = LanguageChooser;
|
3
src/components/languagechooser/languagechooser.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
.language-chooser {
|
||||
|
||||
}
|
|
@ -1,29 +1,60 @@
|
|||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||
|
||||
var Input = require('../forms/input.jsx');
|
||||
var Button = require('../forms/button.jsx');
|
||||
|
||||
require('./login.scss');
|
||||
|
||||
module.exports = React.createClass({
|
||||
var Login = React.createClass({
|
||||
type: 'Login',
|
||||
propTypes: {
|
||||
onLogIn: React.PropTypes.func
|
||||
onLogIn: React.PropTypes.func,
|
||||
error: React.PropTypes.string
|
||||
},
|
||||
handleSubmit: function (event) {
|
||||
event.preventDefault();
|
||||
this.props.onLogIn();
|
||||
this.props.onLogIn({
|
||||
'username': ReactDOM.findDOMNode(this.refs.username).value,
|
||||
'password': ReactDOM.findDOMNode(this.refs.password).value
|
||||
});
|
||||
},
|
||||
render: function () {
|
||||
var error;
|
||||
if (this.props.error) {
|
||||
error = <div className="error">{this.props.error}</div>;
|
||||
}
|
||||
return (
|
||||
<div className="login">
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<label htmlFor="username">Username</label>
|
||||
<Input type="text" name="username" maxLength="30" />
|
||||
<label htmlFor="password">Password</label>
|
||||
<Input type="password" name="password" />
|
||||
<Button className="submit-button white" type="submit">Sign in</Button>
|
||||
<a className="right" href="/accounts/password_reset/">Forgot password?</a>
|
||||
<label htmlFor="username">
|
||||
<FormattedMessage
|
||||
id='general.username'
|
||||
defaultMessage={'Username'} />
|
||||
</label>
|
||||
<Input type="text" ref="username" name="username" maxLength="30" />
|
||||
<label htmlFor="password">
|
||||
<FormattedMessage
|
||||
id='general.password'
|
||||
defaultMessage={'Password'} />
|
||||
</label>
|
||||
<Input type="password" ref="password" name="password" />
|
||||
<Button className="submit-button white" type="submit">
|
||||
<FormattedMessage
|
||||
id='general.signIn'
|
||||
defaultMessage={'Sign in'} />
|
||||
</Button>
|
||||
<a className="right" href="/accounts/password_reset/">
|
||||
<FormattedMessage
|
||||
id='login.forgotPassword'
|
||||
defaultMessage={'Forgot Password?'} />
|
||||
</a>
|
||||
{error}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Login;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import "../../colors";
|
||||
|
||||
.login {
|
||||
padding: 10px;
|
||||
|
||||
|
@ -15,6 +17,13 @@
|
|||
}
|
||||
|
||||
a:hover {
|
||||
background-color: transparent !important;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.error {
|
||||
border: 1px solid $active-dark-gray;
|
||||
border-radius: 5px;
|
||||
background-color: $ui-orange;
|
||||
padding: .75em 1em;
|
||||
}
|
||||
}
|
|
@ -1,22 +1,60 @@
|
|||
var clone = require('lodash.clone');
|
||||
var defaultsDeep = require('lodash.defaultsdeep');
|
||||
var React = require('react');
|
||||
var Modal = require('react-modal');
|
||||
var ReactModal = require('react-modal');
|
||||
|
||||
require('./modal.scss');
|
||||
|
||||
var defaultStyle = {
|
||||
overlay: {
|
||||
zIndex: 100,
|
||||
backgroundColor: 'rgba(0, 0, 0, .75)'
|
||||
},
|
||||
content: {
|
||||
overflow: 'visible',
|
||||
borderRadius: '6px',
|
||||
width: 500,
|
||||
height: 250,
|
||||
padding: 0,
|
||||
top: '50%',
|
||||
right: 'auto',
|
||||
bottom: 'auto',
|
||||
left: '50%',
|
||||
marginTop: -125,
|
||||
marginLeft: -250
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = React.createClass({
|
||||
var Modal = React.createClass({
|
||||
type: 'Modal',
|
||||
statics: {
|
||||
setAppElement: Modal.setAppElement
|
||||
setAppElement: ReactModal.setAppElement
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
style: defaultStyle
|
||||
};
|
||||
},
|
||||
calculateStyle: function () {
|
||||
var style = clone(this.props.style, true);
|
||||
defaultsDeep(style, defaultStyle);
|
||||
style.content.marginTop = (style.content.height + style.content.padding*2) / -2;
|
||||
style.content.marginLeft = (style.content.width + style.content.padding*2) / -2;
|
||||
return style;
|
||||
},
|
||||
requestClose: function () {
|
||||
return this.refs.modal.portal.requestClose();
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Modal ref="modal" {... this.props}>
|
||||
<ReactModal ref="modal"
|
||||
{...this.props}
|
||||
style={this.calculateStyle()}>
|
||||
<div className="modal-close" onClick={this.requestClose}></div>
|
||||
{this.props.children}
|
||||
</Modal>
|
||||
</ReactModal>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Modal;
|
||||
|
|
|
@ -1,49 +1,29 @@
|
|||
/* Copied from the un-styleable react-modal */
|
||||
@import "../../colors";
|
||||
|
||||
.ReactModal__Overlay {
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
z-index: 100;
|
||||
}
|
||||
.ReactModal__Content {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
right: 40px;
|
||||
bottom: 40px;
|
||||
background: #fff;
|
||||
overflow: visible;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
padding: 20px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.ReactModal__Content {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
&.ReactModal__Content {
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
$modal-close-size: 20px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border-radius: $modal-close-size/2;
|
||||
border: 2px solid #ddd;
|
||||
background-color: #666;
|
||||
color: #fff;
|
||||
width: $modal-close-size;
|
||||
height: $modal-close-size;
|
||||
right: 0;
|
||||
margin-top: -$modal-close-size/2;
|
||||
margin-right: -$modal-close-size/2;
|
||||
border: 2px solid $ui-border;
|
||||
border-radius: $modal-close-size/2;
|
||||
background-color: $active-dark-gray;
|
||||
cursor: pointer;
|
||||
width: $modal-close-size;
|
||||
height: $modal-close-size;
|
||||
text-align: center;
|
||||
line-height: $modal-close-size;
|
||||
color: $type-white;
|
||||
font-size: $modal-close-size;
|
||||
cursor: pointer;
|
||||
|
||||
&:before {
|
||||
content: "x";
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
$base-background-color: #2aa3ef;
|
||||
$active-background-color: rgba(0, 0, 0, 0.1);
|
||||
$border-color: rgb(20, 154, 203);
|
|
@ -3,7 +3,8 @@ var classNames = require('classnames');
|
|||
|
||||
require('./dropdown.scss');
|
||||
|
||||
module.exports = React.createClass({
|
||||
var Dropdown = React.createClass({
|
||||
type: 'Dropdown',
|
||||
mixins: [
|
||||
require('react-onclickoutside')
|
||||
],
|
||||
|
@ -35,3 +36,5 @@ module.exports = React.createClass({
|
|||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Dropdown;
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
@import 'colors';
|
||||
@import "../../colors";
|
||||
|
||||
.dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
min-width: 240px;
|
||||
max-width: 260px;
|
||||
background-color: $base-background-color;
|
||||
overflow: visible;
|
||||
border: 1px solid $active-gray;
|
||||
border-radius: 0 0 5px 5px;
|
||||
background-color: $ui-blue;
|
||||
padding: 10px;
|
||||
color: white;
|
||||
min-width: 160px;
|
||||
max-width: 260px;
|
||||
overflow: visible;
|
||||
color: $type-white;
|
||||
font-size: .8125rem;
|
||||
font-weight: normal;
|
||||
font-size: 0.8125rem;
|
||||
border: 1px solid $active-background-color;
|
||||
|
||||
display: none;
|
||||
&.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
background-color: transparent;
|
||||
color: $type-white;
|
||||
}
|
||||
|
||||
input {
|
||||
// 100% minus border and padding
|
||||
width: calc(100% - 30px);
|
||||
margin-bottom: 12px;
|
||||
width: calc(100% - 30px);
|
||||
}
|
||||
|
||||
label {
|
||||
|
@ -40,8 +40,8 @@
|
|||
line-height: 30px;
|
||||
|
||||
&.divider {
|
||||
border-top: 1px solid #149acb;
|
||||
margin-top: 10px;
|
||||
border-top: 1px solid $active-gray;
|
||||
}
|
||||
|
||||
a {
|
||||
|
@ -49,8 +49,8 @@
|
|||
padding: 0 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: $active-background-color;
|
||||
text-decoration: none;
|
||||
background-color: $active-gray;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,22 +60,24 @@
|
|||
margin-top: $arrow-border-width;
|
||||
border-radius: 5px;
|
||||
overflow: visible;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -$arrow-border-width/2;
|
||||
right: 10%;
|
||||
|
||||
height: $arrow-border-width;
|
||||
width: $arrow-border-width;
|
||||
content: '';
|
||||
transform: rotate(45deg);
|
||||
|
||||
background-color: $base-background-color;
|
||||
border-top: 1px solid $active-background-color;
|
||||
border-left: 1px solid $active-background-color;
|
||||
border-top: 1px solid $active-gray;
|
||||
border-left: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
|
||||
background-color: $ui-blue;
|
||||
width: $arrow-border-width;
|
||||
height: $arrow-border-width;
|
||||
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,58 +1,223 @@
|
|||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
var ReactIntl = require('react-intl');
|
||||
var defineMessages = ReactIntl.defineMessages;
|
||||
var FormattedMessage = ReactIntl.FormattedMessage;
|
||||
var injectIntl = ReactIntl.injectIntl;
|
||||
var xhr = require('xhr');
|
||||
|
||||
var Api = require('../../mixins/api.jsx');
|
||||
var Avatar = require('../avatar/avatar.jsx');
|
||||
var Dropdown = require('./dropdown.jsx');
|
||||
var Input = require('../forms/input.jsx');
|
||||
var log = require('../../lib/log.js');
|
||||
var Login = require('../login/login.jsx');
|
||||
var Modal = require('../modal/modal.jsx');
|
||||
var Registration = require('../registration/registration.jsx');
|
||||
var Session = require('../../mixins/session.jsx');
|
||||
|
||||
require('./navigation.scss');
|
||||
|
||||
module.exports = React.createClass({
|
||||
Modal.setAppElement(document.getElementById('view'));
|
||||
|
||||
var defaultMessages = defineMessages({
|
||||
messages: {
|
||||
id: 'general.messages',
|
||||
defaultMessage: 'Messages'
|
||||
},
|
||||
myStuff: {
|
||||
id: 'general.myStuff',
|
||||
defaultMessage: 'My Stuff'
|
||||
}
|
||||
});
|
||||
|
||||
var Navigation = React.createClass({
|
||||
type: 'Navigation',
|
||||
mixins: [
|
||||
Api,
|
||||
Session
|
||||
],
|
||||
getInitialState: function () {
|
||||
return {
|
||||
'loginOpen': false,
|
||||
'loggedIn': false,
|
||||
'loggedInUser': {
|
||||
'username': 'raimondious',
|
||||
'thumbnail': '//cdn2.scratch.mit.edu/get_image/user/2584924_32x32.png'
|
||||
},
|
||||
'accountNavOpen': false
|
||||
accountNavOpen: false,
|
||||
canceledDeletionOpen: false,
|
||||
loginOpen: false,
|
||||
loginError: null,
|
||||
registrationOpen: false,
|
||||
unreadMessageCount: 0,
|
||||
messageCountIntervalId: -1
|
||||
};
|
||||
},
|
||||
componentDidMount: function () {
|
||||
if (this.state.session.user) {
|
||||
this.getMessageCount();
|
||||
var intervalId = setInterval(this.getMessageCount, 120000);
|
||||
this.setState({'messageCountIntervalId': intervalId});
|
||||
}
|
||||
},
|
||||
componentDidUpdate: function (prevProps, prevState) {
|
||||
if (prevState.session.user != this.state.session.user) {
|
||||
this.setState({
|
||||
'loginOpen': false,
|
||||
'accountNavOpen': false
|
||||
});
|
||||
if (this.state.session.user) {
|
||||
this.getMessageCount();
|
||||
var intervalId = setInterval(this.getMessageCount, 120000);
|
||||
this.setState({'messageCountIntervalId': intervalId});
|
||||
} else {
|
||||
// clear message count check, and set to default id.
|
||||
clearInterval(this.state.messageCountIntervalId);
|
||||
this.setState({'messageCountIntervalId': -1});
|
||||
}
|
||||
}
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
// clear message interval if it exists
|
||||
if (this.state.messageCountIntervalId != -1) {
|
||||
clearInterval(this.state.messageCountIntervalId);
|
||||
this.setState({'messageCountIntervalId': -1});
|
||||
}
|
||||
},
|
||||
getProfileUrl: function () {
|
||||
if (!this.state.session.user) return;
|
||||
return '/users/' + this.state.session.user.username + '/';
|
||||
},
|
||||
getMessageCount: function () {
|
||||
this.api({
|
||||
method: 'get',
|
||||
uri: '/proxy/users/' + this.state.session.user.username + '/activity/count'
|
||||
}, function (err, body) {
|
||||
if (body) {
|
||||
var count = parseInt(body.msg_count, this.state.unreadMessageCount);
|
||||
this.setState({'unreadMessageCount': count});
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
handleJoinClick: function (e) {
|
||||
e.preventDefault();
|
||||
this.setState({'registrationOpen': true});
|
||||
},
|
||||
handleLoginClick: function (e) {
|
||||
e.preventDefault();
|
||||
this.setState({'loginOpen': true});
|
||||
this.setState({'loginOpen': !this.state.loginOpen});
|
||||
},
|
||||
closeLogin: function () {
|
||||
this.setState({'loginOpen': false});
|
||||
},
|
||||
handleLogIn: function () {
|
||||
this.setState({'loggedIn': true});
|
||||
handleLogIn: function (formData) {
|
||||
this.setState({'loginError': null});
|
||||
formData['useMessages'] = true;
|
||||
this.api({
|
||||
method: 'post',
|
||||
host: '',
|
||||
uri: '/accounts/login/',
|
||||
json: formData,
|
||||
useCsrf: true
|
||||
}, function (err, body) {
|
||||
if (body) {
|
||||
body = body[0];
|
||||
if (!body.success) {
|
||||
if (body.redirect) {
|
||||
window.location = body.redirect;
|
||||
}
|
||||
this.setState({'loginError': body.msg});
|
||||
} else {
|
||||
this.closeLogin();
|
||||
body.messages.map(function (message) {
|
||||
if (message.message == 'canceled-deletion') {
|
||||
this.showCanceledDeletion();
|
||||
}
|
||||
}.bind(this));
|
||||
window.refreshSession();
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
handleLogOut: function () {
|
||||
this.setState({'loggedIn': false});
|
||||
handleLogOut: function (e) {
|
||||
e.preventDefault();
|
||||
xhr({
|
||||
host: '',
|
||||
uri: '/accounts/logout/'
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
log.error(err);
|
||||
} else {
|
||||
this.closeLogin();
|
||||
window.refreshSession();
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
handleClickAccountNav: function () {
|
||||
handleAccountNavClick: function (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 () {
|
||||
window.refreshSession();
|
||||
this.closeRegistration();
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames({
|
||||
'inner': true,
|
||||
'logged-in': this.state.loggedIn
|
||||
'logged-in': this.state.session.user
|
||||
});
|
||||
var messageClasses = classNames({
|
||||
'messageCount': true,
|
||||
'show': this.state.unreadMessageCount > 0
|
||||
});
|
||||
var formatMessage = this.props.intl.formatMessage;
|
||||
return (
|
||||
<div className={classes}>
|
||||
<ul>
|
||||
<li className="logo"><a href="/"></a></li>
|
||||
|
||||
<li className="link"><a href="/projects/editor">Create</a></li>
|
||||
<li className="link"><a href="/explore">Explore</a></li>
|
||||
<li className="link"><a href="/discuss">Discuss</a></li>
|
||||
<li className="link"><a href="/about">About</a></li>
|
||||
<li className="link"><a href="/help">Help</a></li>
|
||||
<li className="link create">
|
||||
<a href="/projects/editor">
|
||||
<FormattedMessage
|
||||
id="general.create"
|
||||
defaultMessage={'Create'} />
|
||||
</a>
|
||||
</li>
|
||||
<li className="link explore">
|
||||
<a href="/explore?date=this_month">
|
||||
<FormattedMessage
|
||||
id="general.explore"
|
||||
defaultMessage={'Explore'} />
|
||||
</a>
|
||||
</li>
|
||||
<li className="link discuss">
|
||||
<a href="/discuss">
|
||||
<FormattedMessage
|
||||
id="general.discuss"
|
||||
defaultMessage={'Discuss'} />
|
||||
</a>
|
||||
</li>
|
||||
<li className="link about">
|
||||
<a href="/about">
|
||||
<FormattedMessage
|
||||
id="general.about"
|
||||
defaultMessage={'About'} />
|
||||
</a>
|
||||
</li>
|
||||
<li className="link help">
|
||||
<a href="/help">
|
||||
<FormattedMessage
|
||||
id="general.help"
|
||||
defaultMessage={'Help'} />
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className="search">
|
||||
<form action="/search/google_results" method="get">
|
||||
|
@ -62,40 +227,108 @@ module.exports = React.createClass({
|
|||
<Input type="hidden" name="sort_by" value="datetime_shared" />
|
||||
</form>
|
||||
</li>
|
||||
{this.state.loggedIn ? [
|
||||
<li className="link right messages"><a href="/messages/" title="Messages">Messages</a></li>,
|
||||
<li className="link right mystuff"><a href="/mystuff/" title="My Stuff">My Stuff</a></li>,
|
||||
<li className="link right account-nav">
|
||||
<a className="userInfo" href="#" onClick={this.handleClickAccountNav}>
|
||||
<img src={this.state.loggedInUser.thumbnail} />
|
||||
{this.state.loggedInUser.username}
|
||||
{this.state.session.user ? [
|
||||
<li className="link right messages" key="messages">
|
||||
<a
|
||||
href="/messages/"
|
||||
title={formatMessage(defaultMessages.messages)}>
|
||||
|
||||
<span className={messageClasses}>{this.state.unreadMessageCount}</span>
|
||||
<FormattedMessage {...defaultMessages.messages} />
|
||||
</a>
|
||||
</li>,
|
||||
<li className="link right mystuff" key="mystuff">
|
||||
<a
|
||||
href="/mystuff/"
|
||||
title={formatMessage(defaultMessages.myStuff)}>
|
||||
|
||||
<FormattedMessage {...defaultMessages.myStuff} />
|
||||
</a>
|
||||
</li>,
|
||||
<li className="link right account-nav" key="account-nav">
|
||||
<a className="userInfo" href="#" onClick={this.handleAccountNavClick}>
|
||||
<Avatar src={this.state.session.user.thumbnailUrl} />
|
||||
{this.state.session.user.username}
|
||||
</a>
|
||||
<Dropdown
|
||||
as="ul"
|
||||
isOpen={this.state.accountNavOpen}
|
||||
onRequestClose={this.closeAccountNav}>
|
||||
<li><a href="/users/raimondious/">Profile</a></li>
|
||||
<li><a href="/mystuff/">My Stuff</a></li>
|
||||
<li><a href="/accounts/settings/">Account settings</a></li>
|
||||
<li>
|
||||
<a href={this.getProfileUrl()}>
|
||||
<FormattedMessage
|
||||
id='general.profile'
|
||||
defaultMessage={'Profile'} />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/mystuff/">
|
||||
<FormattedMessage {...defaultMessages.myStuff} />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/accounts/settings/">
|
||||
<FormattedMessage
|
||||
id='general.accountSettings'
|
||||
defaultMessage={'Account settings'} />
|
||||
</a>
|
||||
</li>
|
||||
<li className="divider">
|
||||
<a href="#" onClick={this.handleLogOut}>Sign out</a>
|
||||
<a href="#" onClick={this.handleLogOut}>
|
||||
<FormattedMessage
|
||||
id='navigation.signOut'
|
||||
defaultMessage={'Sign out'} />
|
||||
</a>
|
||||
</li>
|
||||
</Dropdown>
|
||||
</li>
|
||||
] : [
|
||||
<li className="link right join"><a href="/join">Join Scratch</a></li>,
|
||||
<li className="link right">
|
||||
<a href="#" onClick={this.handleLoginClick}>Sign In</a>
|
||||
<li className="link right join" key="join">
|
||||
<a href="#" onClick={this.handleJoinClick}>
|
||||
<FormattedMessage
|
||||
id='general.joinScratch'
|
||||
defaultMessage={'Join Scratch'} />
|
||||
</a>
|
||||
</li>,
|
||||
<Registration
|
||||
key="registration"
|
||||
isOpen={this.state.registrationOpen}
|
||||
onRequestClose={this.closeRegistration}
|
||||
onRegistrationDone={this.completeRegistration} />,
|
||||
<li className="link right login-item" key="login">
|
||||
<a
|
||||
href="#"
|
||||
onClick={this.handleLoginClick}
|
||||
className="ignore-react-onclickoutside">
|
||||
<FormattedMessage
|
||||
id='general.signIn'
|
||||
defaultMessage={'Sign In'} />
|
||||
</a>
|
||||
<Dropdown
|
||||
className="login-dropdown with-arrow"
|
||||
isOpen={this.state.loginOpen}
|
||||
onRequestClose={this.closeLogin}>
|
||||
<Login onLogIn={this.handleLogIn} />
|
||||
<Login
|
||||
onLogIn={this.handleLogIn}
|
||||
error={this.state.loginError} />
|
||||
</Dropdown>
|
||||
</li>
|
||||
]}
|
||||
</ul>
|
||||
<Modal isOpen={this.state.canceledDeletionOpen}
|
||||
onRequestClose={this.closeCanceledDeletion}
|
||||
style={{content:{padding: 15}}}>
|
||||
<h4>Your Account Will Not Be Deleted</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.
|
||||
</p>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = injectIntl(Navigation);
|
||||
|
|
|
@ -1,54 +1,60 @@
|
|||
@import 'colors';
|
||||
@import "../../colors";
|
||||
|
||||
#navigation {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: $base-background-color;
|
||||
z-index: 10;
|
||||
border-bottom: 1px solid $active-gray;
|
||||
|
||||
box-shadow: 0 0 3px $box-shadow-gray;
|
||||
background-color: $ui-blue;
|
||||
|
||||
|
||||
width: 100%;
|
||||
/* NOTE: Height should match offset settings in main.scss file */
|
||||
height: 50px;
|
||||
|
||||
.inner > ul {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
|
||||
height: 50px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 50px;
|
||||
list-style: none;
|
||||
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
|
||||
> li {
|
||||
display: inline-block;
|
||||
align-self: flex-start;
|
||||
position: relative;
|
||||
float: left;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-right: 10px;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
width: 81px;
|
||||
height: 50px;
|
||||
margin: 0px 6px 0 0;
|
||||
transition: .15s ease all;
|
||||
margin: 0 6px 0 0;
|
||||
border: 0;
|
||||
|
||||
border: none;
|
||||
background-image: url('/images/logo_sm.png');
|
||||
background-image: url("/images/logo_sm.png");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 95%;
|
||||
transition: .15s ease all;
|
||||
width: 81px;
|
||||
height: 50px;
|
||||
|
||||
&:hover {
|
||||
background-size: 100%;
|
||||
transition: .15s ease all;
|
||||
transition: .15s ease all;
|
||||
background-size: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,26 +62,26 @@
|
|||
.link {
|
||||
> a {
|
||||
display: block;
|
||||
padding: 17px 15px 0 15px;
|
||||
height: 33px;
|
||||
padding: 17px 15px 0px 15px;
|
||||
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 0.85rem;
|
||||
white-space: nowrap;
|
||||
color: $type-white;
|
||||
font-size: .85rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> a:hover {
|
||||
background-color: $active-background-color;
|
||||
background-color: $active-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
margin: 0 20px;
|
||||
border-right: 0;
|
||||
color: $type-white;
|
||||
flex-grow: 3;
|
||||
border-right: none;
|
||||
color: white;
|
||||
margin:0px 20px;
|
||||
|
||||
form {
|
||||
margin: 0;
|
||||
|
@ -83,44 +89,44 @@
|
|||
|
||||
input {
|
||||
display: inline-block;
|
||||
height: 14px;
|
||||
margin-top: 5px;
|
||||
outline: none;
|
||||
border: none;
|
||||
background-color: $active-background-color;
|
||||
margin-top:5px;
|
||||
border: 0;
|
||||
background-color: $active-gray;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
input[type=submit] {
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
background-color: transparent;
|
||||
|
||||
background-image: url('/images/nav-search-glass.png');
|
||||
background-size: 14px 14px;
|
||||
background-image: url("/images/nav-search-glass.png");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 14px 14px;
|
||||
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
transition: .15s ease background-color;
|
||||
padding: 0;
|
||||
padding-right: 10px;
|
||||
padding-left: 40px;
|
||||
width: calc(100% - 50px);
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
|
||||
color: white;
|
||||
padding-left: 40px;
|
||||
padding-right:10px;
|
||||
font-size: 0.85em;
|
||||
transition: .15s ease background-color;
|
||||
color: $type-white;
|
||||
font-size: .85em;
|
||||
|
||||
&::placeholder {
|
||||
color:rgba(255, 255, 255, 0.75);
|
||||
$placeholder-transparent: rgba(255, 255, 255, .75);
|
||||
color: $placeholder-transparent;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
transition: .15s ease background-color;
|
||||
background-color: $active-dark-gray;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,38 +136,62 @@
|
|||
}
|
||||
|
||||
.right {
|
||||
align-self: flex-end;
|
||||
float: right;
|
||||
margin-left: auto;
|
||||
align-self: flex-end;
|
||||
|
||||
a:hover {
|
||||
background-color: $active-background-color;
|
||||
background-color: $active-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.messages, .mystuff {
|
||||
.messages,
|
||||
.mystuff {
|
||||
> a {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
padding-left: 10px;
|
||||
background-size: 45%;
|
||||
padding-right: 10px;
|
||||
text-indent: 100%;
|
||||
white-space: nowrap;
|
||||
padding-left: 10px;
|
||||
width: 30px;
|
||||
overflow: hidden;
|
||||
text-indent: 50px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
> a:hover {
|
||||
background-size: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.messages {
|
||||
> a {
|
||||
background-image: url('/images/nav-notifications.png');
|
||||
width: 22px;
|
||||
background-image: url("/images/nav-notifications.png");
|
||||
}
|
||||
|
||||
.messageCount {
|
||||
display: none;
|
||||
|
||||
&.show {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: .5rem;
|
||||
right: .25rem;
|
||||
border-radius: 1rem;
|
||||
background-color: $ui-orange;
|
||||
padding: 0 .25rem;
|
||||
text-indent: 0;
|
||||
line-height: 1rem;
|
||||
color: $type-white;
|
||||
font-size: .7rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mystuff {
|
||||
> a {
|
||||
background-image: url('/images/mystuff.png');
|
||||
width: 25px;
|
||||
background-image: url("/images/mystuff.png");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,41 +201,41 @@
|
|||
|
||||
.account-nav {
|
||||
.userInfo {
|
||||
padding-top: 11px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
padding-top: 14px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
> a {
|
||||
font-size: .8125rem;
|
||||
font-weight: normal;
|
||||
font-size: 0.8125rem;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
.avatar {
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
border-radius: 3px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&:after {
|
||||
$caret-border-width: 4px;
|
||||
margin-left: $caret-border-width;
|
||||
border: $caret-border-width solid transparent;
|
||||
border-bottom-width: 0;
|
||||
border-top-color: white;
|
||||
content: " ";
|
||||
opacity: 0.5;
|
||||
vertical-align: middle;
|
||||
width: 0;
|
||||
height: 0;
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
|
||||
background-image: url("/images/dropdown.png");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
content: " ";
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
padding-top: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,25 @@
|
|||
var React = require('react');
|
||||
var ReactIntl = require('react-intl');
|
||||
var defineMessages = ReactIntl.defineMessages;
|
||||
var injectIntl = ReactIntl.injectIntl;
|
||||
|
||||
var Box = require('../box/box.jsx');
|
||||
|
||||
require('./news.scss');
|
||||
|
||||
module.exports = React.createClass({
|
||||
var defaultMessages = defineMessages({
|
||||
scratchNews: {
|
||||
id: 'news.scratchNews',
|
||||
defaultMessage: 'Scratch News'
|
||||
},
|
||||
viewAll: {
|
||||
id: 'general.viewAll',
|
||||
defaultMessage: 'View All'
|
||||
}
|
||||
});
|
||||
|
||||
var News = React.createClass({
|
||||
type: 'News',
|
||||
propTypes: {
|
||||
items: React.PropTypes.array
|
||||
},
|
||||
|
@ -14,11 +29,12 @@ module.exports = React.createClass({
|
|||
};
|
||||
},
|
||||
render: function () {
|
||||
var formatMessage = this.props.intl.formatMessage;
|
||||
return (
|
||||
<Box
|
||||
className="news"
|
||||
title="Scratch News"
|
||||
moreTitle="View All"
|
||||
title={formatMessage(defaultMessages.scratchNews)}
|
||||
moreTitle={formatMessage(defaultMessages.viewAll)}
|
||||
moreHref="/news">
|
||||
|
||||
<ul>
|
||||
|
@ -38,3 +54,5 @@ module.exports = React.createClass({
|
|||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = injectIntl(News);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import "../../colors";
|
||||
|
||||
.news {
|
||||
ul {
|
||||
display: block;
|
||||
|
@ -9,10 +11,10 @@
|
|||
|
||||
li {
|
||||
display: block;
|
||||
min-height: 53px;
|
||||
clear: both;
|
||||
margin: 0;
|
||||
padding: 12px 0;
|
||||
clear: both;
|
||||
min-height: 53px;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
|
@ -32,8 +34,8 @@
|
|||
h4 {
|
||||
display: block;
|
||||
|
||||
color: #1aa0d8;
|
||||
font-size: 0.85rem;
|
||||
color: $link-blue;
|
||||
font-size: .85rem;
|
||||
}
|
||||
|
||||
p {
|
||||
|
@ -41,13 +43,13 @@
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
color: #322f31;
|
||||
font-size: 0.85rem;
|
||||
color: $type-gray;
|
||||
font-size: .85rem;
|
||||
}
|
||||
}
|
||||
|
||||
li:nth-child(even) {
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-top: 1px solid $ui-border;
|
||||
border-bottom: 1px solid $ui-border;
|
||||
}
|
||||
}
|
||||
|
|
55
src/components/registration/registration.jsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
var React = require('react');
|
||||
var Modal = require('../modal/modal.jsx');
|
||||
|
||||
require('./registration.scss');
|
||||
|
||||
Modal.setAppElement(document.getElementById('view'));
|
||||
|
||||
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.refs.registrationIframe.contentWindow) return;
|
||||
if (e.data == 'registration-done') this.props.onRegistrationDone();
|
||||
if (e.data == 'registration-relaunch') {
|
||||
this.refs.registrationIframe.contentWindow.location.reload();
|
||||
}
|
||||
},
|
||||
toggleMessageListener: function (present) {
|
||||
if (present) {
|
||||
window.addEventListener('message', this.onMessage);
|
||||
} else {
|
||||
window.removeEventListener('message', this.onMessage);
|
||||
}
|
||||
},
|
||||
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 () {
|
||||
var frameProps = {
|
||||
width: 610,
|
||||
height: 438
|
||||
};
|
||||
return (
|
||||
<Modal
|
||||
isOpen={this.props.isOpen}
|
||||
onRequestClose={this.props.onRequestClose}
|
||||
className="registration"
|
||||
style={{content:frameProps}}>
|
||||
<iframe ref="registrationIframe" src="/accounts/standalone-registration/" {...frameProps} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Registration;
|
3
src/components/registration/registration.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
.registration {
|
||||
overflow: hidden;
|
||||
}
|
|
@ -1,28 +1,71 @@
|
|||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
|
||||
require('./thumbnail.scss');
|
||||
|
||||
module.exports = React.createClass({
|
||||
var Thumbnail = React.createClass({
|
||||
type: 'Thumbnail',
|
||||
propTypes: {
|
||||
src: React.PropTypes.string
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
href: '/projects/1000/',
|
||||
title: 'Example Project',
|
||||
src: 'http://www.lorempixel.com/144/108/',
|
||||
extra: 'by raimondious'
|
||||
href: '#',
|
||||
title: 'Project',
|
||||
src: '',
|
||||
type: 'project',
|
||||
showLoves: false,
|
||||
showRemixes: false
|
||||
};
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'thumbnail',
|
||||
this.props.type,
|
||||
this.props.className
|
||||
);
|
||||
var extra = [];
|
||||
if (this.props.creator) {
|
||||
extra.push(
|
||||
<div key="creator" className="thumbnail-creator">
|
||||
by <a href={'/users/' + this.props.creator + '/'}>{this.props.creator}</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
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.remixes && this.props.showRemixes) {
|
||||
extra.push(
|
||||
<div
|
||||
key="remixes"
|
||||
className="thumbnail-remixes"
|
||||
title={this.props.remixes + ' remixes'}>
|
||||
|
||||
{this.props.remixes}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={'thumbnail ' + this.props.className}>
|
||||
<div className={classes} >
|
||||
<a className="thumbnail-image" href={this.props.href}>
|
||||
<img src={this.props.src} />
|
||||
</a>
|
||||
<span className="thumbnail-title"><a href={this.props.href}>{this.props.title}</a></span>
|
||||
<span className="thumbnail-extra">{this.props.extra}</span>
|
||||
<div className="thumbnail-title">
|
||||
<a href={this.props.href}>{this.props.title}</a>
|
||||
</div>
|
||||
{extra}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Thumbnail;
|
||||
|
|
|
@ -1,33 +1,90 @@
|
|||
@import "../../colors";
|
||||
|
||||
.thumbnail {
|
||||
.thumbnail-image,
|
||||
.thumbnail-title,
|
||||
.thumbnail-extra {
|
||||
.thumbnail-image {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.thumbnail-image img {
|
||||
margin-bottom: 2px;
|
||||
border: 1px solid #ddd;
|
||||
border: 1px solid $ui-border;
|
||||
}
|
||||
|
||||
$extras: ".thumbnail-creator, .thumbnail-loves, .thumbnail-remixes";
|
||||
|
||||
.thumbnail-title,
|
||||
.thumbnail-extra {
|
||||
#{$extras} {
|
||||
line-height: normal;
|
||||
word-wrap: break-word;
|
||||
|
||||
a {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail-title {
|
||||
margin-bottom: 1px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
font-weight: 800;
|
||||
font-size: .9230em;
|
||||
font-weight: 800;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail-extra {
|
||||
#{$extras} {
|
||||
color: $type-gray;
|
||||
font-size: .8462em;
|
||||
color: #666;
|
||||
|
||||
a {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail-loves,
|
||||
.thumbnail-remixes {
|
||||
&:before {
|
||||
display: inline-block;
|
||||
margin-right: .1rem;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: contain;
|
||||
width: .93rem;
|
||||
height: .93rem;
|
||||
vertical-align: text-top;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail-loves:before {
|
||||
background-image: url("/svgs/love/love_type-gray.svg");
|
||||
}
|
||||
|
||||
.thumbnail-remixes:before {
|
||||
background-image: url("/svgs/remix/remix_type-gray.svg");
|
||||
}
|
||||
|
||||
&.project {
|
||||
$project-width: 144px;
|
||||
$project-height: 108px;
|
||||
width: $project-width;
|
||||
|
||||
img {
|
||||
width: $project-width;
|
||||
height: $project-height;
|
||||
}
|
||||
}
|
||||
|
||||
&.gallery {
|
||||
$gallery-width: 170px;
|
||||
$gallery-height: 100px;
|
||||
width: $gallery-width;
|
||||
|
||||
img {
|
||||
width: $gallery-width;
|
||||
height: $gallery-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
70
src/components/welcome/welcome.jsx
Normal file
|
@ -0,0 +1,70 @@
|
|||
var React = require('react');
|
||||
var ReactIntl = require('react-intl');
|
||||
|
||||
var injectIntl = ReactIntl.injectIntl;
|
||||
var FormattedMessage = ReactIntl.FormattedMessage;
|
||||
|
||||
var Box = require('../box/box.jsx');
|
||||
|
||||
require('./welcome.scss');
|
||||
|
||||
var Welcome = React.createClass({
|
||||
type: 'Welcome',
|
||||
propTypes: {
|
||||
onDismiss: React.PropTypes.func
|
||||
},
|
||||
render: function () {
|
||||
var formatMessage = this.props.intl.formatMessage;
|
||||
return (
|
||||
<Box title={formatMessage({id: 'welcome.welcomeToScratch', defaultMessage: 'Welcome to Scratch!'})}
|
||||
className="welcome"
|
||||
moreTitle="x"
|
||||
moreHref="#"
|
||||
moreProps={{
|
||||
className: 'close',
|
||||
title: 'Dismiss',
|
||||
onClick: this.props.onDismiss
|
||||
}}>
|
||||
|
||||
<div className="welcome-col blue">
|
||||
<h4>
|
||||
<a href="/projects/editor/?tip_bar=getStarted">
|
||||
<FormattedMessage
|
||||
id="welcome.learn"
|
||||
defaultMessage="Learn how to make a project in Scratch" />
|
||||
</a>
|
||||
</h4>
|
||||
<a href="/projects/editor/?tip_bar=getStarted">
|
||||
<img src="/images/welcome-learn.png" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="welcome-col green">
|
||||
<h4>
|
||||
<a href="/starter_projects/">
|
||||
<FormattedMessage
|
||||
id="welcome.tryOut"
|
||||
defaultMessage="Try out starter projects" />
|
||||
</a>
|
||||
</h4>
|
||||
<a href="/starter_projects/">
|
||||
<img src="/images/welcome-try.png" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="welcome-col pink">
|
||||
<h4>
|
||||
<a href="/studios/146521/">
|
||||
<FormattedMessage
|
||||
id="welcome.connect"
|
||||
defaultMessage="Connect with other Scratchers" />
|
||||
</a>
|
||||
</h4>
|
||||
<a href="/studios/146521/">
|
||||
<img src="/images/welcome-connect.png" />
|
||||
</a>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = injectIntl(Welcome);
|
61
src/components/welcome/welcome.scss
Normal file
|
@ -0,0 +1,61 @@
|
|||
@import "../../colors";
|
||||
|
||||
.welcome {
|
||||
.box-content {
|
||||
padding: 0;
|
||||
}
|
||||
.welcome-col {
|
||||
display: inline-block;
|
||||
margin: 10px 15px;
|
||||
width: 150px;
|
||||
height: 253px;
|
||||
|
||||
h4 {
|
||||
margin-top: 12px;
|
||||
padding: 0;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
> a {
|
||||
display: block;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 35px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
$color-bars: "h4:before, > a:after";
|
||||
|
||||
#{$color-bars} {
|
||||
display: block;
|
||||
margin: 10px 0;
|
||||
border-radius: 5px;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
content: "";
|
||||
}
|
||||
&.blue {
|
||||
#{$color-bars} {
|
||||
background-color: $splash-blue;
|
||||
}
|
||||
a {
|
||||
color: $splash-blue;
|
||||
}
|
||||
}
|
||||
&.green {
|
||||
#{$color-bars} {
|
||||
background-color: $splash-green;
|
||||
}
|
||||
a {
|
||||
color: $splash-green;
|
||||
}
|
||||
}
|
||||
&.pink {
|
||||
#{$color-bars} {
|
||||
background-color: $splash-pink;
|
||||
}
|
||||
a {
|
||||
color: $splash-pink;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
6
src/environment.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
var Environment = {
|
||||
NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||
API_HOST: JSON.stringify(process.env.API_HOST || 'https://api-staging.scratch.mit.edu')
|
||||
};
|
||||
|
||||
module.exports = Environment;
|
81
src/init.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
var api = require('./mixins/api.jsx').api;
|
||||
var jar = require('./lib/jar');
|
||||
|
||||
var translations = require('../locales/translations.json');
|
||||
|
||||
/**
|
||||
* -----------------------------------------------------------------------------
|
||||
* Session
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
(function () {
|
||||
window._session = {};
|
||||
|
||||
/**
|
||||
* Binds the object to private session variable and dispatches a global
|
||||
* "session" event.
|
||||
*
|
||||
* @param {object} Session object
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
window.updateSession = function (session) {
|
||||
window._session = session;
|
||||
var sessionEvent = new CustomEvent('session', session);
|
||||
window.dispatchEvent(sessionEvent);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a session object from the local proxy method. Calls "updateSession"
|
||||
* once session has been returned from the proxy.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
window.refreshSession = function () {
|
||||
api({
|
||||
host: '',
|
||||
uri: '/session/'
|
||||
}, function (err, body) {
|
||||
if (body.banned) {
|
||||
return window.location = body.redirectUrl;
|
||||
} else {
|
||||
window.updateSession(body);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Fetch session
|
||||
window.refreshSession();
|
||||
})();
|
||||
|
||||
/**
|
||||
* -----------------------------------------------------------------------------
|
||||
* 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');
|
||||
if (typeof obj === 'undefined') {
|
||||
obj = window.navigator.userLanguage || window.navigator.language;
|
||||
}
|
||||
if (typeof translations[obj] === 'undefined') {
|
||||
// Fall back on the split
|
||||
obj = obj.split('-')[0];
|
||||
}
|
||||
if (typeof translations[obj] === 'undefined') {
|
||||
// Language appears to not be supported – return `null`
|
||||
obj = null;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
window._locale = updateLocale() || 'en';
|
||||
window._translations = translations;
|
||||
})();
|
|
@ -7,7 +7,7 @@
|
|||
* Licensed under the MIT and GPL licenses.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
var Format = {
|
||||
date: function (stamp) {
|
||||
stamp = (stamp || '').replace(/-/g,'/').replace(/[TZ]/g,' ');
|
||||
|
||||
|
@ -30,3 +30,5 @@ module.exports = {
|
|||
day_diff < 31 && Math.ceil( day_diff / 7 ) + ' weeks ago';
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Format;
|
||||
|
|
41
src/lib/jar.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
var cookie = require('cookie');
|
||||
var xhr = require('xhr');
|
||||
|
||||
var Jar = {};
|
||||
|
||||
Jar.get = function (name, callback) {
|
||||
// Get cookie by name
|
||||
var obj = cookie.parse(document.cookie) || {};
|
||||
|
||||
// Handle optional callback
|
||||
if (typeof callback === 'function') {
|
||||
if (typeof obj === 'undefined') return callback('Cookie not found.');
|
||||
return callback(null, obj[name]);
|
||||
}
|
||||
|
||||
return obj[name];
|
||||
};
|
||||
|
||||
Jar.use = function (name, uri, callback) {
|
||||
// Attempt to get cookie
|
||||
Jar.get(name, function (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);
|
||||
Jar.get(name, callback);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Jar.set = function (name, value) {
|
||||
var obj = cookie.serialize(name, value);
|
||||
var expires = '; expires=' + new Date(new Date().setYear(new Date().getFullYear() + 1)).toUTCString();
|
||||
var path = '; path=/';
|
||||
document.cookie = obj + expires + path;
|
||||
};
|
||||
|
||||
module.exports = Jar;
|
4
src/lib/log.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
var minilog = require('minilog');
|
||||
minilog.enable();
|
||||
|
||||
module.exports = minilog('www');
|
25
src/lib/render.jsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
var ReactDOM = require('react-dom');
|
||||
var ReactIntl = require('react-intl');
|
||||
var IntlProvider = ReactIntl.IntlProvider;
|
||||
|
||||
var render = function (jsx, element) {
|
||||
// Get locale and messages from global namespace (see "init.js")
|
||||
var locale = window._locale;
|
||||
var messages = window._translations[locale];
|
||||
|
||||
// Render component
|
||||
var component = ReactDOM.render(
|
||||
<IntlProvider locale={locale} messages={messages}>
|
||||
{jsx}
|
||||
</IntlProvider>,
|
||||
element
|
||||
);
|
||||
|
||||
// If in production, provide list of rendered components
|
||||
if (process.env.NODE_ENV != 'production') {
|
||||
window._renderedComponents = window._renderedComponents || [];
|
||||
window._renderedComponents.push(component);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = render;
|
|
@ -1,9 +1,9 @@
|
|||
var React = require('react');
|
||||
var render = require('./lib/render.jsx');
|
||||
|
||||
require('./main.scss');
|
||||
|
||||
var Navigation = require('./components/navigation/navigation.jsx');
|
||||
var Footer = require('./components/footer/footer.jsx');
|
||||
|
||||
React.render(<Navigation />, document.getElementById('navigation'));
|
||||
React.render(<Footer />, document.getElementById('footer'));
|
||||
render(<Navigation />, document.getElementById('navigation'));
|
||||
render(<Footer />, document.getElementById('footer'));
|
||||
|
|
|
@ -1,39 +1,45 @@
|
|||
@import "colors";
|
||||
|
||||
/* Tags */
|
||||
html, body {
|
||||
html,
|
||||
body {
|
||||
display: block;
|
||||
margin: 0;
|
||||
background-color: darken($ui-blue, 8%);
|
||||
padding: 0;
|
||||
|
||||
color: #322f31;
|
||||
background-color: #fdfdfd;
|
||||
|
||||
color: $type-gray;
|
||||
font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
h1, h2, h3, h4 {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
|
||||
color: #554747;
|
||||
color: $header-gray;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.625rem;
|
||||
line-height: 2.125rem;
|
||||
font-size: 1.625rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.0rem;
|
||||
line-height: 1.1rem;
|
||||
font-size: 1.0rem;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
a:link, a:visited, a:active {
|
||||
color: #1aa0d8;
|
||||
a:link,
|
||||
a:visited,
|
||||
a:active {
|
||||
text-decoration: none;
|
||||
color: $link-blue;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
|
@ -42,14 +48,30 @@ a:hover {
|
|||
|
||||
/* Classes */
|
||||
.inner {
|
||||
width: 942px;
|
||||
margin: 0 auto;
|
||||
width: 942px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
$bg-blue: #d9edf7;
|
||||
$bg-blue-accent: #bce8f1;
|
||||
border: 1px solid $bg-blue-accent;
|
||||
border-radius: 5px;
|
||||
background-color: $bg-blue;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
line-height: 2rem;
|
||||
color: $type-gray;
|
||||
h4 {
|
||||
color: $type-gray;
|
||||
}
|
||||
}
|
||||
|
||||
#view {
|
||||
min-height: 768px;
|
||||
padding: 20px 0;
|
||||
|
||||
/* NOTE: Margin should match height in navigation.scss */
|
||||
margin-top: 50px;
|
||||
background-color: $background-color;
|
||||
|
||||
padding: 20px 0;
|
||||
min-height: 768px;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,51 @@
|
|||
var defaults = require('lodash.defaults');
|
||||
var xhr = require('xhr');
|
||||
|
||||
module.exports = {
|
||||
var jar = require('../lib/jar.js');
|
||||
var log = require('../lib/log.js');
|
||||
|
||||
var CookieMixinFactory = require('./cookieMixinFactory.jsx');
|
||||
|
||||
var Api = {
|
||||
mixins: [
|
||||
// Provides useScratchcsrftoken
|
||||
CookieMixinFactory('scratchcsrftoken', '/csrf_token/')
|
||||
],
|
||||
api: function (opts, callback) {
|
||||
xhr(opts, function (err, res, body) {
|
||||
if (err) {
|
||||
// emit global "error" event
|
||||
return callback(err);
|
||||
}
|
||||
// @todo Global error handler
|
||||
callback(err, body);
|
||||
defaults(opts, {
|
||||
host: process.env.API_HOST,
|
||||
headers: {},
|
||||
json: {},
|
||||
useCsrf: false
|
||||
});
|
||||
|
||||
defaults(opts.headers, {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
});
|
||||
|
||||
opts.uri = opts.host + opts.uri;
|
||||
|
||||
var apiRequest = function (opts) {
|
||||
xhr(opts, function (err, res, body) {
|
||||
if (err) log.error(err);
|
||||
callback(err, body);
|
||||
});
|
||||
}.bind(this);
|
||||
|
||||
if (typeof jar.get('scratchlanguage') !== 'undefined') {
|
||||
opts.headers['Accept-Language'] = jar.get('scratchlanguage') + ', en;q=0.8';
|
||||
}
|
||||
if (opts.useCsrf) {
|
||||
this.useScratchcsrftoken(function (err, csrftoken) {
|
||||
if (err) return log.error('Error while retrieving CSRF token', err);
|
||||
opts.json.csrftoken = csrftoken;
|
||||
opts.headers['X-CSRFToken'] = csrftoken;
|
||||
apiRequest(opts);
|
||||
}.bind(this));
|
||||
} else {
|
||||
apiRequest(opts);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Api;
|
||||
|
|
17
src/mixins/cookieMixinFactory.jsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
var jar = require('../lib/jar');
|
||||
|
||||
var cookieMixinFactory = function (cookieName, cookieSetter) {
|
||||
var capitalizedCookieName = cookieName.charAt(0).toUpperCase() + cookieName.slice(1);
|
||||
var getterName = 'get' + capitalizedCookieName;
|
||||
var userName = 'use' + capitalizedCookieName;
|
||||
var mixin = {};
|
||||
mixin[getterName] = function (callback) {
|
||||
jar.get(cookieName, callback);
|
||||
};
|
||||
mixin[userName] = function (callback) {
|
||||
jar.use(cookieName, cookieSetter, callback);
|
||||
};
|
||||
return mixin;
|
||||
};
|
||||
|
||||
module.exports = cookieMixinFactory;
|
|
@ -1,10 +1,18 @@
|
|||
module.exports = {
|
||||
var Session = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
session: {}
|
||||
session: window._session
|
||||
};
|
||||
},
|
||||
updateSession: function () {
|
||||
this.setState({'session': window._session});
|
||||
},
|
||||
componentWillMount: function () {
|
||||
// @todo Fetch session from API
|
||||
window.addEventListener('session', this.updateSession);
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
window.removeEventListener('session', this.updateSession);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Session;
|
||||
|
|
87
src/scripts/build-locales
Executable file
|
@ -0,0 +1,87 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/*
|
||||
Converts the existing .po translation files in the module to JSON files.
|
||||
Requires po2json in order to work. Takes as input a directory
|
||||
in which to store the resulting json translation files.
|
||||
*/
|
||||
|
||||
var fs = require('fs');
|
||||
var glob = require('glob');
|
||||
var path = require('path');
|
||||
var po2icu = require('po2icu');
|
||||
|
||||
/*
|
||||
Existing translations should be in the key value format specified by react-intl (i.e.
|
||||
formatted message id, with icu string as the value). New Translations should be in the
|
||||
format returned by po2icu (i.e. a source language icu string for key, and a localized
|
||||
language icu string for value).
|
||||
|
||||
ICU Map is an object in the reverse react-intl formatting (icu string as key), which will
|
||||
help determine if the translation belongs in www currently.
|
||||
*/
|
||||
var mergeNewTranslations = function (existingTranslations, newTranslations, icuMap) {
|
||||
for (var id in newTranslations) {
|
||||
if (icuMap.hasOwnProperty(id) && newTranslations[id].length > 0) {
|
||||
existingTranslations[icuMap[id]] = newTranslations[id];
|
||||
}
|
||||
}
|
||||
return existingTranslations;
|
||||
};
|
||||
|
||||
var args = process.argv.slice(2);
|
||||
|
||||
if (!args.length) {
|
||||
process.stdout.write('A destination directory must be specified.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var poUiDir = path.resolve(__dirname, '../../node_modules/scratchr2_translations/ui');
|
||||
var outputFile = path.resolve(__dirname, '../../', args[0]);
|
||||
// Create the directory if it doesn't exist.
|
||||
var fileInfo = path.parse(outputFile);
|
||||
try {
|
||||
fs.accessSync(fileInfo.dir, fs.F_OK);
|
||||
} catch (err) {
|
||||
// Doesn't exist – create it.
|
||||
fs.mkdirSync(fileInfo.dir);
|
||||
}
|
||||
|
||||
var icuTemplateFile = path.resolve(__dirname, '../../en.json');
|
||||
var idsWithICU = JSON.parse(fs.readFileSync(icuTemplateFile, 'utf8'));
|
||||
var icuWithIds = {};
|
||||
for (var id in idsWithICU) {
|
||||
icuWithIds[idsWithICU[id]] = id;
|
||||
}
|
||||
var locales = {
|
||||
en: idsWithICU
|
||||
};
|
||||
|
||||
// Get ui localization strings first
|
||||
glob(poUiDir + '/*', function (err, files) {
|
||||
if (err) throw new Error(err);
|
||||
|
||||
files.forEach(function (file) {
|
||||
var lang = file.split('/').pop();
|
||||
var jsFile = path.resolve(file, 'LC_MESSAGES/djangojs.po');
|
||||
var pyFile = path.resolve(file, 'LC_MESSAGES/django.po');
|
||||
|
||||
var translations = {};
|
||||
try {
|
||||
var jsTranslations = po2icu.poFileToICUSync(lang, jsFile);
|
||||
translations = mergeNewTranslations(translations, jsTranslations, icuWithIds);
|
||||
} catch (err) {
|
||||
process.stdout.write(lang + ': ' + err + '\n');
|
||||
}
|
||||
|
||||
try {
|
||||
var pyTranslations = po2icu.poFileToICUSync(lang, pyFile);
|
||||
translations = mergeNewTranslations(translations, pyTranslations, icuWithIds);
|
||||
} catch (err) {
|
||||
process.stdout.write(lang + ': ' + err + '\n');
|
||||
}
|
||||
|
||||
locales[lang] = translations;
|
||||
});
|
||||
fs.writeFileSync(outputFile, JSON.stringify(locales, null, 4));
|
||||
});
|
|
@ -1,8 +1,10 @@
|
|||
var React = require('react');
|
||||
var render = require('../../lib/render.jsx');
|
||||
|
||||
require('./about.scss');
|
||||
|
||||
var View = React.createClass({
|
||||
var About = React.createClass({
|
||||
type: 'About',
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
|
@ -12,4 +14,4 @@ var View = React.createClass({
|
|||
}
|
||||
});
|
||||
|
||||
React.render(<View />, document.getElementById('view'));
|
||||
render(<About />, document.getElementById('view'));
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
var React = require('react');
|
||||
var render = require('../../lib/render.jsx');
|
||||
|
||||
var Activity = require('../../components/activity/activity.jsx');
|
||||
var Box = require('../../components/box/box.jsx');
|
||||
var Button = require('../../components/forms/button.jsx');
|
||||
var Carousel = require('../../components/carousel/carousel.jsx');
|
||||
|
@ -8,7 +10,8 @@ var Input = require('../../components/forms/input.jsx');
|
|||
|
||||
require('./components.scss');
|
||||
|
||||
var View = React.createClass({
|
||||
var Components = React.createClass({
|
||||
type: 'Components',
|
||||
render: function () {
|
||||
return (
|
||||
<div className="inner">
|
||||
|
@ -30,9 +33,13 @@ var View = React.createClass({
|
|||
title="Carousel component in a box!">
|
||||
<Carousel />
|
||||
</Box>
|
||||
<h1>{'What\'s Happening??'}</h1>
|
||||
<Activity />
|
||||
<h1>{'Nothing!!!'}</h1>
|
||||
<Activity items={[]} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
React.render(<View />, document.getElementById('view'));
|
||||
render(<Components />, document.getElementById('view'));
|
||||
|
|
147
src/views/hoc/hoc.jsx
Normal file
|
@ -0,0 +1,147 @@
|
|||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
var render = require('../../lib/render.jsx');
|
||||
|
||||
require('./hoc.scss');
|
||||
|
||||
var Button = require('../../components/forms/button.jsx');
|
||||
var Box = require('../../components/box/box.jsx');
|
||||
|
||||
var Hoc = React.createClass({
|
||||
type: 'Hoc',
|
||||
|
||||
getInitialState: function () {
|
||||
return {
|
||||
bgClass: ''
|
||||
};
|
||||
},
|
||||
onCardEnter: function (bgClass) {
|
||||
this.setState({
|
||||
bgClass: bgClass
|
||||
});
|
||||
},
|
||||
render: function () {
|
||||
var classes = classNames(
|
||||
'top-banner',
|
||||
this.state.bgClass
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<div className={classes}>
|
||||
<h1>Get Creative with Coding</h1>
|
||||
<p>
|
||||
With Scratch, you can program your own stories, games, and animations —
|
||||
and share them online.
|
||||
</p>
|
||||
|
||||
<div className="card-deck">
|
||||
<div className="card">
|
||||
<a href="/projects/editor/?tip_bar=name">
|
||||
<div className="card-info" onMouseEnter={this.onCardEnter.bind(this, 'name-bg')}>
|
||||
<img src="/images/name-tutorial.jpg" />
|
||||
<Button>Animate Your Name</Button>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="card" onMouseEnter={this.onCardEnter.bind(this, 'wbb-bg')}>
|
||||
<a href="/hide">
|
||||
<div className="card-info">
|
||||
<img src="/images/hide-seek-tutorial.jpg" />
|
||||
<Button> Hide-and-Seek Game</Button>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="card" onMouseEnter={this.onCardEnter.bind(this, 'dance-bg')}>
|
||||
<a href="/projects/editor/?tip_bar=dance">
|
||||
<div className="card-info">
|
||||
<img src="/images/dance-tutorial.jpg" />
|
||||
<Button>Dance, Dance, Dance</Button>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul className="sub-nav">
|
||||
<li className="info">Find out more:</li>
|
||||
<a href="/about"><li className="link">About Scratch</li></a>
|
||||
<a href="/parents"><li className="link">For Parents</li></a>
|
||||
<a href="/educators"><li className="link">For Educators</li></a>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="inner">
|
||||
<Box>
|
||||
<section className="one-up">
|
||||
<div className="column">
|
||||
<h3>Activity Cards and Guides</h3>
|
||||
<p>
|
||||
Want tips and ideas for your Hour-of-Code activities?
|
||||
View and print activity cards and facilitator guides.
|
||||
<br />
|
||||
For more resources, see <a href="/help">Scratch Help</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="resource">
|
||||
<img src="/svgs/tips-card.svg" />
|
||||
<div className="resource-info">
|
||||
<h5>Animate Your Name</h5>
|
||||
<a href="#">Activity Cards</a>
|
||||
<a href="#">Facilitator Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="resource">
|
||||
<img src="/svgs/tips-card.svg" />
|
||||
<div className="resource-info">
|
||||
<h5>Hide-and-Seek</h5>
|
||||
<a href="#">Activity Cards</a>
|
||||
<a href="#">Facilitator Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="resource">
|
||||
<img src="/svgs/tips-card.svg" />
|
||||
<div className="resource-info">
|
||||
<h5>Dance, Dance, Dance</h5>
|
||||
<a href="#">Activity Cards</a>
|
||||
<a href="#">Facilitator Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="two-up">
|
||||
<div className="column">
|
||||
<h3>Tips Window</h3>
|
||||
<p>
|
||||
Need help getting started? Looking for ideas?
|
||||
You can find tutorials and helpful hints in the
|
||||
<br />
|
||||
<a href="/projects/editor/?tip_bar=home">Tips Window</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="column">
|
||||
<img src="/images/tips-test-animation.gif" />
|
||||
</div>
|
||||
</section>
|
||||
</Box>
|
||||
|
||||
<section className="one-up">
|
||||
<h3>Collaborators</h3>
|
||||
<div className="logos">
|
||||
<img src="/images/code-org-logo.png" />
|
||||
<img src="/images/cn-logo.png" />
|
||||
<img src="/images/paa-logo.png" />
|
||||
<img src="/images/pocketcode-logo.png" />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
render(<Hoc />, document.getElementById('view'));
|
245
src/views/hoc/hoc.scss
Normal file
|
@ -0,0 +1,245 @@
|
|||
@import "../../colors";
|
||||
|
||||
$base-bg: $ui-white;
|
||||
|
||||
#view {
|
||||
padding: 0;
|
||||
|
||||
// To be integrated into the Global Typography standards
|
||||
p {
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
// To be revamped in Global Grids standards
|
||||
.inner {
|
||||
margin: 0 auto;
|
||||
width: 80%;
|
||||
max-width: 960px;
|
||||
|
||||
.box {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.top-banner {
|
||||
transition: background-image .5s ease, background-color .5s ease;
|
||||
|
||||
margin-top: 10px;
|
||||
margin-bottom: 40px;
|
||||
|
||||
background-color: $ui-aqua;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
|
||||
padding: 10px 0;
|
||||
width: 100%;
|
||||
|
||||
&.wbb-bg {
|
||||
background-image: url("/images/hide-bg.jpg");
|
||||
}
|
||||
|
||||
&.dance-bg {
|
||||
background-image: url("/images/dance-bg.jpg");
|
||||
}
|
||||
|
||||
&.name-bg {
|
||||
background-image: url("/images/name-bg.jpg");
|
||||
}
|
||||
|
||||
h1,
|
||||
p {
|
||||
margin: 0 auto;
|
||||
padding-top: 10px;
|
||||
|
||||
max-width: 500px;
|
||||
|
||||
text-align: center;
|
||||
color: $type-white;
|
||||
}
|
||||
|
||||
.card-deck,
|
||||
.sub-nav {
|
||||
display: flex;
|
||||
margin: 20px auto;
|
||||
|
||||
width: 80%;
|
||||
max-width: 960px;
|
||||
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.card-deck {
|
||||
|
||||
.card {
|
||||
display: inline-block;
|
||||
margin: 10px;
|
||||
border-radius: 7px;
|
||||
background-color: $active-gray;
|
||||
padding: 2px;
|
||||
|
||||
width: 30%;
|
||||
min-width: 200px;
|
||||
max-width: 230px;
|
||||
|
||||
.card-info {
|
||||
border-radius: 5px;
|
||||
|
||||
background-color: $base-bg;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
|
||||
button,
|
||||
img {
|
||||
width: calc(100% - 20px);
|
||||
}
|
||||
|
||||
img {
|
||||
margin: 10px 10px 5px 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0 10px 10px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sub-nav {
|
||||
color: $type-white;
|
||||
font-size: .8em;
|
||||
font-weight: bold;
|
||||
|
||||
li {
|
||||
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
|
||||
padding: .75em 1em;
|
||||
|
||||
list-style-type: none;
|
||||
|
||||
}
|
||||
|
||||
a .link {
|
||||
|
||||
border: 2px solid $active-gray;
|
||||
|
||||
border-radius: 50px;
|
||||
|
||||
text-decoration: none;
|
||||
color: $type-white;
|
||||
|
||||
|
||||
&:hover {
|
||||
transition: background-color .25s ease;
|
||||
border-color: transparent;
|
||||
background-color: $active-gray;
|
||||
}
|
||||
|
||||
&:active {
|
||||
border: 0 solid transparent;
|
||||
box-shadow: inset 0 0 5px $box-shadow-gray;
|
||||
background-color: $active-dark-gray;
|
||||
padding: calc(.75em + 2px) calc(1em + 2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
border-bottom: 1px solid $ui-border;
|
||||
padding: 30px 0;
|
||||
width: 95%;
|
||||
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
h3,
|
||||
p {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.logos {
|
||||
margin: 10px 0;
|
||||
width: 100%;
|
||||
|
||||
img {
|
||||
margin: 0 20px;
|
||||
max-width: 200px;
|
||||
max-height: 75px;
|
||||
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.resource {
|
||||
display: flex;
|
||||
margin: 10px;
|
||||
border-radius: 5px;
|
||||
|
||||
padding: 10px 15px;
|
||||
width: 30%;
|
||||
min-width: 200px;
|
||||
max-width: 230px;
|
||||
text-align: left;
|
||||
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
h5 {
|
||||
margin: 8px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
margin: 5px 0;
|
||||
font-size: .8em;
|
||||
}
|
||||
}
|
||||
|
||||
&.one-up {
|
||||
text-align: center;
|
||||
|
||||
.column {
|
||||
margin: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.two-up {
|
||||
|
||||
.column {
|
||||
margin: 10px;
|
||||
|
||||
min-width: 200px;
|
||||
max-width: 40%;
|
||||
|
||||
img {
|
||||
border-radius: 5px;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +1,26 @@
|
|||
var injectIntl = require('react-intl').injectIntl;
|
||||
var omit = require('lodash.omit');
|
||||
var React = require('react');
|
||||
var render = require('../../lib/render.jsx');
|
||||
|
||||
var Api = require('../../mixins/api.jsx');
|
||||
var Session = require('../../mixins/session.jsx');
|
||||
|
||||
var Activity = require('../../components/activity/activity.jsx');
|
||||
var AdminPanel = require('../../components/adminpanel/adminpanel.jsx');
|
||||
var Banner = require('../../components/banner/banner.jsx');
|
||||
var Box = require('../../components/box/box.jsx');
|
||||
var Button = require('../../components/forms/button.jsx');
|
||||
var Carousel = require('../../components/carousel/carousel.jsx');
|
||||
var Intro = require('../../components/intro/intro.jsx');
|
||||
var Modal = require('../../components/modal/modal.jsx');
|
||||
var News = require('../../components/news/news.jsx');
|
||||
var Welcome = require('../../components/welcome/welcome.jsx');
|
||||
|
||||
require('./splash.scss');
|
||||
|
||||
var View = React.createClass({
|
||||
var Splash = injectIntl(React.createClass({
|
||||
type: 'Splash',
|
||||
mixins: [
|
||||
Api,
|
||||
Session
|
||||
|
@ -21,34 +30,336 @@ var View = React.createClass({
|
|||
projectCount: 10569070,
|
||||
activity: [],
|
||||
news: [],
|
||||
featured: require('./featured.json')
|
||||
featuredCustom: {},
|
||||
featuredGlobal: {},
|
||||
showEmailConfirmationModal: false
|
||||
};
|
||||
},
|
||||
componentDidUpdate: function (prevProps, prevState) {
|
||||
if (this.state.session.user != prevState.session.user) {
|
||||
if (this.state.session.user) {
|
||||
this.getActivity();
|
||||
this.getFeaturedCustom();
|
||||
this.getNews();
|
||||
} else {
|
||||
this.setState({featuredCustom: []});
|
||||
this.setState({activity: []});
|
||||
this.setState({news: []});
|
||||
this.getProjectCount();
|
||||
}
|
||||
if (this.shouldShowEmailConfirmation()) {
|
||||
window.addEventListener('message', this.onMessage);
|
||||
} else {
|
||||
window.removeEventListener('message', this.onMessage);
|
||||
}
|
||||
}
|
||||
},
|
||||
componentDidMount: function () {
|
||||
// @todo API request for News
|
||||
// @todo API request for Activity
|
||||
// @todo API request for Featured
|
||||
this.getFeaturedGlobal();
|
||||
if (this.state.session.user) {
|
||||
this.getActivity();
|
||||
this.getFeaturedCustom();
|
||||
this.getNews();
|
||||
} else {
|
||||
this.getProjectCount();
|
||||
}
|
||||
if (this.shouldShowEmailConfirmation()) window.addEventListener('message', this.onMessage);
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
window.removeEventListener('message', this.onMessage);
|
||||
},
|
||||
onMessage: function (e) {
|
||||
if (e.origin != window.location.origin) return;
|
||||
if (e.source != this.refs.emailConfirmationiFrame.contentWindow) return;
|
||||
if (e.data == 'resend-done') {
|
||||
this.hideEmailConfirmationModal();
|
||||
} else {
|
||||
var data = JSON.parse(e.data);
|
||||
if (data['action'] === 'leave-page') {
|
||||
window.location.href = data['uri'];
|
||||
}
|
||||
}
|
||||
},
|
||||
getActivity: function () {
|
||||
this.api({
|
||||
uri: '/proxy/users/' + this.state.session.user.username + '/activity?limit=5'
|
||||
}, function (err, body) {
|
||||
if (!err) this.setState({activity: body});
|
||||
}.bind(this));
|
||||
},
|
||||
getFeaturedGlobal: function () {
|
||||
this.api({
|
||||
uri: '/proxy/featured'
|
||||
}, function (err, body) {
|
||||
if (!err) this.setState({featuredGlobal: body});
|
||||
}.bind(this));
|
||||
},
|
||||
getFeaturedCustom: function () {
|
||||
this.api({
|
||||
uri: '/proxy/users/' + this.state.session.user.id + '/featured'
|
||||
}, function (err, body) {
|
||||
if (!err) this.setState({featuredCustom: body});
|
||||
}.bind(this));
|
||||
},
|
||||
getNews: function () {
|
||||
this.api({
|
||||
uri: '/news?limit=3'
|
||||
}, function (err, body) {
|
||||
if (!err) this.setState({news: body});
|
||||
}.bind(this));
|
||||
},
|
||||
getProjectCount: function () {
|
||||
this.api({
|
||||
uri: '/projects/count/all'
|
||||
}, function (err, body) {
|
||||
if (!err) this.setState({projectCount: body.count});
|
||||
}.bind(this));
|
||||
},
|
||||
showEmailConfirmationModal: function () {
|
||||
this.setState({emailConfirmationModalOpen: true});
|
||||
},
|
||||
hideEmailConfirmationModal: function () {
|
||||
this.setState({emailConfirmationModalOpen: false});
|
||||
},
|
||||
handleDismiss: function (cue) {
|
||||
this.api({
|
||||
host: '',
|
||||
uri: '/site-api/users/set-template-cue/',
|
||||
method: 'post',
|
||||
useCsrf: true,
|
||||
json: {cue: cue, value: false}
|
||||
}, function (err) {
|
||||
if (!err) window.refreshSession();
|
||||
});
|
||||
},
|
||||
shouldShowWelcome: function () {
|
||||
if (!this.state.session.user || !this.state.session.flags.show_welcome) return false;
|
||||
return (
|
||||
new Date(this.state.session.user.dateJoined) >
|
||||
new Date(new Date - 2*7*24*60*60*1000) // Two weeks ago
|
||||
);
|
||||
},
|
||||
shouldShowEmailConfirmation: function () {
|
||||
return (
|
||||
this.state.session.user && this.state.session.flags.has_outstanding_email_confirmation &&
|
||||
this.state.session.flags.confirm_email_banner);
|
||||
},
|
||||
renderHomepageRows: function () {
|
||||
var formatMessage = this.props.intl.formatMessage;
|
||||
|
||||
var rows = [
|
||||
<Box
|
||||
title={formatMessage({
|
||||
id: 'splash.featuredProjects',
|
||||
defaultMessage: 'Featured Projects'})}
|
||||
key="community_featured_projects">
|
||||
<Carousel items={this.state.featuredGlobal.community_featured_projects} />
|
||||
</Box>,
|
||||
<Box
|
||||
title={formatMessage({
|
||||
id: 'splash.featuredStudios',
|
||||
defaultMessage: 'Featured Studios'})}
|
||||
key="community_featured_studios">
|
||||
<Carousel items={this.state.featuredGlobal.community_featured_studios}
|
||||
settings={{slidesToShow: 4, slidesToScroll: 4, lazyLoad: false}} />
|
||||
</Box>
|
||||
];
|
||||
|
||||
if (this.state.featuredGlobal.curator_top_projects &&
|
||||
this.state.featuredGlobal.curator_top_projects.length > 4) {
|
||||
|
||||
rows.push(
|
||||
<Box
|
||||
key="curator_top_projects"
|
||||
title={
|
||||
'Projects Curated by ' +
|
||||
this.state.featuredGlobal.curator_top_projects[0].curator_name}
|
||||
moreTitle={formatMessage({id: 'general.learnMore', defaultMessage: 'Learn More'})}
|
||||
moreHref="/studios/386359/">
|
||||
<Carousel
|
||||
items={this.state.featuredGlobal.curator_top_projects} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.featuredGlobal.scratch_design_studio &&
|
||||
this.state.featuredGlobal.scratch_design_studio.length > 4) {
|
||||
|
||||
rows.push(
|
||||
<Box
|
||||
key="scratch_design_studio"
|
||||
title={
|
||||
formatMessage({
|
||||
id: 'splash.scratchDesignStudioTitle',
|
||||
defaultMessage: 'Scratch Design Studio' })
|
||||
+ ' - ' + this.state.featuredGlobal.scratch_design_studio[0].gallery_title}
|
||||
moreTitle={formatMessage({id: 'splash.visitTheStudio', defaultMessage: 'Visit the studio'})}
|
||||
moreHref={'/studios/' + this.state.featuredGlobal.scratch_design_studio[0].gallery_id + '/'}>
|
||||
<Carousel
|
||||
items={this.state.featuredGlobal.scratch_design_studio} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.session.user &&
|
||||
this.state.featuredGlobal.community_newest_projects &&
|
||||
this.state.featuredGlobal.community_newest_projects.length > 0) {
|
||||
|
||||
rows.push(
|
||||
<Box
|
||||
title={
|
||||
formatMessage({
|
||||
id: 'splash.recentlySharedProjects',
|
||||
defaultMessage: 'Recently Shared Projects' })}
|
||||
key="community_newest_projects">
|
||||
<Carousel
|
||||
items={this.state.featuredGlobal.community_newest_projects} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.featuredCustom.custom_projects_by_following &&
|
||||
this.state.featuredCustom.custom_projects_by_following.length > 0) {
|
||||
|
||||
rows.push(
|
||||
<Box title={
|
||||
formatMessage({
|
||||
id: 'splash.projectsByScratchersFollowing',
|
||||
defaultMessage: 'Projects by Scratchers I\'m Following'})}
|
||||
key="custom_projects_by_following">
|
||||
|
||||
<Carousel items={this.state.featuredCustom.custom_projects_by_following} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
if (this.state.featuredCustom.custom_projects_loved_by_following &&
|
||||
this.state.featuredCustom.custom_projects_loved_by_following.length > 0) {
|
||||
|
||||
rows.push(
|
||||
<Box title={
|
||||
formatMessage({
|
||||
id: 'splash.projectsLovedByScratchersFollowing',
|
||||
defaultMessage: 'Projects Loved by Scratchers I\'m Following'})}
|
||||
key="custom_projects_loved_by_following">
|
||||
|
||||
<Carousel items={this.state.featuredCustom.custom_projects_loved_by_following} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.featuredCustom.custom_projects_in_studios_following &&
|
||||
this.state.featuredCustom.custom_projects_in_studios_following.length > 0) {
|
||||
|
||||
rows.push(
|
||||
<Box title={
|
||||
formatMessage({
|
||||
id:'splash.projectsInStudiosFollowing',
|
||||
defaultMessage: 'Projects in Studios I\'m Following'})}
|
||||
key="custom_projects_in_studios_following">
|
||||
|
||||
<Carousel items={this.state.featuredCustom.custom_projects_in_studios_following} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
rows.push(
|
||||
<Box title={
|
||||
formatMessage({
|
||||
id: 'splash.communityRemixing',
|
||||
defaultMessage: 'What the Community is Remixing' })}
|
||||
key="community_most_remixed_projects">
|
||||
|
||||
<Carousel items={this.state.featuredGlobal.community_most_remixed_projects} showRemixes={true} />
|
||||
</Box>,
|
||||
<Box title={
|
||||
formatMessage({
|
||||
id: 'splash.communityLoving',
|
||||
defaultMessage: 'What the Community is Loving' })}
|
||||
key="community_most_loved_projects">
|
||||
|
||||
<Carousel items={this.state.featuredGlobal.community_most_loved_projects} showLoves={true} />
|
||||
</Box>
|
||||
);
|
||||
|
||||
return rows;
|
||||
},
|
||||
render: function () {
|
||||
var featured = this.renderHomepageRows();
|
||||
var emailConfirmationStyle = {width: 500, height: 330, padding: 1};
|
||||
return (
|
||||
<div className="inner">
|
||||
<Intro projectCount={this.state.projectCount} />
|
||||
<div className="splash-header">
|
||||
<Activity />
|
||||
<News />
|
||||
<div className="splash">
|
||||
{this.shouldShowEmailConfirmation() ? [
|
||||
<Banner key="confirmedEmail"
|
||||
className="warning"
|
||||
onRequestDismiss={this.handleDismiss.bind(this, 'confirmed_email')}>
|
||||
<a href="#" onClick={this.showEmailConfirmationModal}>Confirm your email</a>
|
||||
{' '}to enable sharing.{' '}
|
||||
<a href="/info/faq/#accounts">Having trouble?</a>
|
||||
</Banner>,
|
||||
<Modal key="emailConfirmationModal"
|
||||
isOpen={this.state.emailConfirmationModalOpen}
|
||||
onRequestClose={this.hideEmailConfirmationModal}
|
||||
style={{content: emailConfirmationStyle}}>
|
||||
<iframe ref="emailConfirmationiFrame"
|
||||
src="/accounts/email_resend_standalone/"
|
||||
{...omit(emailConfirmationStyle, 'padding')} />
|
||||
</Modal>
|
||||
] : []}
|
||||
<div key="inner" className="inner">
|
||||
{this.state.session.user ? [
|
||||
<div key="header" className="splash-header">
|
||||
{this.shouldShowWelcome() ? [
|
||||
<Welcome key="welcome" onDismiss={this.handleDismiss.bind(this, 'welcome')}/>
|
||||
] : [
|
||||
<Activity key="activity" items={this.state.activity} />
|
||||
]}
|
||||
<News items={this.state.news} />
|
||||
</div>
|
||||
] : [
|
||||
<Intro projectCount={this.state.projectCount} key="intro"/>
|
||||
]}
|
||||
|
||||
{featured}
|
||||
|
||||
<AdminPanel>
|
||||
<dt>Tools</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/scratch_admin/tickets">Ticket Queue</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/scratch_admin/ip-search/">IP Search</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/scratch_admin/email-search/">Email Search</a>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>Homepage Cache</dt>
|
||||
<dd>
|
||||
<ul className="cache-list">
|
||||
<li>
|
||||
<form
|
||||
id="homepage-refresh-form"
|
||||
method="post"
|
||||
action="/scratch_admin/homepage/clear-cache/">
|
||||
|
||||
<div className="button-row">
|
||||
<span>Refresh row data:</span>
|
||||
<Button type="submit">
|
||||
<span>Refresh</span>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</AdminPanel>
|
||||
</div>
|
||||
{this.state.featured.map(function (set) {
|
||||
return (
|
||||
<Box
|
||||
className="featured"
|
||||
title={set.title}>
|
||||
<Carousel items={set.items} />
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
React.render(<View />, document.getElementById('view'));
|
||||
render(<Splash />, document.getElementById('view'));
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
|
||||
.activity {
|
||||
.box {
|
||||
display: inline-block;
|
||||
width: calc(60% - 20px);
|
||||
}
|
||||
|
|
BIN
static/images/cn-logo.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
static/images/code-org-logo.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
static/images/dance-bg.jpg
Normal file
After Width: | Height: | Size: 114 KiB |
BIN
static/images/dance-tutorial.jpg
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
static/images/dropdown.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
static/images/hide-bg.jpg
Normal file
After Width: | Height: | Size: 193 KiB |
BIN
static/images/hide-seek-tutorial.jpg
Normal file
After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 1.4 KiB |
BIN
static/images/name-bg.jpg
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
static/images/name-tutorial.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.8 KiB |
BIN
static/images/og_image.jpg
Normal file
After Width: | Height: | Size: 212 KiB |
BIN
static/images/paa-logo.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
static/images/pocketcode-logo.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
static/images/tips-test-animation.gif
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
static/images/welcome-connect.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
static/images/welcome-learn.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
static/images/welcome-try.png
Normal file
After Width: | Height: | Size: 26 KiB |
719
static/js/lib/polyfill.min.js
vendored
42
static/js/lib/react-dom.js
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* ReactDOM v0.14.0
|
||||
*
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
// Based off https://github.com/ForbesLindesay/umd/blob/master/template.js
|
||||
;(function(f) {
|
||||
// CommonJS
|
||||
if (typeof exports === "object" && typeof module !== "undefined") {
|
||||
module.exports = f(require('react'));
|
||||
|
||||
// RequireJS
|
||||
} else if (typeof define === "function" && define.amd) {
|
||||
define(['react'], f);
|
||||
|
||||
// <script>
|
||||
} else {
|
||||
var g
|
||||
if (typeof window !== "undefined") {
|
||||
g = window;
|
||||
} else if (typeof global !== "undefined") {
|
||||
g = global;
|
||||
} else if (typeof self !== "undefined") {
|
||||
g = self;
|
||||
} else {
|
||||
// works providing we're not in "use strict";
|
||||
// needed for Java 8 Nashorn
|
||||
// see https://github.com/facebook/react/issues/3037
|
||||
g = this;
|
||||
}
|
||||
g.ReactDOM = f(g.React);
|
||||
}
|
||||
|
||||
})(function(React) {
|
||||
return React.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
||||
});
|