mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-27 01:25:52 -05:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
9d171a6880
278 changed files with 9745 additions and 2543 deletions
|
@ -5,3 +5,4 @@ intl/*
|
|||
locales/*
|
||||
**/*.min.js
|
||||
**/node_modules/*
|
||||
scratch-gui/*
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -28,3 +28,4 @@ ENV
|
|||
/test/integration-cypress/cypress/screenshots
|
||||
/test/integration-cypress/cypress/videos
|
||||
/test/integration-cypress/package-lock.json
|
||||
/test/integration/node_modules/*
|
||||
|
|
74
.travis.yml
74
.travis.yml
|
@ -18,10 +18,27 @@ env:
|
|||
- API_HOST_VAR=API_HOST_$TRAVIS_BRANCH
|
||||
- API_HOST=${!API_HOST_VAR}
|
||||
- API_HOST=${API_HOST:-$API_HOST_STAGING}
|
||||
# EB_AWS_ACCESS_KEY_ID
|
||||
- secure: A138rYuXDsOmpEwYxZ31WyXEeq5fgr9qyqsQh1nTFsjBKpFtNM+CN9e0QJQFT3PLs4wH/lWTRSyHxakxKQS1sxq828f9gHed+f15REKk/fRUplcCYIexT9xKVtU3D8CRNn/KBFWk75fZyZt20eyOVIv4h3pInKQz7y84J6PWzB1BCrAFvADrzS1X68Z3NJJLyxnz0YEurzz8mC2v4D0s/XifKTWvRtefD4QM6pE0C2iYyk+ThrLwg7i9FDHVfo0MrkgcdX7mz37SnTr7p7mHWnGXrGngi/NiDRQ+Uwwq/sr2UIww0rCwS1xsOcS//dC4NNqrrt1kUTsoC1Yt87Ny+gI0nUplsfEpdKajAkOYdANC5bJUGqPdSlOds1v9aJs9Hx48uGamWkm/3cFmoJ5uA2ZzUwbSGjTkWbnhwzT0YRvcLGhP1WE/EswaIyK5qMp522E79mP1yH6M750iUvi4N39+QW1BNX3ADkOwyAI67ArX5on5gWP83RXcJ15im7XsBpsmVn/KXi6AouWPb8jmSmKCj0QZCzfLY7ivM42IugYpK2NV7kFB38DpXQamJ5eskgwYa3elRmednIFUuwb1QDnONvJogVjk4CLmoSxssC2mJnnrUItM7l8G6As81GMI+6lTtl86hAuXBjUk60FMbgTAQDX9ll26LgpBy8jHSx8=
|
||||
# EB_AWS_SECRET_ACCESS_KEY
|
||||
- secure: EX1fyov+f6ytWN2ZSL4dLslwrVkp6Ho/uoSLO38/qNG3XdGmBN4VprxddcQiWfo+Mrg3GdWcfcM/VazhhStBi1uLfZiw3RHZaSGuWbiuD2EtzqtlC+OVvoajgy91QFajh9Zzuwa0rYbEPd/sw01R53NoWJYl0GSteWk7C8Wv6anl4FUJCqgvvTV2ZEcyTtGcVJgUhKi1MfNpTSM6JWBy0DWszcyxj7C8LSs1+l9ZjAtnlUBWY13HsrNu8G5d+FwqGHZLUAjdu2O602wxV897/xLARLduZ+01ALpVefNEEGMB1Wd+xMw4dm2B0Uk86a4TBRCeOgJZ1yoJoPpGPOHTo+dgNXcU8ReszGVoy7uOjFWwu82FQq8gzfcf75yzaRJgG8/BJ6BkJfa0EmFg3iO5CwixQyHR5+CqsedtoLAWVT8zlOfQ/Z6yx4Pm7jXQSOkyvo09YJ2QIn4IFGPvwOVS7Firzi+fLl8GYApeSV9G10e1IzA4pPrKdJMRA4qRMPt9zJGq7ZO1J/d9aW/5KIsJUDnodnl7yXJyDMOyNeljT9I82ciHZcURxRRY080vrW6dgNJE1V9jxBhWEvr2iCeWMMedWaGuC41I7K9L79eW8lmaE+cQ+OZrzpOJP4GbfmIiXrh+0M4ChL/xBpjtiFwpNdkCXXhzWMnjJ4wCrii4yuc=
|
||||
- ASSET_HOST_master=https://assets.scratch.mit.edu
|
||||
- ASSET_HOST_STAGING=https://assets.scratch.ly
|
||||
- ASSET_HOST_VAR=ASSET_HOST_$TRAVIS_BRANCH
|
||||
- ASSET_HOST=${!ASSET_HOST_VAR}
|
||||
- ASSET_HOST=${ASSET_HOST:-$ASSET_HOST_STAGING}
|
||||
- BACKPACK_HOST_master=https://backpack.scratch.mit.edu
|
||||
- BACKPACK_HOST_STAGING=https://backpack.scratch.ly
|
||||
- BACKPACK_HOST_VAR=BACKPACK_HOST_$TRAVIS_BRANCH
|
||||
- BACKPACK_HOST=${!BACKPACK_HOST_VAR}
|
||||
- BACKPACK_HOST=${BACKPACK_HOST:-$BACKPACK_HOST_STAGING}
|
||||
- ROOT_URL_master=https://scratch.mit.edu
|
||||
- ROOT_URL_STAGING=https://scratch.ly
|
||||
- ROOT_URL_VAR=ROOT_URL_$TRAVIS_BRANCH
|
||||
- ROOT_URL=${!ROOT_URL_VAR}
|
||||
- ROOT_URL=${ROOT_URL:-$ROOT_URL_STAGING}
|
||||
- PROJECT_HOST_master=https://projects.scratch.mit.edu
|
||||
- PROJECT_HOST_STAGING=https://projects.scratch.ly
|
||||
- PROJECT_HOST_VAR=PROJECT_HOST_$TRAVIS_BRANCH
|
||||
- PROJECT_HOST=${!PROJECT_HOST_VAR}
|
||||
- PROJECT_HOST=${PROJECT_HOST:-$PROJECT_HOST_STAGING}
|
||||
- PATH=$PATH:$PWD/test/integration/node_modules/chromedriver/bin
|
||||
- AWS_ACCESS_KEY_ID=$EB_AWS_ACCESS_KEY_ID
|
||||
- AWS_SECRET_ACCESS_KEY=$EB_AWS_SECRET_ACCESS_KEY
|
||||
- FASTLY_ACTIVATE_CHANGES=true
|
||||
|
@ -58,6 +75,7 @@ env:
|
|||
- NODE_ENV=production
|
||||
- WWW_VERSION=${TRAVIS_COMMIT:0:5}
|
||||
addons:
|
||||
chrome: stable
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
|
@ -66,20 +84,34 @@ addons:
|
|||
install:
|
||||
- sudo -H pip install -r requirements.txt
|
||||
- npm --production=false install
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: $SKIP_CLEANUP
|
||||
script: env make sync
|
||||
on:
|
||||
repo: LLK/scratch-www
|
||||
branch:
|
||||
- develop
|
||||
- hotfix/*
|
||||
- release/*
|
||||
- provider: script
|
||||
skip_cleanup: $SKIP_CLEANUP
|
||||
script: env make sync
|
||||
on:
|
||||
repo: LLK/scratch-www
|
||||
branch:
|
||||
- master
|
||||
- npm --production=false update
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: $SKIP_CLEANUP
|
||||
script: env make sync
|
||||
on:
|
||||
repo: LLK/scratch-www
|
||||
branch:
|
||||
- develop
|
||||
- hotfix/*
|
||||
- release/*
|
||||
- provider: script
|
||||
skip_cleanup: $SKIP_CLEANUP
|
||||
script: env make sync
|
||||
on:
|
||||
repo: LLK/scratch-www
|
||||
branch:
|
||||
- master
|
||||
- stage: smoke
|
||||
install:
|
||||
- cd test/integration
|
||||
- npm install
|
||||
- cd -
|
||||
script: npm run smoke-sauce
|
||||
stages:
|
||||
- test
|
||||
- name: smoke
|
||||
if: type != pull_request
|
||||
|
|
31
.tx/config
31
.tx/config
|
@ -128,14 +128,31 @@ source_file = src/views/messages/l10n.json
|
|||
source_lang = en
|
||||
type = KEYVALUEJSON
|
||||
|
||||
[scratch-website.conference-index-l10njson]
|
||||
file_filter = localizations/conference-index/<lang>.json
|
||||
source_file = src/views/conference/2018/index/l10n.json
|
||||
source_lang = en
|
||||
type = KEYVALUEJSON
|
||||
|
||||
[scratch-website.preview-faq-l10njson]
|
||||
file_filter = localizations/preview-faq/<lang>.json
|
||||
source_file = src/views/preview-faq/l10n.json
|
||||
source_lang = en
|
||||
type = KEYVALUEJSON
|
||||
type = KEYVALUEJSON
|
||||
|
||||
[scratch-website.research-l10njson]
|
||||
file_filter = localizations/research/<lang>.json
|
||||
source_file = src/views/research/l10n.json
|
||||
source_lang = en
|
||||
type = KEYVALUEJSON
|
||||
|
||||
[scratch-website.preview-l10njson]
|
||||
file_filter = localizations/preview/<lang>.json
|
||||
source_file = src/views/preview/l10n.json
|
||||
source_lang = en
|
||||
type = KEYVALUEJSON
|
||||
|
||||
[scratch-website.ev3-l10njson]
|
||||
source_file = src/views/ev3/l10n.json
|
||||
source_lang = en
|
||||
type = KEYVALUEJSON
|
||||
|
||||
[scratch-website.microbit-l10njson]
|
||||
source_file = src/views/microbit/l10n.json
|
||||
source_lang = en
|
||||
type = KEYVALUEJSON
|
||||
|
||||
|
|
10
Dockerfile
Normal file
10
Dockerfile
Normal file
|
@ -0,0 +1,10 @@
|
|||
FROM node:8
|
||||
|
||||
RUN mkdir -p /var/app/current
|
||||
WORKDIR /var/app/current
|
||||
COPY . ./
|
||||
RUN rm -rf ./node_modules
|
||||
RUN npm install
|
||||
|
||||
EXPOSE 8333
|
||||
|
9
Makefile
9
Makefile
|
@ -1,10 +1,10 @@
|
|||
ESLINT=./node_modules/.bin/eslint
|
||||
NODE=node
|
||||
NODE= NODE_OPTIONS=--max_old_space_size=8000 node
|
||||
SASSLINT=./node_modules/.bin/sass-lint -v
|
||||
S3CMD=s3cmd sync -P --delete-removed --add-header=Cache-Control:no-cache,public,max-age=3600
|
||||
TAP=./node_modules/.bin/tap
|
||||
WATCH=./node_modules/.bin/watch
|
||||
WEBPACK=./node_modules/.bin/webpack
|
||||
WATCH= NODE_OPTIONS=--max_old_space_size=8000 ./node_modules/.bin/watch
|
||||
WEBPACK= NODE_OPTIONS=--max_old_space_size=8000 ./node_modules/.bin/webpack
|
||||
|
||||
# ------------------------------------
|
||||
|
||||
|
@ -71,6 +71,9 @@ integration:
|
|||
smoke:
|
||||
$(TAP) ./test/integration/smoke-testing/*.js --timeout=3600
|
||||
|
||||
smoke-verbose:
|
||||
$(TAP) ./test/integration/smoke-testing/*.js --timeout=3600 -R spec
|
||||
|
||||
localization:
|
||||
$(TAP) ./test/localization/*.js
|
||||
|
||||
|
|
58
README.md
58
README.md
|
@ -9,7 +9,7 @@
|
|||
### Where am I?
|
||||
Physically? No idea.
|
||||
|
||||
Digitally? You’re at Scratch’s open source web client!
|
||||
Digitally? You’re at Scratch’s open source web client!
|
||||
|
||||
We’re working to update the [Scratch website](https://scratch.mit.edu) to use a new codebase, contained in this repository.
|
||||
|
||||
|
@ -23,7 +23,7 @@ We’re currently building Scratch using [React](https://facebook.github.io/reac
|
|||
|
||||
### Before Getting Started
|
||||
* Make sure you have node (v4.2 or higher) and npm [installed](https://docs.npmjs.com/getting-started/installing-node)
|
||||
We use npm (Node Package Manager) to maintain and update packages required to build the site.
|
||||
We use npm (Node Package Manager) to maintain and update packages required to build the site.
|
||||
|
||||
### Update Packages
|
||||
It's important to make sure that all of the dependencies are up to date because the scratch-www code only works with specific versions of the dependencies. You can update the packages by running this command:
|
||||
|
@ -81,14 +81,17 @@ To stop the process that is making the site available to your web browser (creat
|
|||
|
||||
`npm start` can be configured with the following environment variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
| ------------- | ----------------------------- | ---------------------------------------------- |
|
||||
| `API_HOST` | `https://api.scratch.mit.edu` | Hostname for API requests |
|
||||
| `SENTRY_DSN` | `''` | DSN for Sentry |
|
||||
| `FALLBACK` | `''` | Pass-through location for old site |
|
||||
| `GA_TRACKER` | `''` | Where to log Google Analytics data |
|
||||
| `NODE_ENV` | `null` | If not `production`, app acts like development |
|
||||
| `PORT` | `8333` | Port for devserver (http://localhost:XXXX) |
|
||||
| Variable | Default | Description |
|
||||
| --------------- | ---------------------------------- | ---------------------------------------------- |
|
||||
| `API_HOST` | `https://api.scratch.mit.edu` | Hostname for API requests |
|
||||
| `ASSETS_HOST` | `https://assets.scratch.mit.edu` | Hostname for asset requests |
|
||||
| `BACKPACK_HOST` | `https://backpack.scratch.mit.edu` | Hostname for backpack requests |
|
||||
| `PROJECTS_HOST` | `https://projects.scratch.mit.edu` | Hostname for project requests |
|
||||
| `SENTRY_DSN` | `''` | DSN for Sentry |
|
||||
| `FALLBACK` | `''` | Pass-through location for old site |
|
||||
| `GA_TRACKER` | `''` | Where to log Google Analytics data |
|
||||
| `NODE_ENV` | `null` | If not `production`, app acts like development |
|
||||
| `PORT` | `8333` | Port for devserver (http://localhost:XXXX) |
|
||||
|
||||
**NOTE:** Because by default `API_HOST=https://api.scratch.mit.edu`, please be aware that, by default, you will be seeing and interacting with real data on the Scratch website.
|
||||
|
||||
|
@ -98,6 +101,9 @@ npm test
|
|||
```
|
||||
|
||||
### To Deploy
|
||||
|
||||
Deploying to staging or production will upload code to S3 and configure Fastly.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
virtualenv ENV
|
||||
|
@ -132,3 +138,35 @@ Additionally, if you set `FALLBACK=https://scratch.mit.edu`, be aware that click
|
|||
|
||||
#### Windows
|
||||
Some users have experienced difficulties when trying to get our web client to work on Windows. One solution could be to use [Cygwin](https://www.cygwin.com/). If that doesn't work, you might want to use [Wubi](https://wiki.ubuntu.com/WubiGuide) (Windows XP, Vista, 7) or [Wubiuefi](https://github.com/hakuna-m/wubiuefi) (Windows 8 or higher). Wubi(uefi) is a Windows Installer for Ubuntu that allows you to have Ubuntu and Windows on one disk, without the need of an extra partition.
|
||||
|
||||
#### Docker
|
||||
|
||||
_This section is only relevant to the Scratch Team since it requires access to private repositories, so is not usable by 3rd party contributors._
|
||||
|
||||
A set of [Docker](https://www.docker.com/what-docker) related files are provided to create isolated [container](https://www.docker.com/what-container) environments suitable for end-to-end local development:
|
||||
|
||||
* Dockerfile
|
||||
* docker-compose.yml
|
||||
* docker_entrypoint.sh
|
||||
|
||||
##### Docker Quick Start (CLI)
|
||||
|
||||
Make sure you already have the Scratch REST API running locally in its docker environment.
|
||||
|
||||
```
|
||||
$ docker-compose up
|
||||
```
|
||||
|
||||
After this has launched you will be able to access a running copy of `scratch-www` on port 8333 via `http://localhost:8333`
|
||||
|
||||
##### Docker Configuration
|
||||
|
||||
`Dockerfile` defines how a `scratch-www` docker image is created.
|
||||
|
||||
`docker-compose.yml` takes care of launching `scratch-www` into a development environment that is composed of other components, such as the Scratch REST API server and the legacy Scratch code. If you have not already setup the Scratch REST API in your local environment, you will need to modify `docker-compose.yml` by removing `external: true` from:
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
scratchapi_scratch_network:
|
||||
external: true
|
||||
```
|
||||
|
|
|
@ -1,7 +1 @@
|
|||
The Scratch trademarks, including the Scratch name, logo, the Scratch Cat, Gobo, Pico, Nano, Tera and Giga graphics (the "Marks"), are property of the Massachusetts Institute of Technology (MIT), and the use of the Marks is governed by this policy.
|
||||
|
||||
You may use the Marks to refer to Scratch in Substantially Unmodified form.
|
||||
|
||||
"Substantially Unmodified" means the source code provided by MIT, possibly with minor modifications including but not limited to: bug fixes (including security), changing the locations of files for better integration with the host operating system, adding documentation, and changes to the dynamic linking of libraries.
|
||||
|
||||
A version is not "Substantially Unmodified" if it incorporates features not present in a release of Scratch by MIT. If you do make a substantial modification, to avoid confusion with versions of Scratch produced by MIT you must remove all Marks from your version of the software and refrain from using any of the Marks to refer to your version.
|
||||
The Scratch trademarks, including the Scratch name, logo, the Scratch Cat, Gobo, Pico, Nano, Tera and Giga graphics (the "Marks"), are property of the Massachusetts Institute of Technology (MIT). Marks may not be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
|
|
@ -176,7 +176,7 @@ async.auto({
|
|||
if (err) throw new Error(err);
|
||||
if (process.env.FASTLY_ACTIVATE_CHANGES) {
|
||||
fastly.activateVersion(results.version, function (e, resp) {
|
||||
if (err) throw new Error(e);
|
||||
if (e) throw new Error(e);
|
||||
process.stdout.write('Successfully configured and activated version ' + resp.number + '\n');
|
||||
if (process.env.FASTLY_PURGE_ALL) {
|
||||
fastly.purgeAll(FASTLY_SERVICE_ID, function (error) {
|
||||
|
|
|
@ -21,7 +21,18 @@ routes.forEach(route => {
|
|||
app.get(route.pattern, handler(route));
|
||||
});
|
||||
|
||||
app.use(webpackDevMiddleware(compiler));
|
||||
var middlewareOptions = {};
|
||||
if (process.env.USE_DOCKER_WATCHOPTIONS) {
|
||||
middlewareOptions = {
|
||||
watchOptions: {
|
||||
aggregateTimeout: 500,
|
||||
poll: 2500,
|
||||
ignored: ['node_modules', 'build']
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
app.use(webpackDevMiddleware(compiler, middlewareOptions));
|
||||
|
||||
var proxyHost = process.env.FALLBACK || '';
|
||||
if (proxyHost !== '') {
|
||||
|
|
39
docker-compose.yml
Normal file
39
docker-compose.yml
Normal file
|
@ -0,0 +1,39 @@
|
|||
version: '3.4'
|
||||
volumes:
|
||||
npm_data:
|
||||
runtime_data:
|
||||
|
||||
networks:
|
||||
scratch-api_scratch_network:
|
||||
external: true
|
||||
|
||||
services:
|
||||
app:
|
||||
container_name: scratch-www-app
|
||||
hostname: scratch-www-app
|
||||
environment:
|
||||
- API_HOST=http://localhost:8491
|
||||
- FALLBACK=http://localhost:8080
|
||||
- USE_DOCKER_WATCHOPTIONS=true
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: Dockerfile
|
||||
image: scratch-www:latest
|
||||
command: ./docker_entrypoint.sh npm start
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./
|
||||
target: /var/app/current
|
||||
volume:
|
||||
nocopy: true
|
||||
- type: bind
|
||||
source: ../scratch-gui
|
||||
target: /var/app/current/scratch-gui
|
||||
volume:
|
||||
nocopy: true
|
||||
- npm_data:/var/app/current/node_modules
|
||||
- runtime_data:/runtime
|
||||
ports:
|
||||
- "8333:8333"
|
||||
networks:
|
||||
- scratch-api_scratch_network
|
11
docker_entrypoint.sh
Executable file
11
docker_entrypoint.sh
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
echo "App Entrypoint"
|
||||
|
||||
if [ ! -f /runtime/.translations ]; then
|
||||
echo "Generating intl/translations"
|
||||
make translations
|
||||
touch /runtime/.translations
|
||||
fi
|
||||
|
||||
exec "$@"
|
|
@ -34,6 +34,7 @@
|
|||
"is": "Íslenska",
|
||||
"it": "Italiano",
|
||||
"kn": "ಭಾಷೆ-ಹೆಸರು",
|
||||
"kk": "Қазақша",
|
||||
"rw": "Kinyarwanda",
|
||||
"ht": "Kreyòl",
|
||||
"ku": "Kurdî",
|
||||
|
|
12
package.json
12
package.json
|
@ -6,7 +6,9 @@
|
|||
"start": "make start",
|
||||
"stop": "make stop",
|
||||
"test": "make test",
|
||||
"smoke": "make smoke",
|
||||
"smoke": "tap ./test/integration/smoke-testing/*.js --timeout=3600",
|
||||
"smoke-verbose": "tap ./test/integration/smoke-testing/*.js --timeout=3600 -R spec",
|
||||
"smoke-sauce": "SMOKE_REMOTE=true tap ./test/integration/smoke-testing/*.js --timeout=60000",
|
||||
"watch": "make watch",
|
||||
"build": "make build",
|
||||
"dev": "make watch && make start &"
|
||||
|
@ -28,10 +30,13 @@
|
|||
"express-http-proxy": "1.1.0",
|
||||
"lodash.defaults": "4.0.1",
|
||||
"newrelic": "1.25.4",
|
||||
"raven": "0.10.0"
|
||||
"raven": "0.10.0",
|
||||
"scratch-parser": "^4.2.0",
|
||||
"scratch-storage": "^0.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ajv": "6.4.0",
|
||||
"approximate-number": "2.0.0",
|
||||
"async": "1.5.2",
|
||||
"autoprefixer": "6.3.6",
|
||||
"babel-cli": "6.26.0",
|
||||
|
@ -72,6 +77,7 @@
|
|||
"lodash.merge": "3.3.2",
|
||||
"lodash.omit": "3.1.0",
|
||||
"lodash.range": "3.0.1",
|
||||
"lodash.truncate": "4.4.2",
|
||||
"minilog": "2.0.8",
|
||||
"node-dir": "0.1.16",
|
||||
"node-sass": "4.6.1",
|
||||
|
@ -88,11 +94,13 @@
|
|||
"react-redux": "5.0.7",
|
||||
"react-responsive": "3.0.0",
|
||||
"react-slick": "0.16.0",
|
||||
"react-string-replace": "0.4.1",
|
||||
"react-telephone-input": "4.3.4",
|
||||
"redux": "3.5.2",
|
||||
"redux-thunk": "2.0.1",
|
||||
"sass-lint": "1.5.1",
|
||||
"sass-loader": "6.0.6",
|
||||
"scratch-gui": "latest",
|
||||
"scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master",
|
||||
"slick-carousel": "1.6.0",
|
||||
"source-map-support": "0.3.2",
|
||||
|
|
|
@ -1,45 +1,54 @@
|
|||
/* UI Primary Colors */
|
||||
$ui-blue: hsla(200, 90, 55, 1); // #25AFF4
|
||||
$ui-orange: hsla(35, 90, 55, 1); // #F49D25
|
||||
$ui-blue: hsla(215, 100, 65, 1); // #4C97FF Motion Primary
|
||||
$ui-blue-dark: hsla(215, 65, 55, 1); // #3373CC Motion Secondary
|
||||
$ui-blue-10percent: hsla(215, 100, 65, .1);
|
||||
$ui-blue-25percent: hsla(215, 100, 65, .25);
|
||||
|
||||
$ui-orange: hsla(38, 100, 55, 1); // #FFAB19 Control Primary
|
||||
$ui-orange-10percent: hsla(35, 90, 55, .1);
|
||||
$ui-orange-25percent: hsla(35, 90, 55, .25);
|
||||
|
||||
$ui-light-gray: hsla(0, 0, 98, 1); //#FAFAFA
|
||||
$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-purple: hsla(265, 55, 55, 1); //#824DCB
|
||||
$ui-yellow: hsla(45, 100, 50, 1); //#FFBF00
|
||||
$ui-white: #fff;
|
||||
/* 3.0 colors */
|
||||
/* Using www naming convention for now, should be consistent with gui */
|
||||
$ui-aqua: hsla(163, 85, 40, 1); // #0FBD8C Extension Primary
|
||||
$ui-purple: hsla(260, 100, 70, 1); // #9966FF Looks Primary
|
||||
$ui-purple-dark: hsla(260, 60, 60, 1); // #774DCB Looks Secondary
|
||||
|
||||
$ui-yellow: hsla(45, 100, 50, 1); // #FFBF00 Control Primary
|
||||
$ui-coral: hsla(350, 100, 70, 1); // #FF6680 More Blocks Primary
|
||||
$ui-coral-dark: hsla(350, 100, 60, 1); // #FF3355 More Blocks tertiary
|
||||
|
||||
$ui-white: hsla(0, 100%, 100%, 1); //#FFF
|
||||
$ui-white-15percent: hsla(0, 100%, 100%, .15); //#FFF
|
||||
$ui-light-primary: hsl(215, 100, 95);
|
||||
|
||||
$ui-border: hsla(0, 0, 85, 1); //#D9D9D9
|
||||
|
||||
/* 3.0 colors */
|
||||
/* Using www naming convention for now, should be consistent with gui */
|
||||
$ui-green: hsla(163, 83, 40, 1); //#0fbd8c Pen Primary
|
||||
$ui-coral: hsla(350, 100, 70, 1); //#FF6680 More Priamry
|
||||
$ui-blue-10percent: hsla(215, 100, 65, .1);
|
||||
$ui-orange-25percent: hsla(35, 90, 55, .25);
|
||||
/* modals */
|
||||
$ui-mint-green: hsl(163, 69, 44);
|
||||
$ui-light-mint: hsl(163, 53, 67);
|
||||
|
||||
/* 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);
|
||||
$transparent-light-blue: rgba(229, 240, 254, 0);
|
||||
|
||||
/* Typography Colors */
|
||||
$header-gray: hsla(0, 0, 42, 1); //#6B6B6B
|
||||
$type-gray: hsla(0, 0, 42, 1); //#6B6B6B
|
||||
$type-white: #fff;
|
||||
$header-gray: hsla(225, 15, 40, 1); //#575E75
|
||||
$type-gray: hsla(225, 15, 40, 1); //#575E75
|
||||
$type-gray-75percent: hsla(225, 15, 40, .75);
|
||||
$type-white: hsla(0, 100, 100, 1); //#FFF
|
||||
|
||||
$link-blue: $ui-blue;
|
||||
|
||||
/* Component colors */
|
||||
$splash-green: #9c0;
|
||||
$splash-pink: #c2479d;
|
||||
$splash-blue: #199ed7;
|
||||
|
||||
/* Down Deep */
|
||||
$dd-darkblue: hsla(195, 72.4, 17.1, 1);
|
||||
|
|
30
src/components/extension-landing/extension-header.jsx
Normal file
30
src/components/extension-landing/extension-header.jsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
|
||||
require('./extension-landing.scss');
|
||||
|
||||
const ExtensionHeader = props => (
|
||||
<div className="extension-header">
|
||||
<FlexRow className="inner">
|
||||
<FlexRow className="column extension-info">
|
||||
{props.children}
|
||||
</FlexRow>
|
||||
<div className="extension-image">
|
||||
<img
|
||||
alt={props.imageAlt}
|
||||
src={props.imageSrc}
|
||||
/>
|
||||
</div>
|
||||
</FlexRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
ExtensionHeader.propTypes = {
|
||||
children: PropTypes.node,
|
||||
imageAlt: PropTypes.string,
|
||||
imageSrc: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = ExtensionHeader;
|
25
src/components/extension-landing/extension-landing.jsx
Normal file
25
src/components/extension-landing/extension-landing.jsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
const bindAll = require('lodash.bindall');
|
||||
const React = require('react');
|
||||
|
||||
const OS_ENUM = require('./os-enum.js');
|
||||
|
||||
class ExtensionLanding extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'onSetOS'
|
||||
]);
|
||||
|
||||
this.state = {
|
||||
OS: OS_ENUM.WINDOWS
|
||||
};
|
||||
}
|
||||
|
||||
onSetOS (os) {
|
||||
this.setState({
|
||||
OS: os
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ExtensionLanding;
|
261
src/components/extension-landing/extension-landing.scss
Normal file
261
src/components/extension-landing/extension-landing.scss
Normal file
|
@ -0,0 +1,261 @@
|
|||
@import "../../colors";
|
||||
@import "../../frameless";
|
||||
|
||||
#view {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.extension-landing {
|
||||
&>div {
|
||||
padding: 4rem 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
span {
|
||||
line-height: 1.7rem;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 4rem 0;
|
||||
border-width: 1px 0 0 0;
|
||||
border-style: solid;
|
||||
border-color: $ui-border;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.download {
|
||||
display: inline-block;
|
||||
|
||||
&::after {
|
||||
display: inline-block;
|
||||
margin-left: .5rem;
|
||||
background-image: url("/svgs/extensions/download.svg");
|
||||
background-repeat: no-repeat;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: text-top;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
.screenshot {
|
||||
border-radius: .5rem;
|
||||
}
|
||||
|
||||
.tip-box {
|
||||
margin-top: 4rem;
|
||||
border: 1px solid $ui-blue-25percent;
|
||||
border-radius: 1rem;
|
||||
background-color: $ui-blue-10percent;
|
||||
padding: 2rem 3rem;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.tip-content {
|
||||
align-items: flex-start;
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.extension-header {
|
||||
background-size: cover;
|
||||
color: $ui-white;
|
||||
|
||||
.inner {
|
||||
justify-content: space-between;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.extension-info {
|
||||
max-width: $cols7;
|
||||
align-items: flex-start;
|
||||
|
||||
.extension-copy {
|
||||
margin-bottom: 5rem;
|
||||
align-items: flex-start;
|
||||
|
||||
h2 {
|
||||
display: flex;
|
||||
margin-bottom: 2rem;
|
||||
color: $ui-white;
|
||||
}
|
||||
|
||||
h2 img {
|
||||
padding-right: .5rem;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
a {
|
||||
border-bottom: 1px solid $ui-white;
|
||||
color: $ui-white;
|
||||
}
|
||||
}
|
||||
|
||||
.extension-requirements-container {
|
||||
font-weight: 500;
|
||||
align-items: flex-start;
|
||||
|
||||
.requirements-header {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.extension-requirements {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.extension-requirements span {
|
||||
display: flex;
|
||||
margin-right: 1rem;
|
||||
font-size: 15px; // TODO: change to rem later
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.extension-requirements span img {
|
||||
padding-right: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.extension-image {
|
||||
width: 100%;
|
||||
max-width: $cols5;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.os-chooser {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.install-scratch-link {
|
||||
padding: 2rem 0;
|
||||
|
||||
.inner {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.step-image.badge {
|
||||
height: initial;
|
||||
}
|
||||
|
||||
.download-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.extension-section {
|
||||
.inner {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.getting-started {
|
||||
.getting-started-section {
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
|
||||
a {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.things-to-try .inner {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.project-card {
|
||||
margin: 0 1.5rem;
|
||||
border: 1px solid $ui-border;
|
||||
border-radius: .5rem;
|
||||
background-color: $ui-white;
|
||||
overflow: hidden;
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.project-card-image {
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.project-card-info {
|
||||
padding: 1rem;
|
||||
|
||||
p {
|
||||
margin: .2rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.faq {
|
||||
p {
|
||||
margin-bottom: 1.25rem;
|
||||
margin-left: 0;
|
||||
max-width: $cols8;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.faq-title {
|
||||
margin-bottom: 0;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
max-width: $cols8;
|
||||
}
|
||||
|
||||
section {
|
||||
ul {
|
||||
max-width: $cols8;
|
||||
}
|
||||
|
||||
.nav-spacer {
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
margin-top: -50px; // height of nav bar
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
&.indented {
|
||||
padding-left: $cols1 + (20px / $em);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.blue {
|
||||
background-color: $ui-blue-10percent;
|
||||
}
|
||||
|
||||
.inner {
|
||||
max-width: $cols12;
|
||||
}
|
||||
}
|
24
src/components/extension-landing/extension-requirements.jsx
Normal file
24
src/components/extension-landing/extension-requirements.jsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
const PropTypes = require('prop-types');
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const React = require('react');
|
||||
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
|
||||
require('./extension-landing.scss');
|
||||
|
||||
const ExtensionRequirements = props => (
|
||||
<FlexRow className="column extension-requirements-container">
|
||||
<span className="requirements-header">
|
||||
<FormattedMessage id="extensionHeader.requirements" />
|
||||
</span>
|
||||
<FlexRow className="extension-requirements">
|
||||
{props.children}
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
);
|
||||
|
||||
ExtensionRequirements.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
module.exports = ExtensionRequirements;
|
22
src/components/extension-landing/extension-section.jsx
Normal file
22
src/components/extension-landing/extension-section.jsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
const PropTypes = require('prop-types');
|
||||
const classNames = require('classnames');
|
||||
const React = require('react');
|
||||
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
|
||||
require('./extension-landing.scss');
|
||||
|
||||
const ExtensionSection = props => (
|
||||
<div className={classNames('extension-section', props.className)}>
|
||||
<FlexRow className="inner column">
|
||||
{props.children}
|
||||
</FlexRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
ExtensionSection.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = ExtensionSection;
|
69
src/components/extension-landing/install-scratch-link.jsx
Normal file
69
src/components/extension-landing/install-scratch-link.jsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
const PropTypes = require('prop-types');
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const React = require('react');
|
||||
|
||||
const OS_ENUM = require('./os-enum.js');
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
const Steps = require('../../components/steps/steps.jsx');
|
||||
const Step = require('../../components/steps/step.jsx');
|
||||
|
||||
require('./extension-landing.scss');
|
||||
|
||||
const InstallScratchLink = ({
|
||||
currentOS
|
||||
}) => (
|
||||
<div className="blue install-scratch-link">
|
||||
<FlexRow className="inner column">
|
||||
<h2><FormattedMessage id="installScratchLink.installHeaderTitle" /></h2>
|
||||
<Steps>
|
||||
<div className="step">
|
||||
<Step
|
||||
compact
|
||||
number={1}
|
||||
>
|
||||
<span className="step-description">
|
||||
<FormattedMessage id="installScratchLink.downloadAndInstall" />
|
||||
</span>
|
||||
<a
|
||||
className="step-image badge"
|
||||
href={`https://downloads.scratch.mit.edu/link/${
|
||||
currentOS === OS_ENUM.WINDOWS ? 'windows' : 'mac'
|
||||
}.zip`}
|
||||
>
|
||||
<button className="button download-button">
|
||||
{currentOS === OS_ENUM.WINDOWS ?
|
||||
<FormattedMessage id="installScratchLink.windowsDownload" /> :
|
||||
<FormattedMessage id="installScratchLink.macosDownload" />
|
||||
}
|
||||
<img src="/svgs/extensions/download-white.svg" />
|
||||
</button>
|
||||
</a>
|
||||
</Step>
|
||||
|
||||
</div>
|
||||
<Step
|
||||
compact
|
||||
number={2}
|
||||
>
|
||||
<span className="step-description">
|
||||
<FormattedMessage id="installScratchLink.startScratchLink" />
|
||||
</span>
|
||||
<div className="step-image">
|
||||
<img
|
||||
className="screenshot"
|
||||
src={`/images/scratchlink/${
|
||||
currentOS === OS_ENUM.WINDOWS ? 'windows' : 'mac'
|
||||
}-toolbar.png`}
|
||||
/>
|
||||
</div>
|
||||
</Step>
|
||||
</Steps>
|
||||
</FlexRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
InstallScratchLink.propTypes = {
|
||||
currentOS: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = InstallScratchLink;
|
6
src/components/extension-landing/os-enum.js
Normal file
6
src/components/extension-landing/os-enum.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
const OS_ENUM = {
|
||||
WINDOWS: 'Windows',
|
||||
MACOS: 'macOS'
|
||||
};
|
||||
|
||||
module.exports = OS_ENUM;
|
27
src/components/extension-landing/project-card.jsx
Normal file
27
src/components/extension-landing/project-card.jsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const ProjectCard = props => (
|
||||
<a
|
||||
download
|
||||
className="project-card"
|
||||
href={props.cardUrl}
|
||||
>
|
||||
<div className="project-card-image">
|
||||
<img src={props.imageSrc} />
|
||||
</div>
|
||||
<div className="project-card-info">
|
||||
<h4>{props.title}</h4>
|
||||
<p>{props.description}</p>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
|
||||
ProjectCard.propTypes = {
|
||||
cardUrl: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
imageSrc: PropTypes.string,
|
||||
title: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = ProjectCard;
|
20
src/components/extension-landing/tip-box.jsx
Normal file
20
src/components/extension-landing/tip-box.jsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
|
||||
|
||||
const TipBox = props => (
|
||||
<div className="tip-box">
|
||||
<h4>{props.title}</h4>
|
||||
<FlexRow className="column tip-content">
|
||||
{props.children}
|
||||
</FlexRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
TipBox.propTypes = {
|
||||
children: PropTypes.node,
|
||||
title: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = TipBox;
|
|
@ -11,6 +11,75 @@ require('../footer.scss');
|
|||
|
||||
const ConferenceFooter = props => (
|
||||
<FooterBox>
|
||||
<div className="collaborators">
|
||||
<h4>Sponsors</h4>
|
||||
<FlexRow as="ul">
|
||||
<li className="odl">
|
||||
<a href="https://odl.mit.edu/">
|
||||
<img
|
||||
alt="MIT Office of Digital Learning"
|
||||
src="/images/conference/footer/2018/mit-ol-logo.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="google">
|
||||
<a href="http://www.google.com/">
|
||||
<img
|
||||
alt="Google"
|
||||
src="/images/conference/footer/2018/google.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="epam">
|
||||
<a href="https://www.epam.com/">
|
||||
<img
|
||||
alt="EPAM Systems"
|
||||
src="/images/conference/footer/2018/epam.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="intel">
|
||||
<a href="http://www.intel.com/content/www/us/en/homepage.html">
|
||||
<img
|
||||
alt="Intel"
|
||||
src="/images/conference/footer/2018/intel.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="lego">
|
||||
<a href="http://www.legofoundation.com/">
|
||||
<img
|
||||
alt="The LEGO Foundation"
|
||||
src="/images/conference/footer/2018/lego-foundation.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="siegel">
|
||||
<a href="http://www.siegelendowment.org/">
|
||||
<img
|
||||
alt="Siegel Family Endowment"
|
||||
src="/images/conference/footer/2018/siegel.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="cartoon-network">
|
||||
<a href="https://www.cartoonnetwork.com/">
|
||||
<img
|
||||
alt="Cartoon Network"
|
||||
src="/images/conference/footer/2018/cartoon-network.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="scratchfoundation">
|
||||
<a href="http://www.scratchfoundation.org/">
|
||||
<img
|
||||
alt="Scratch Foundation"
|
||||
src="/images/conference/footer/2018/scratch-foundation.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</FlexRow>
|
||||
</div>
|
||||
<FlexRow className="scratch-links">
|
||||
<div className="family">
|
||||
<h4><FormattedMessage id="footer.scratchFamily" /></h4>
|
||||
|
@ -121,16 +190,28 @@ const ConferenceFooter = props => (
|
|||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="http://medium.com/scratchfoundation-blog"
|
||||
href="https://medium.com/scratchteam-blog"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
alt="scratch foundation blog"
|
||||
alt="scratch team blog"
|
||||
src="/images/conference/footer/medium.png"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://scratch-foundation.myshopify.com/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
alt="scratch store"
|
||||
src="/images/conference/footer/shopify-white.svg"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</FlexRow>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -17,19 +17,14 @@
|
|||
width: 100%;
|
||||
|
||||
ul {
|
||||
justify-content: space-between;
|
||||
margin: 5px 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
img {
|
||||
margin: 20px 0;
|
||||
max-width: 180px;
|
||||
max-height: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.nostarch {
|
||||
img {
|
||||
max-height: 40px;
|
||||
max-height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +33,17 @@
|
|||
max-height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.siegel {
|
||||
img {
|
||||
max-height: 60px;
|
||||
}
|
||||
}
|
||||
.cartoon-network {
|
||||
img {
|
||||
min-height: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scratch-links {
|
||||
|
|
|
@ -108,7 +108,7 @@ const Footer = props => (
|
|||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="https://wiki.scratch.mit.edu/">
|
||||
<a href="https://en.scratch-wiki.info/">
|
||||
<FormattedMessage id="general.wiki" />
|
||||
</a>
|
||||
</dd>
|
||||
|
|
|
@ -8,31 +8,21 @@ $pass-bg: $ui-aqua;
|
|||
display: inline-block;
|
||||
margin: .5em 0;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 1px $box-shadow-gray;
|
||||
border-radius: .5rem;
|
||||
background-color: $ui-blue;
|
||||
cursor: pointer;
|
||||
padding: .75em 1em;
|
||||
padding: 1em 1.25em;
|
||||
color: $type-white;
|
||||
font-size: .8rem;
|
||||
font-weight: bold;
|
||||
|
||||
/* USER BUTTON STATES */
|
||||
&:hover {
|
||||
box-shadow: 0 2px 2px $box-shadow-gray;
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: inset 0 1px 2px $box-shadow-gray;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* DATA BUTTON STATES */
|
||||
&.white {
|
||||
border-top: 1px inset $active-gray;
|
||||
background-color: $base-bg;
|
||||
color: $ui-blue;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
.char-count {
|
||||
letter-spacing: 1px;
|
||||
color: lighten($type-gray, 30%);
|
||||
color: $type-gray-75percent;
|
||||
font-weight: 500;
|
||||
|
||||
&.overmax {
|
||||
|
|
72
src/components/forms/inplace-input.jsx
Normal file
72
src/components/forms/inplace-input.jsx
Normal file
|
@ -0,0 +1,72 @@
|
|||
const bindAll = require('lodash.bindall');
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const FRCInput = require('formsy-react-components').Input;
|
||||
const FRCTextarea = require('formsy-react-components').Textarea;
|
||||
const classNames = require('classnames');
|
||||
|
||||
require('./row.scss');
|
||||
require('./inplace-input.scss');
|
||||
|
||||
class InplaceInput extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleBlur',
|
||||
'setRef'
|
||||
]);
|
||||
}
|
||||
handleBlur (name, value) {
|
||||
if (this.inputRef.props.errorMessages.length === 0) {
|
||||
const jsonData = {};
|
||||
jsonData[name] = value;
|
||||
this.props.handleUpdate(jsonData);
|
||||
}
|
||||
}
|
||||
setRef (input) {
|
||||
this.inputRef = input;
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
className,
|
||||
type,
|
||||
handleUpdate, // eslint-disable-line no-unused-vars
|
||||
...props
|
||||
} = this.props;
|
||||
return (
|
||||
(type === 'textarea') ?
|
||||
<FRCTextarea
|
||||
className="inplace-textarea"
|
||||
componentRef={this.setRef}
|
||||
elementWrapperClassName="grow"
|
||||
label={null}
|
||||
rowClassName={classNames('textarea-row no-label', className)}
|
||||
onBlur={this.handleBlur}
|
||||
{...props}
|
||||
/> :
|
||||
<FRCInput
|
||||
className="inplace-input"
|
||||
componentRef={this.setRef}
|
||||
rowClassName={classNames(
|
||||
className,
|
||||
'no-label'
|
||||
)}
|
||||
onBlur={this.handleBlur}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InplaceInput.propTypes = {
|
||||
className: PropTypes.string,
|
||||
handleUpdate: PropTypes.func.isRequired,
|
||||
type: PropTypes.string
|
||||
};
|
||||
|
||||
InplaceInput.defaultProps = {
|
||||
type: 'text',
|
||||
value: ''
|
||||
};
|
||||
|
||||
module.exports = InplaceInput;
|
69
src/components/forms/inplace-input.scss
Normal file
69
src/components/forms/inplace-input.scss
Normal file
|
@ -0,0 +1,69 @@
|
|||
@import "../../colors";
|
||||
@import "../../frameless";
|
||||
|
||||
.inplace-input {
|
||||
transition: all .5s ease;
|
||||
border: 2px dashed $ui-blue-25percent;
|
||||
border-radius: 8px;
|
||||
background-color: transparent;
|
||||
padding: 0 1rem;
|
||||
width: calc(100% - 2.25rem);
|
||||
color: $type-gray;
|
||||
|
||||
&:focus {
|
||||
transition: all .5s ease;
|
||||
outline: none;
|
||||
border: 2px solid $ui-blue;
|
||||
box-shadow: 0 0 0 4px $ui-blue-25percent;
|
||||
}
|
||||
|
||||
&.fail {
|
||||
border: 1px solid $ui-orange;
|
||||
}
|
||||
|
||||
&.pass {
|
||||
border: 1px solid $active-dark-gray;
|
||||
}
|
||||
|
||||
/* IE10/11-specific style resets */
|
||||
&::-ms-reveal, &::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.inplace-textarea {
|
||||
transition: all 1s ease;
|
||||
border: 2px dashed $ui-blue-25percent;
|
||||
border-radius: 8px;
|
||||
background-color: $ui-light-gray;
|
||||
padding: .75rem 1rem;
|
||||
width: 100%;
|
||||
line-height: 1.75em;
|
||||
color: $type-gray;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
resize: none;
|
||||
|
||||
&:focus {
|
||||
transition: all 1s ease;
|
||||
outline: none;
|
||||
border: 2px solid $ui-blue;
|
||||
box-shadow: 0 0 0 4px $ui-blue-25percent;
|
||||
}
|
||||
|
||||
&.fail {
|
||||
border: 1px solid $ui-orange;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
padding-top: 1rem;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
@import "../../frameless";
|
||||
|
||||
$base-bg: $ui-light-gray;
|
||||
$pass-bg: lighten($ui-aqua, 35%);
|
||||
|
||||
.row {
|
||||
label {
|
||||
|
@ -32,8 +31,7 @@ $pass-bg: lighten($ui-aqua, 35%);
|
|||
}
|
||||
|
||||
&.pass {
|
||||
border: 1px solid $active-dark-gray;
|
||||
background-color: $pass-bg;
|
||||
border: 1px solid $ui-aqua;
|
||||
}
|
||||
|
||||
/* IE10/11-specific style resets */
|
||||
|
|
|
@ -22,4 +22,8 @@
|
|||
&.fail {
|
||||
border: 1px solid $ui-orange;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,8 @@ module.exports.validationHOCFactory = defaultValidationErrors => (Component => {
|
|||
<Component
|
||||
validationErrors={defaults(
|
||||
{},
|
||||
defaultValidationErrors,
|
||||
props.validationErrors
|
||||
props.validationErrors,
|
||||
defaultValidationErrors
|
||||
)}
|
||||
{...omit(props, ['validationErrors'])}
|
||||
/>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
.thumbnail {
|
||||
margin: 7px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 3px $box-shadow-gray;
|
||||
box-shadow: 0 0 0 1px $active-gray;
|
||||
background-color: $ui-white;
|
||||
padding-bottom: 4px;
|
||||
width: $thumbnail-width;
|
||||
|
|
|
@ -6,11 +6,9 @@
|
|||
@import "../../colors";
|
||||
@import "../../frameless";
|
||||
|
||||
$ui-secondary: darken($ui-blue, 10%);
|
||||
|
||||
.title-banner {
|
||||
&.masthead {
|
||||
background-color: $ui-secondary;
|
||||
background-color: $ui-blue-dark;
|
||||
padding-bottom: .5rem;
|
||||
|
||||
h1 {
|
||||
|
|
|
@ -94,37 +94,37 @@
|
|||
|
||||
&.sprite-1 {
|
||||
.circle {
|
||||
background-color: $splash-green;
|
||||
background-color: $ui-aqua;
|
||||
}
|
||||
|
||||
.text {
|
||||
top: 60px;
|
||||
left: 50px;
|
||||
color: $splash-green;
|
||||
color: $ui-aqua;
|
||||
}
|
||||
}
|
||||
|
||||
&.sprite-2 {
|
||||
.circle {
|
||||
background-color: $splash-pink;
|
||||
background-color: $ui-purple;
|
||||
}
|
||||
|
||||
.text {
|
||||
top: 77px;
|
||||
left: 50px;
|
||||
color: $splash-pink;
|
||||
color: $ui-purple;
|
||||
}
|
||||
}
|
||||
|
||||
&.sprite-3 {
|
||||
.circle {
|
||||
background-color: $splash-blue;
|
||||
background-color: $ui-blue;
|
||||
}
|
||||
|
||||
.text {
|
||||
top: 37px;
|
||||
left: 45px;
|
||||
color: $splash-blue;
|
||||
color: $ui-blue;
|
||||
}
|
||||
|
||||
.subtext {
|
||||
|
@ -145,19 +145,19 @@
|
|||
|
||||
&.sprite-1 {
|
||||
.circle {
|
||||
box-shadow: 0 0 10px 2px $splash-green;
|
||||
box-shadow: 0 0 10px 2px $ui-aqua;
|
||||
}
|
||||
}
|
||||
|
||||
&.sprite-2 {
|
||||
.circle {
|
||||
box-shadow: 0 0 10px 2px $splash-pink;
|
||||
box-shadow: 0 0 10px 2px $ui-purple;
|
||||
}
|
||||
}
|
||||
|
||||
&.sprite-3 {
|
||||
.circle {
|
||||
box-shadow: 0 0 10px 2px $splash-blue;
|
||||
box-shadow: 0 0 10px 2px $ui-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
72
src/components/modal/addtostudio/container.jsx
Normal file
72
src/components/modal/addtostudio/container.jsx
Normal file
|
@ -0,0 +1,72 @@
|
|||
const bindAll = require('lodash.bindall');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const AddToStudioModalPresentation = require('./presentation.jsx');
|
||||
|
||||
class AddToStudioModal extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleRequestClose',
|
||||
'handleSubmit'
|
||||
]);
|
||||
|
||||
this.state = {
|
||||
waitingToClose: false
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUpdate () {
|
||||
this.closeIfFinishedUpdating();
|
||||
}
|
||||
|
||||
hasOutstandingUpdates () {
|
||||
return (this.props.studios.some(studio => (studio.hasRequestOutstanding === true)));
|
||||
}
|
||||
|
||||
closeIfFinishedUpdating () {
|
||||
if (this.state.waitingToClose === true && this.hasOutstandingUpdates() === false) {
|
||||
this.closeAndStopWaiting();
|
||||
}
|
||||
}
|
||||
|
||||
// before closing, set waitingToClose to false. That way, if user reopens
|
||||
// modal, it won't unexpectedly close.
|
||||
closeAndStopWaiting () {
|
||||
this.setState({waitingToClose: false}, () => {
|
||||
this.props.onRequestClose();
|
||||
});
|
||||
}
|
||||
|
||||
handleRequestClose () {
|
||||
this.closeAndStopWaiting();
|
||||
}
|
||||
|
||||
handleSubmit () {
|
||||
this.setState({waitingToClose: true}, () => {
|
||||
this.closeIfFinishedUpdating();
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<AddToStudioModalPresentation
|
||||
isOpen={this.props.isOpen}
|
||||
studios={this.props.studios}
|
||||
waitingToClose={this.state.waitingToClose}
|
||||
onRequestClose={this.handleRequestClose}
|
||||
onSubmit={this.handleSubmit}
|
||||
onToggleStudio={this.props.onToggleStudio}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddToStudioModal.propTypes = {
|
||||
isOpen: PropTypes.bool,
|
||||
onRequestClose: PropTypes.func,
|
||||
onToggleStudio: PropTypes.func,
|
||||
studios: PropTypes.arrayOf(PropTypes.object)
|
||||
};
|
||||
|
||||
module.exports = AddToStudioModal;
|
189
src/components/modal/addtostudio/modal.scss
Normal file
189
src/components/modal/addtostudio/modal.scss
Normal file
|
@ -0,0 +1,189 @@
|
|||
@import "../../../colors";
|
||||
@import "../../../frameless";
|
||||
|
||||
.mod-addToStudio * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.mod-addToStudio {
|
||||
margin: 100px auto;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
width: 36.25rem; /* 580px; */
|
||||
height: 388px; /* 24.25rem; */
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.addToStudio-modal-header {
|
||||
box-shadow: inset 0 -1px 0 0 $ui-blue-dark;
|
||||
background-color: $ui-blue;
|
||||
padding-top: .75rem;
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.addToStudio-content-label {
|
||||
text-align: center;
|
||||
color: $type-white;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.addToStudio-modal-content {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.studio-list-outer-scrollbox {
|
||||
position: relative;
|
||||
background-color: $ui-blue-10percent;
|
||||
}
|
||||
|
||||
.studio-list-inner-scrollbox {
|
||||
margin-right: .5rem;
|
||||
padding-right: .5rem;
|
||||
height: 16.9375rem;
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
background-color: $active-dark-gray;
|
||||
height: 92px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.studio-list-container {
|
||||
display: flex;
|
||||
padding: .40625rem 0 0 1.46875rem;
|
||||
justify-content: flex-start;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
/* NOTE: force scrolling: add to above:
|
||||
min-height: 30rem;
|
||||
*/
|
||||
|
||||
.studio-list-bottom-gradient {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(
|
||||
$transparent-light-blue,
|
||||
$ui-light-primary
|
||||
);
|
||||
height: 32px;
|
||||
pointer-events: none; /* pass clicks through to buttons underneath */
|
||||
}
|
||||
|
||||
|
||||
.studio-selector-button {
|
||||
display: flex;
|
||||
position: relative;
|
||||
margin: .21875rem .21875rem;
|
||||
border-radius: .5rem;
|
||||
background-color: $ui-white;
|
||||
padding: 0;
|
||||
width: 16.1875rem; /* 259px */
|
||||
height: 2.5rem;
|
||||
box-sizing: border-box;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.studio-selector-button-text {
|
||||
position: absolute;
|
||||
/* per spec, should be:
|
||||
margin: .375rem 2.18375rem .375rem .6875rem
|
||||
but in practice, our css seems to vertically align text to top, where
|
||||
invision spec aligned to middle.
|
||||
*/
|
||||
margin: .575rem 2.18375rem .175rem .6875rem;
|
||||
width: 13.3125rem;
|
||||
height: 1rem; /* diff from spec, in case we ever do valign to middle */
|
||||
line-height: 1.25rem;
|
||||
font-family: "Helvetica Neue";
|
||||
font-size: .875rem;
|
||||
font-weight: regular;
|
||||
}
|
||||
|
||||
.studio-selector-button-selected {
|
||||
background-color: $ui-mint-green;
|
||||
color: $ui-white;
|
||||
}
|
||||
|
||||
.studio-selector-button-waiting {
|
||||
background-color: $ui-light-mint;
|
||||
color: $ui-white;
|
||||
}
|
||||
|
||||
.studio-selector-button-text-selected {
|
||||
color: $ui-white;
|
||||
}
|
||||
|
||||
.studio-selector-button-text-unselected {
|
||||
color: $type-gray;
|
||||
}
|
||||
|
||||
.studio-status-icon {
|
||||
position: absolute;
|
||||
margin: .5rem .625rem .5rem 14.0625rem;
|
||||
border-radius: .75rem;
|
||||
padding: .0625rem .075rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
color: $ui-white;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.studio-status-icon-unselected {
|
||||
background-color: $ui-blue;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
background-color: $ui-blue;
|
||||
}
|
||||
|
||||
.submit-button-waiting {
|
||||
background-color: $ui-blue;
|
||||
}
|
||||
|
||||
.studio-status-icon-plus-img {
|
||||
width: 1.4rem;
|
||||
height: 1.4rem;
|
||||
}
|
||||
|
||||
.studio-status-icon--img {
|
||||
width: 1.4rem;
|
||||
height: 1.4rem;
|
||||
}
|
||||
|
||||
.action-button-text .spinner-smooth {
|
||||
margin: .2125rem auto;
|
||||
width: 1.875rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.studio-status-icon .spinner-smooth {
|
||||
position: unset; /* don't understand why neither relative nor absolute work */
|
||||
}
|
||||
|
||||
.studio-status-icon .spinner-smooth .circle {
|
||||
/* overlay spinner on circle */
|
||||
position: absolute;
|
||||
margin: .1875rem; /* stay within boundaries of circle */
|
||||
width: 75%; /* stay within boundaries of circle */
|
||||
height: 75%; /* stay within boundaries of circle */
|
||||
}
|
119
src/components/modal/addtostudio/presentation.jsx
Normal file
119
src/components/modal/addtostudio/presentation.jsx
Normal file
|
@ -0,0 +1,119 @@
|
|||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const injectIntl = require('react-intl').injectIntl;
|
||||
const intlShape = require('react-intl').intlShape;
|
||||
const Modal = require('../base/modal.jsx');
|
||||
|
||||
const Form = require('../../forms/form.jsx');
|
||||
const Button = require('../../forms/button.jsx');
|
||||
const Spinner = require('../../spinner/spinner.jsx');
|
||||
const FlexRow = require('../../flex-row/flex-row.jsx');
|
||||
const StudioButton = require('./studio-button.jsx');
|
||||
|
||||
require('../../forms/button.scss');
|
||||
require('./modal.scss');
|
||||
|
||||
const AddToStudioModalPresentation = ({
|
||||
intl,
|
||||
isOpen,
|
||||
studios,
|
||||
waitingToClose,
|
||||
onToggleStudio,
|
||||
onRequestClose,
|
||||
onSubmit
|
||||
}) => {
|
||||
const contentLabel = intl.formatMessage({id: 'addToStudio.title'});
|
||||
const studioButtons = studios.map(studio => (
|
||||
<StudioButton
|
||||
hasRequestOutstanding={studio.hasRequestOutstanding}
|
||||
id={studio.id}
|
||||
includesProject={studio.includesProject}
|
||||
key={studio.id}
|
||||
title={studio.title}
|
||||
onToggleStudio={onToggleStudio}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="mod-addToStudio"
|
||||
contentLabel={contentLabel}
|
||||
isOpen={isOpen}
|
||||
onRequestClose={onRequestClose}
|
||||
>
|
||||
<div>
|
||||
<div className="addToStudio-modal-header">
|
||||
<div className="addToStudio-content-label">
|
||||
{contentLabel}
|
||||
</div>
|
||||
</div>
|
||||
<div className="addToStudio-modal-content">
|
||||
<div className="studio-list-outer-scrollbox">
|
||||
<div className="studio-list-inner-scrollbox">
|
||||
<div className="studio-list-container">
|
||||
{studioButtons}
|
||||
</div>
|
||||
<div className="studio-list-bottom-gradient" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<Form
|
||||
className="add-to-studio"
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<FlexRow className="action-buttons">
|
||||
<Button
|
||||
className="action-button close-button white"
|
||||
key="closeButton"
|
||||
name="closeButton"
|
||||
type="button"
|
||||
onClick={onRequestClose}
|
||||
>
|
||||
<div className="action-button-text">
|
||||
<FormattedMessage id="general.close" />
|
||||
</div>
|
||||
</Button>
|
||||
{waitingToClose ? [
|
||||
<Button
|
||||
className="action-button submit-button submit-button-waiting"
|
||||
disabled="disabled"
|
||||
key="submitButton"
|
||||
type="submit"
|
||||
>
|
||||
<div className="action-button-text">
|
||||
<Spinner mode="smooth" />
|
||||
<FormattedMessage id="addToStudio.finishing" />
|
||||
</div>
|
||||
</Button>
|
||||
] : [
|
||||
<Button
|
||||
className="action-button submit-button"
|
||||
key="submitButton"
|
||||
type="submit"
|
||||
>
|
||||
<div className="action-button-text">
|
||||
<FormattedMessage id="general.okay" />
|
||||
</div>
|
||||
</Button>
|
||||
]}
|
||||
</FlexRow>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
AddToStudioModalPresentation.propTypes = {
|
||||
intl: intlShape,
|
||||
isOpen: PropTypes.bool,
|
||||
onRequestClose: PropTypes.func,
|
||||
onSubmit: PropTypes.func,
|
||||
onToggleStudio: PropTypes.func,
|
||||
studios: PropTypes.arrayOf(PropTypes.object),
|
||||
waitingToClose: PropTypes.bool
|
||||
};
|
||||
|
||||
module.exports = injectIntl(AddToStudioModalPresentation);
|
72
src/components/modal/addtostudio/studio-button.jsx
Normal file
72
src/components/modal/addtostudio/studio-button.jsx
Normal file
|
@ -0,0 +1,72 @@
|
|||
const truncateAtWordBoundary = require('../../../lib/truncate').truncateAtWordBoundary;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const Spinner = require('../../spinner/spinner.jsx');
|
||||
|
||||
require('./modal.scss');
|
||||
|
||||
const StudioButton = ({
|
||||
hasRequestOutstanding,
|
||||
id,
|
||||
includesProject,
|
||||
title,
|
||||
onToggleStudio
|
||||
}) => {
|
||||
const checkmark = (
|
||||
<img
|
||||
alt="checkmark-icon"
|
||||
className="studio-status-icon-checkmark-img"
|
||||
src="/svgs/modal/confirm.svg"
|
||||
/>
|
||||
);
|
||||
const plus = (
|
||||
<img
|
||||
alt="plus-icon"
|
||||
className="studio-status-icon-plus-img"
|
||||
src="/svgs/modal/add.svg"
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'studio-selector-button',
|
||||
{'studio-selector-button-waiting': hasRequestOutstanding},
|
||||
{'studio-selector-button-selected':
|
||||
includesProject && !hasRequestOutstanding}
|
||||
)}
|
||||
data-id={id}
|
||||
onClick={onToggleStudio}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'studio-selector-button-text',
|
||||
{'studio-selector-button-text-selected': includesProject || hasRequestOutstanding},
|
||||
{'studio-selector-button-text-unselected': !includesProject && !hasRequestOutstanding}
|
||||
)}
|
||||
>
|
||||
{truncateAtWordBoundary(title, 25)}
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
'studio-status-icon',
|
||||
{'studio-status-icon-unselected': !includesProject}
|
||||
)}
|
||||
>
|
||||
{(hasRequestOutstanding ?
|
||||
(<Spinner mode="smooth" />) :
|
||||
(includesProject ? checkmark : plus))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
StudioButton.propTypes = {
|
||||
hasRequestOutstanding: PropTypes.bool,
|
||||
id: PropTypes.number,
|
||||
includesProject: PropTypes.bool,
|
||||
onToggleStudio: PropTypes.func,
|
||||
title: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = StudioButton;
|
|
@ -5,10 +5,14 @@
|
|||
position: relative;
|
||||
margin: 3.75rem auto;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 0 0 1px $active-gray;
|
||||
box-shadow: 0 0 0 4px $ui-white-15percent;
|
||||
background-color: $ui-white;
|
||||
padding: 0;
|
||||
width: 48.75rem;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
|
@ -21,10 +25,6 @@
|
|||
background-color: transparentize($ui-blue, .3);
|
||||
}
|
||||
|
||||
.modal-content:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
$modal-close-size: 1rem;
|
||||
.modal-content-close {
|
||||
position: absolute;
|
||||
|
@ -59,3 +59,52 @@ $modal-close-size: 1rem;
|
|||
position: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
/* Close button, Submit button, etc. */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
margin: 1.125rem .8275rem .9375rem .8275rem;
|
||||
line-height: 1.5rem;
|
||||
justify-content: flex-end !important;
|
||||
align-items: flex-start;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
/* setting overall modal to contain overflow looks good, but isn't
|
||||
compatible with elements (like validation popups) that need to bleed
|
||||
past modal boundary. This class can be used to force modal button
|
||||
row to appear to contain overflow. */
|
||||
.action-buttons-overflow-fix {
|
||||
margin-bottom: .9375rem;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
margin: 0 0 0 .54625rem;
|
||||
border-radius: .25rem;
|
||||
padding: 6px 1.25rem 14px 1.25rem;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.action-button.close-button {
|
||||
border: 1px solid $active-gray;
|
||||
}
|
||||
|
||||
.action-button-text {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.action-button.disabled {
|
||||
background-color: $active-dark-gray;
|
||||
}
|
||||
|
||||
.error-text
|
||||
{
|
||||
display: block;
|
||||
border: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
background-color: $ui-orange;
|
||||
padding: 1rem;
|
||||
min-height: 1rem;
|
||||
overflow: visible;
|
||||
color: $type-white;
|
||||
}
|
||||
|
|
270
src/components/modal/report/modal.jsx
Normal file
270
src/components/modal/report/modal.jsx
Normal file
|
@ -0,0 +1,270 @@
|
|||
const bindAll = require('lodash.bindall');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const connect = require('react-redux').connect;
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const injectIntl = require('react-intl').injectIntl;
|
||||
const intlShape = require('react-intl').intlShape;
|
||||
const Modal = require('../base/modal.jsx');
|
||||
const classNames = require('classnames');
|
||||
|
||||
const Form = require('../../forms/form.jsx');
|
||||
const Button = require('../../forms/button.jsx');
|
||||
const Select = require('../../forms/select.jsx');
|
||||
const Spinner = require('../../spinner/spinner.jsx');
|
||||
const TextArea = require('../../forms/textarea.jsx');
|
||||
const FlexRow = require('../../flex-row/flex-row.jsx');
|
||||
const previewActions = require('../../../redux/preview.js');
|
||||
|
||||
require('../../forms/button.scss');
|
||||
require('./modal.scss');
|
||||
|
||||
const REPORT_OPTIONS = [
|
||||
{
|
||||
value: '',
|
||||
label: {id: 'report.reasonPlaceHolder'},
|
||||
prompt: {id: 'report.promptPlaceholder'}
|
||||
},
|
||||
{
|
||||
value: '0',
|
||||
label: {id: 'report.reasonCopy'},
|
||||
prompt: {id: 'report.promptCopy'}
|
||||
},
|
||||
{
|
||||
value: '1',
|
||||
label: {id: 'report.reasonUncredited'},
|
||||
prompt: {id: 'report.promptUncredited'}
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: {id: 'report.reasonScary'},
|
||||
prompt: {id: 'report.promptScary'}
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: {id: 'report.reasonLanguage'},
|
||||
prompt: {id: 'report.promptLanguage'}
|
||||
},
|
||||
{
|
||||
value: '4',
|
||||
label: {id: 'report.reasonMusic'},
|
||||
prompt: {id: 'report.promptMusic'}
|
||||
},
|
||||
{
|
||||
value: '8',
|
||||
label: {id: 'report.reasonImage'},
|
||||
prompt: {id: 'report.promptImage'}
|
||||
},
|
||||
{
|
||||
value: '5',
|
||||
label: {id: 'report.reasonPersonal'},
|
||||
prompt: {id: 'report.promptPersonal'}
|
||||
},
|
||||
{
|
||||
value: '6',
|
||||
label: {id: 'general.other'},
|
||||
prompt: {id: 'report.promptGuidelines'}
|
||||
}
|
||||
];
|
||||
|
||||
class ReportModal extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleCategorySelect',
|
||||
'handleValid',
|
||||
'handleInvalid'
|
||||
]);
|
||||
this.state = {
|
||||
category: '',
|
||||
notes: '',
|
||||
valid: false
|
||||
};
|
||||
}
|
||||
handleCategorySelect (name, value) {
|
||||
this.setState({category: value});
|
||||
}
|
||||
handleValid () {
|
||||
this.setState({valid: true});
|
||||
}
|
||||
handleInvalid () {
|
||||
this.setState({valid: false});
|
||||
}
|
||||
lookupPrompt (value) {
|
||||
const prompt = REPORT_OPTIONS.find(item => item.value === value).prompt;
|
||||
return this.props.intl.formatMessage(prompt);
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
intl,
|
||||
isConfirmed,
|
||||
isError,
|
||||
isOpen,
|
||||
isWaiting,
|
||||
onReport, // eslint-disable-line no-unused-vars
|
||||
onRequestClose,
|
||||
type,
|
||||
...modalProps
|
||||
} = this.props;
|
||||
const submitEnabled = this.state.valid && !isWaiting;
|
||||
const submitDisabledParam = submitEnabled ? {} : {disabled: 'disabled'};
|
||||
const contentLabel = intl.formatMessage({id: `report.${type}`});
|
||||
return (
|
||||
<Modal
|
||||
className="mod-report"
|
||||
contentLabel={contentLabel}
|
||||
isOpen={isOpen}
|
||||
onRequestClose={onRequestClose}
|
||||
{...modalProps}
|
||||
>
|
||||
<div>
|
||||
<div className="report-modal-header">
|
||||
<div className="report-content-label">
|
||||
{contentLabel}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Form
|
||||
className="report"
|
||||
onInvalid={this.handleInvalid}
|
||||
onValid={this.handleValid}
|
||||
onValidSubmit={onReport}
|
||||
>
|
||||
<div className="report-modal-content">
|
||||
{isConfirmed ? (
|
||||
<div className="received">
|
||||
<div className="received-header">
|
||||
<FormattedMessage id="report.receivedHeader" />
|
||||
</div>
|
||||
<FormattedMessage id="report.receivedBody" />
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className="instructions">
|
||||
<FormattedMessage
|
||||
id={`report.${type}Instructions`}
|
||||
key={`report.${type}Instructions`}
|
||||
values={{
|
||||
CommunityGuidelinesLink: (
|
||||
<a href="/community_guidelines">
|
||||
<FormattedMessage id="report.CommunityGuidelinesLinkText" />
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
required
|
||||
elementWrapperClassName="report-modal-field"
|
||||
label={null}
|
||||
name="report_category"
|
||||
options={REPORT_OPTIONS.map(option => ({
|
||||
value: option.value,
|
||||
label: this.props.intl.formatMessage(option.label),
|
||||
key: option.value
|
||||
}))}
|
||||
validationErrors={{
|
||||
isDefaultRequiredValue: this.props.intl.formatMessage({
|
||||
id: 'report.reasonMissing'
|
||||
})
|
||||
}}
|
||||
value={this.state.category}
|
||||
onChange={this.handleCategorySelect}
|
||||
/>
|
||||
<TextArea
|
||||
required
|
||||
className="report-text"
|
||||
elementWrapperClassName="report-modal-field"
|
||||
label={null}
|
||||
name="notes"
|
||||
placeholder={this.lookupPrompt(this.state.category)}
|
||||
validationErrors={{
|
||||
isDefaultRequiredValue: this.props.intl.formatMessage({
|
||||
id: 'report.textMissing'
|
||||
}),
|
||||
maxLength: this.props.intl.formatMessage({id: 'report.tooLongError'}),
|
||||
minLength: this.props.intl.formatMessage({id: 'report.tooShortError'})
|
||||
}}
|
||||
validations={{
|
||||
maxLength: 500,
|
||||
minLength: 20
|
||||
}}
|
||||
value={this.state.notes}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isError && (
|
||||
<div className="error-text">
|
||||
<FormattedMessage id="report.error" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<FlexRow className="action-buttons">
|
||||
<div className="action-buttons-overflow-fix">
|
||||
{isConfirmed ? (
|
||||
<Button
|
||||
className="action-button submit-button"
|
||||
type="button"
|
||||
onClick={onRequestClose}
|
||||
>
|
||||
<div className="action-button-text">
|
||||
<FormattedMessage id="general.close" />
|
||||
</div>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
className={classNames(
|
||||
'action-button',
|
||||
'submit-button',
|
||||
{disabled: !submitEnabled}
|
||||
)}
|
||||
{...submitDisabledParam}
|
||||
key="submitButton"
|
||||
type="submit"
|
||||
>
|
||||
{isWaiting ? (
|
||||
<div className="action-button-text">
|
||||
<Spinner mode="smooth" />
|
||||
<FormattedMessage id="report.sending" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="action-button-text">
|
||||
<FormattedMessage id="report.send" />
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</FlexRow>
|
||||
</Form>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReportModal.propTypes = {
|
||||
intl: intlShape,
|
||||
isConfirmed: PropTypes.bool,
|
||||
isError: PropTypes.bool,
|
||||
isOpen: PropTypes.bool,
|
||||
isWaiting: PropTypes.bool,
|
||||
onReport: PropTypes.func,
|
||||
onRequestClose: PropTypes.func,
|
||||
type: PropTypes.string
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isConfirmed: state.preview.status.report === previewActions.Status.FETCHED,
|
||||
isError: state.preview.status.report === previewActions.Status.ERROR,
|
||||
isWaiting: state.preview.status.report === previewActions.Status.FETCHING
|
||||
});
|
||||
|
||||
const mapDispatchToProps = () => ({});
|
||||
|
||||
const ConnectedReportModal = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ReportModal);
|
||||
|
||||
module.exports = injectIntl(ConnectedReportModal);
|
109
src/components/modal/report/modal.scss
Normal file
109
src/components/modal/report/modal.scss
Normal file
|
@ -0,0 +1,109 @@
|
|||
@import "../../../colors";
|
||||
@import "../../../frameless";
|
||||
|
||||
.mod-report * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.mod-report {
|
||||
margin: 100px auto;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
width: 36.25rem; /* 580px; */
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.report-modal-header {
|
||||
border-radius: 1rem 1rem 0 0;
|
||||
box-shadow: inset 0 -1px 0 0 $ui-coral-dark;
|
||||
background-color: $ui-coral;
|
||||
padding-top: .75rem;
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.report-content-label {
|
||||
text-align: center;
|
||||
color: $type-white;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.report-modal-content {
|
||||
margin: 1rem auto;
|
||||
width: 80%;
|
||||
font-size: .875rem;
|
||||
|
||||
.instructions {
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.received {
|
||||
margin: 0 auto;
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
line-height: 1.65rem;
|
||||
|
||||
.received-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.error-text {
|
||||
margin-top: .9375rem;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
$arrow-border-width: 1rem;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 100%; /* position to the right of parent */
|
||||
margin-left: $arrow-border-width;
|
||||
border: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
background-color: $ui-orange;
|
||||
padding: 1rem;
|
||||
min-width: 12rem;
|
||||
max-width: 18.75rem;
|
||||
min-height: 1rem;
|
||||
overflow: visible;
|
||||
color: $type-white;
|
||||
|
||||
/* arrow on box that points to the left */
|
||||
&:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: -$arrow-border-width / 2;
|
||||
|
||||
transform: rotate(45deg);
|
||||
|
||||
border-bottom: 1px solid $active-gray;
|
||||
border-left: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
|
||||
background-color: $ui-orange;
|
||||
width: $arrow-border-width;
|
||||
height: $arrow-border-width;
|
||||
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.report-modal-field {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-group.has-error {
|
||||
.textarea, select {
|
||||
border: 1px solid $ui-orange;
|
||||
}
|
||||
}
|
||||
|
||||
.report-text .textarea {
|
||||
margin-bottom: 0;
|
||||
}
|
|
@ -33,20 +33,28 @@ const Navigation = () => (
|
|||
</li>
|
||||
<li className="li-right mod-2018">
|
||||
<ul className="li-right-ul mod-2018">
|
||||
<li className="link info">
|
||||
<li className="link expect">
|
||||
<a
|
||||
className="link-a"
|
||||
href="#info"
|
||||
href="/conference/2018/expect"
|
||||
>
|
||||
Registration Info
|
||||
What to Expect
|
||||
</a>
|
||||
</li>
|
||||
<li className="link questions">
|
||||
<li className="link plan">
|
||||
<a
|
||||
className="link-a"
|
||||
href="#questions"
|
||||
href="/conference/2018/plan"
|
||||
>
|
||||
Questions
|
||||
Plan Your Visit
|
||||
</a>
|
||||
</li>
|
||||
<li className="link schedule">
|
||||
<a
|
||||
className="link-a"
|
||||
href="/conference/2018/schedule"
|
||||
>
|
||||
Schedule
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{
|
||||
"id": 128283902498,
|
||||
"headline": "Update to Scratch Offline Editor",
|
||||
"copy": "We’ve released an update to Offline Editor which fixed bugs affecting Linux users.",
|
||||
"copy": "We’ve released an update to Offline Editor which fixed bugs affecting Windows users.",
|
||||
"url": "https://scratch.mit.edu/news#128283902498",
|
||||
"image": "https://33.media.tumblr.com/695b93f4ab74c68feaef1fe03baebdd5/tumblr_inline_n0xubtT0vU1szpavb.png"
|
||||
},
|
||||
|
|
48
src/components/os-chooser/os-chooser.jsx
Normal file
48
src/components/os-chooser/os-chooser.jsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
const classNames = require('classnames');
|
||||
const injectIntl = require('react-intl').injectIntl;
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
const Button = require('../../components/forms/button.jsx');
|
||||
|
||||
const OS_ENUM = require('../../components/extension-landing/os-enum.js');
|
||||
|
||||
require('./os-chooser.scss');
|
||||
|
||||
const OSChooser = props => (
|
||||
<div className="os-chooser">
|
||||
<FlexRow className="inner">
|
||||
<FormattedMessage id="oschooser.choose" />
|
||||
<Button
|
||||
className={classNames({active: props.currentOS === OS_ENUM.WINDOWS})}
|
||||
onClick={() => // eslint-disable-line react/jsx-no-bind
|
||||
props.handleSetOS(OS_ENUM.WINDOWS)
|
||||
}
|
||||
>
|
||||
<img src="/svgs/extensions/windows.svg" />
|
||||
Windows
|
||||
</Button>
|
||||
<Button
|
||||
className={classNames({active: props.currentOS === OS_ENUM.MACOS})}
|
||||
onClick={() => // eslint-disable-line react/jsx-no-bind
|
||||
props.handleSetOS(OS_ENUM.MACOS)
|
||||
}
|
||||
>
|
||||
<img src="/svgs/extensions/mac.svg" />
|
||||
macOS
|
||||
</Button>
|
||||
</FlexRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
OSChooser.propTypes = {
|
||||
currentOS: PropTypes.string,
|
||||
handleSetOS: PropTypes.func
|
||||
};
|
||||
|
||||
const wrappedOSChooser = injectIntl(OSChooser);
|
||||
|
||||
module.exports = wrappedOSChooser;
|
40
src/components/os-chooser/os-chooser.scss
Normal file
40
src/components/os-chooser/os-chooser.scss
Normal file
|
@ -0,0 +1,40 @@
|
|||
@import "../../colors";
|
||||
|
||||
.os-chooser {
|
||||
display: flex;
|
||||
position: sticky;
|
||||
top: 50px;
|
||||
z-index: 9;
|
||||
box-shadow: 0 0 3px $box-shadow-gray;
|
||||
background-color: $ui-white;
|
||||
padding: 0;
|
||||
height: 5rem;
|
||||
|
||||
.inner {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-right: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: flex;
|
||||
margin-right: 1rem;
|
||||
border-radius: 1.6rem;
|
||||
background-color: $active-gray;
|
||||
padding: .5rem 1.1rem;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
img {
|
||||
margin-right: .3rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $ui-blue;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1243,9 +1243,9 @@ class EmailStep extends React.Component {
|
|||
validations="equalsField:user.email"
|
||||
/>
|
||||
<Checkbox
|
||||
value
|
||||
help={null}
|
||||
name="subscribe"
|
||||
value={false}
|
||||
valueLabel={
|
||||
this.props.intl.formatMessage({id: 'registration.optIn'})
|
||||
}
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const ThumbnailColumn = require('../../components/thumbnailcolumn/thumbnailcolumn.jsx');
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
|
||||
require('./remixlist.scss');
|
||||
|
||||
/*
|
||||
* Container for a list of project remixes
|
||||
*/
|
||||
const RemixList = props => (
|
||||
<FlexRow className={classNames('remix-list', props.className)}>
|
||||
<h1>Remixes</h1>
|
||||
{props.items.length === 0 ? (
|
||||
<span>No remixes</span>
|
||||
) : (
|
||||
<ThumbnailColumn
|
||||
cards
|
||||
showAvatar
|
||||
itemType="preview"
|
||||
items={props.items.slice(0, 5)}
|
||||
showFavorites={false}
|
||||
showLoves={false}
|
||||
showViews={false}
|
||||
/>
|
||||
)}
|
||||
</FlexRow>
|
||||
);
|
||||
|
||||
RemixList.propTypes = {
|
||||
className: PropTypes.string,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||
};
|
||||
|
||||
module.exports = RemixList;
|
|
@ -1,17 +0,0 @@
|
|||
.remix-list {
|
||||
flex-direction: column;
|
||||
|
||||
.project {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.creator-image img {
|
||||
max-width: 2rem;
|
||||
max-height: 2rem;
|
||||
}
|
||||
|
||||
.thumbnail-column {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./share-banner.scss');
|
||||
|
||||
const ShareBanner = props => (
|
||||
<div className={classNames('shareBanner', props.className)}>
|
||||
<div className="inner">
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
ShareBanner.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = ShareBanner;
|
|
@ -1,10 +0,0 @@
|
|||
@import "../../colors";
|
||||
|
||||
$navigation-height: 50px;
|
||||
|
||||
.shareBanner {
|
||||
background-color: $ui-orange-25percent;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
color: $ui-orange;
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
}
|
||||
|
||||
.social-message.mod-unread {
|
||||
background-color: lighten($ui-blue, 40);
|
||||
background-color: $ui-blue-10percent;
|
||||
}
|
||||
|
||||
.social-message.mod-unread .social-message-icon {
|
||||
|
@ -41,7 +41,7 @@ a.social-messages-profile-link {
|
|||
color: $type-gray;
|
||||
|
||||
&:hover {
|
||||
color: darken($type-gray, 10);
|
||||
color: $link-blue;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,29 @@
|
|||
const range = require('lodash.range');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./spinner.scss');
|
||||
|
||||
// Adapted from http://tobiasahlin.com/spinkit/
|
||||
const Spinner = () => (
|
||||
<div className="spinner">
|
||||
{range(1, 13).map(id => (
|
||||
<div
|
||||
className={`circle${id} circle`}
|
||||
key={`circle${id}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
const Spinner = ({
|
||||
mode
|
||||
}) => {
|
||||
const spinnerClassName = (mode === 'smooth' ? 'spinner-smooth' : 'spinner');
|
||||
const spinnerDivCount = (mode === 'smooth' ? 24 : 12);
|
||||
return (
|
||||
<div className={spinnerClassName}>
|
||||
{range(1, spinnerDivCount + 1).map(id => (
|
||||
<div
|
||||
className={`circle${id} circle`}
|
||||
key={`circle${id}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Spinner.propTypes = {
|
||||
mode: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = Spinner;
|
||||
|
|
|
@ -18,13 +18,13 @@
|
|||
animation: circleFadeDelay 1.2s infinite ease-in-out both;
|
||||
margin: 0 auto;
|
||||
border-radius: 100%;
|
||||
background-color: darken($ui-white, 8%);
|
||||
background-color: $ui-gray;
|
||||
width: 15%;
|
||||
height: 15%;
|
||||
content: "";
|
||||
|
||||
.white & {
|
||||
background-color: darken($ui-blue, 8%);
|
||||
background-color: $ui-blue-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@
|
|||
transform: rotate($rotation);
|
||||
|
||||
&:before {
|
||||
animation-delay: $delay;
|
||||
animation-delay: $delay;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,5 +54,65 @@
|
|||
|
||||
40% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*********************/
|
||||
/* type === "smooth" */
|
||||
/*********************/
|
||||
|
||||
.spinner-smooth {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
.circle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&:before {
|
||||
display: block;
|
||||
animation: circleFadeDelaySmooth 1.8s infinite ease-in-out both;
|
||||
margin: 0 auto;
|
||||
border-radius: 100%;
|
||||
background-color: $ui-white;
|
||||
width: 30%;
|
||||
height: 20%;
|
||||
content: "";
|
||||
|
||||
.white & {
|
||||
background-color: darken($ui-blue, 8%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 1 through 24 {
|
||||
$rotation: 15deg * ($i - 1);
|
||||
$delay: -1.9s + $i * .075;
|
||||
|
||||
.circle#{$i} {
|
||||
transform: rotate($rotation);
|
||||
|
||||
&:before {
|
||||
animation-delay: $delay;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@keyframes circleFadeDelaySmooth {
|
||||
0%,
|
||||
35% {
|
||||
opacity: 0;
|
||||
},
|
||||
40% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
30
src/components/steps/step.jsx
Normal file
30
src/components/steps/step.jsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
|
||||
require('./steps.scss');
|
||||
|
||||
const Step = props => (
|
||||
<div className="step">
|
||||
{(props.compact || props.number) &&
|
||||
<FlexRow className="step-number-row">
|
||||
{props.number && <div className="step-number">{props.number}</div>}
|
||||
{props.compact && <FlexRow className="step-content">{props.children}</FlexRow>}
|
||||
</FlexRow>
|
||||
}
|
||||
{!props.compact &&
|
||||
<div className="step-content">
|
||||
{props.children}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
Step.propTypes = {
|
||||
children: PropTypes.node,
|
||||
compact: PropTypes.bool,
|
||||
number: PropTypes.number
|
||||
};
|
||||
|
||||
module.exports = Step;
|
21
src/components/steps/steps.jsx
Normal file
21
src/components/steps/steps.jsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const classNames = require('classnames');
|
||||
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
|
||||
require('./steps.scss');
|
||||
|
||||
const Steps = props => (
|
||||
<FlexRow className={classNames('steps', props.className)}>
|
||||
{/* TODO: Should this component do something with automatically numbering individual steps? */}
|
||||
{props.children}
|
||||
</FlexRow>
|
||||
);
|
||||
|
||||
Steps.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = Steps;
|
59
src/components/steps/steps.scss
Normal file
59
src/components/steps/steps.scss
Normal file
|
@ -0,0 +1,59 @@
|
|||
@import "../../colors";
|
||||
|
||||
.steps {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.step {
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
|
||||
.step-number-row {
|
||||
padding-bottom: 1.5rem;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
.step-content {
|
||||
text-align: left;
|
||||
align-items: flex-start;
|
||||
|
||||
.step-description {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.step-number {
|
||||
display: inline-flex;
|
||||
border-radius: 2rem;
|
||||
background-color: $ui-blue;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
color: $ui-white;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.step-content {
|
||||
display: flex;
|
||||
padding: 0 2rem;
|
||||
text-align: center;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
.step-image {
|
||||
height: 10rem;
|
||||
|
||||
img {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ const ThumbnailColumn = props => (
|
|||
if (props.itemType === 'preview') {
|
||||
return (
|
||||
<Thumbnail
|
||||
avatar={`https://cdn2.scratch.mit.edu/get_image/user/${item.author.i}_32x32.png`}
|
||||
avatar={`https://cdn2.scratch.mit.edu/get_image/user/${item.author.id}_32x32.png`}
|
||||
creator={item.author.username}
|
||||
favorites={item.stats.favorites}
|
||||
href={href}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
.thumbnail {
|
||||
margin: 7px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 3px $box-shadow-gray;
|
||||
box-shadow: 0 0 0 1px $active-gray;
|
||||
background-color: $ui-white;
|
||||
padding-bottom: 4px;
|
||||
width: $thumbnail-width;
|
||||
|
|
|
@ -106,7 +106,7 @@
|
|||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($link-blue, 40%);
|
||||
background-color: $ui-blue-10percent;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,31 +37,31 @@
|
|||
|
||||
&.blue {
|
||||
#{$color-bars} {
|
||||
background-color: $splash-blue;
|
||||
background-color: $ui-blue;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $splash-blue;
|
||||
color: $ui-blue;
|
||||
}
|
||||
}
|
||||
|
||||
&.green {
|
||||
#{$color-bars} {
|
||||
background-color: $splash-green;
|
||||
background-color: $ui-aqua;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $splash-green;
|
||||
color: $ui-aqua;
|
||||
}
|
||||
}
|
||||
|
||||
&.pink {
|
||||
#{$color-bars} {
|
||||
background-color: $splash-pink;
|
||||
background-color: $ui-purple;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $splash-pink;
|
||||
color: $ui-purple;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
22
src/init.js
22
src/init.js
|
@ -36,3 +36,25 @@ const Raven = require('raven-js');
|
|||
|
||||
window._locale = updateLocale();
|
||||
})();
|
||||
|
||||
/**
|
||||
* -----------------------------------------------------------------------------
|
||||
* Console warning
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
(() => {
|
||||
window.onload = function () {
|
||||
/* eslint-disable no-console */
|
||||
console.log('%cStop!', 'color: #F00; font-size: 30px; -webkit-text-stroke: 1px black; font-weight:bold');
|
||||
console.log(
|
||||
'This is part of your browser intended for developers. ' +
|
||||
'If someone told you to copy-and-paste something here, ' +
|
||||
'don\'t do it! It could allow them to take over your ' +
|
||||
'Scratch account, delete all of your projects, or do many ' +
|
||||
'other harmful things. If you don\'t understand what exactly ' +
|
||||
'you are doing here, you should close this window without doing ' +
|
||||
'anything.'
|
||||
);
|
||||
/* eslint-enable no-console */
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"general.birthMonth": "Birth Month",
|
||||
"general.birthYear": "Birth Year",
|
||||
"general.donate": "Donate",
|
||||
"general.close": "Close",
|
||||
"general.collaborators": "Collaborators",
|
||||
"general.community": "Community",
|
||||
"general.confirmEmail": "Confirm Email",
|
||||
|
@ -52,6 +53,7 @@
|
|||
"general.noDeletionDescription": "Your account was scheduled for deletion but you logged in. Your account has been reactivated. If you didn’t request for your account to be deleted, you should {resetLink} to make sure your account is secure.",
|
||||
"general.noDeletionLink": "change your password",
|
||||
"general.notRequired": "Not Required",
|
||||
"general.okay": "Okay",
|
||||
"general.other": "Other",
|
||||
"general.offlineEditor": "Offline Editor",
|
||||
"general.password": "Password",
|
||||
|
@ -103,6 +105,16 @@
|
|||
|
||||
"navigation.signOut": "Sign out",
|
||||
|
||||
"extensionHeader.requirements": "Requirements",
|
||||
|
||||
"oschooser.choose": "Choose your OS:",
|
||||
|
||||
"installScratchLink.installHeaderTitle": "Install Scratch Link",
|
||||
"installScratchLink.downloadAndInstall": "Download and install Scratch Link.",
|
||||
"installScratchLink.windowsDownload": "Download for Windows",
|
||||
"installScratchLink.macosDownload": "Download for macOS",
|
||||
"installScratchLink.startScratchLink": "Start Scratch Link and make sure it is running. It should appear in your toolbar.",
|
||||
|
||||
"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?",
|
||||
|
@ -159,5 +171,34 @@
|
|||
"registration.welcomeStepPrompt": "To get started, click on the button below.",
|
||||
"registration.welcomeStepTitle": "Hurray! Welcome to Scratch!",
|
||||
|
||||
"thumbnail.by": "by"
|
||||
}
|
||||
"thumbnail.by": "by",
|
||||
"report.error": "Something went wrong when trying to send your message. Please try again.",
|
||||
"report.project": "Report Project",
|
||||
"report.projectInstructions": "From the dropdown below, please select the reason why you feel this project is disrespectful or inappropriate or otherwise breaks the {CommunityGuidelinesLink}.",
|
||||
"report.CommunityGuidelinesLinkText": "Scratch Community Guidelines",
|
||||
"report.reasonPlaceHolder": "Select a reason",
|
||||
"report.reasonCopy": "Exact Copy of Project",
|
||||
"report.reasonUncredited": "Uses Image/Music Without Credit",
|
||||
"report.reasonScary": "Too Violent or Scary",
|
||||
"report.reasonLanguage": "Inappropriate Language",
|
||||
"report.reasonMusic": "Inappropriate Music",
|
||||
"report.reasonMissing": "Please select a reason",
|
||||
"report.reasonImage": "Inappropriate Images",
|
||||
"report.reasonPersonal": "Sharing Personal Contact Information",
|
||||
"report.receivedHeader": "We have received your report!",
|
||||
"report.receivedBody": "The Scratch Team will review the project based on the Scratch community guidelines.",
|
||||
"report.promptPlaceholder": "Select a reason why above.",
|
||||
"report.promptCopy": "Please provide a link to the original project",
|
||||
"report.promptUncredited": "Please provide links to the uncredited content",
|
||||
"report.promptScary": "Please say why the project is too violent or scary",
|
||||
"report.promptLanguage": "Please say where the inappropriate language occurs in the project (For example: Notes & Credits, sprite name, project text, etc.)",
|
||||
"report.promptMusic": "Please say the name of the audio file with the inappropriate music",
|
||||
"report.promptPersonal": "Please say where the personal contact information is shared (For example: Notes & Credits, sprite name, project text, etc.)",
|
||||
"report.promptGuidelines": "Please be specific about why this project does not follow our Community Guidelines",
|
||||
"report.promptImage": "Please say the name of the sprite or the backdrop with the inappropriate image",
|
||||
"report.tooLongError": "That's too long! Please find a way to shorten your text.",
|
||||
"report.tooShortError": "That's too short. Please describe in detail what's inappropriate or disrespectful about the project.",
|
||||
"report.send": "Send",
|
||||
"report.sending": "Sending...",
|
||||
"report.textMissing": "Please tell us why you are reporting this project"
|
||||
}
|
||||
|
|
30
src/lib/decorate-text.jsx
Normal file
30
src/lib/decorate-text.jsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
const React = require('react'); // eslint-disable-line
|
||||
const reactStringReplace = require('react-string-replace');
|
||||
|
||||
/**
|
||||
* Helper method that replaces @mentions and #hashtags in plain text
|
||||
*
|
||||
* @param {string} text string to convert
|
||||
* @return {string} string with links for @mentions and #hashtags
|
||||
*/
|
||||
module.exports = text => {
|
||||
let replacedText;
|
||||
|
||||
// Match @-mentions (username is alphanumeric, underscore and dash)
|
||||
replacedText = reactStringReplace(text, /@([\w-]+)/g, (match, i) => (
|
||||
<a
|
||||
href={`/users/${match}`}
|
||||
key={match + i}
|
||||
>@{match}</a>
|
||||
));
|
||||
|
||||
// Match hashtags
|
||||
replacedText = reactStringReplace(replacedText, /(#[\w-]+)/g, (match, i) => (
|
||||
<a
|
||||
href={`/search/projects?q=${match}`}
|
||||
key={match + i}
|
||||
>{match}</a>
|
||||
));
|
||||
|
||||
return replacedText;
|
||||
};
|
36
src/lib/extensions.js
Normal file
36
src/lib/extensions.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
const EXTENSION_INFO = {
|
||||
microbit: {
|
||||
name: 'micro:bit',
|
||||
icon: 'extension-microbit.svg',
|
||||
hasStatus: true
|
||||
},
|
||||
music: {
|
||||
l10nId: 'preview.musicExtensionChip',
|
||||
icon: 'extension-music.svg'
|
||||
},
|
||||
pen: {
|
||||
l10nId: 'preview.penExtensionChip',
|
||||
icon: 'extension-pen.svg'
|
||||
},
|
||||
speak: {
|
||||
name: 'Amazon Polly'
|
||||
},
|
||||
speech: {
|
||||
l10nId: 'preview.speechExtensionChip'
|
||||
},
|
||||
translate: {
|
||||
l10nId: 'preview.translateExtensionChip',
|
||||
icon: 'extension-translate.svg'
|
||||
},
|
||||
videoSensing: {
|
||||
l10nId: 'preview.videoMotionChip',
|
||||
icon: 'extension-videomotion.svg'
|
||||
},
|
||||
wedo2: {
|
||||
name: 'LEGO WeDo 2.0',
|
||||
icon: 'extension-wedo2.svg',
|
||||
hasStatus: true
|
||||
}
|
||||
};
|
||||
|
||||
export default EXTENSION_INFO;
|
|
@ -14,11 +14,13 @@ require('../main.scss');
|
|||
|
||||
/**
|
||||
* Function to render views into a full page
|
||||
* @param {object} jsx jsx component of the view
|
||||
* @param {object} element html element to render to on the template
|
||||
* @param {array} reducers list of view-specific reducers
|
||||
* @param {object} jsx jsx component of the view
|
||||
* @param {object} element html element to render to on the template
|
||||
* @param {array} reducers list of view-specific reducers
|
||||
* @param {object} initialState optional initialState for store
|
||||
* @param {bool} enhancer whether or not to apply redux-throttle middleware
|
||||
*/
|
||||
const render = (jsx, element, reducers) => {
|
||||
const render = (jsx, element, reducers, initialState, enhancer) => {
|
||||
// Get locale and messages from global namespace (see "init.js")
|
||||
let locale = window._locale || 'en';
|
||||
let messages = {};
|
||||
|
@ -35,9 +37,20 @@ const render = (jsx, element, reducers) => {
|
|||
}
|
||||
|
||||
const allReducers = reducer(reducers);
|
||||
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || redux.compose;
|
||||
const enhancers = enhancer ?
|
||||
composeEnhancers(
|
||||
redux.applyMiddleware(thunk),
|
||||
enhancer
|
||||
) :
|
||||
composeEnhancers(
|
||||
redux.applyMiddleware(thunk)
|
||||
);
|
||||
const store = redux.createStore(
|
||||
allReducers,
|
||||
redux.applyMiddleware(thunk)
|
||||
initialState || {},
|
||||
enhancers
|
||||
);
|
||||
|
||||
// Render view component
|
||||
|
|
26
src/lib/storage.js
Normal file
26
src/lib/storage.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import ScratchStorage from 'scratch-storage';
|
||||
|
||||
const PROJECT_SERVER = 'https://projects.scratch.mit.edu';
|
||||
|
||||
/**
|
||||
* Wrapper for ScratchStorage which adds default web sources.
|
||||
* @todo make this more configurable
|
||||
*/
|
||||
class Storage extends ScratchStorage {
|
||||
constructor () {
|
||||
super();
|
||||
this.addWebSource(
|
||||
[this.AssetType.Project],
|
||||
projectAsset => {
|
||||
const [projectId, revision] = projectAsset.assetId.split('.');
|
||||
return revision ?
|
||||
`${PROJECT_SERVER}/internalapi/project/${projectId}/get/${revision}` :
|
||||
`${PROJECT_SERVER}/internalapi/project/${projectId}/get/`;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const storage = new Storage();
|
||||
|
||||
export default storage;
|
9
src/lib/truncate.js
Normal file
9
src/lib/truncate.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
const lodashTruncate = require('lodash.truncate');
|
||||
|
||||
/*
|
||||
* Function that applies regex for word boundaries, replaces removed string
|
||||
* with indication of ellipsis (...)
|
||||
*/
|
||||
module.exports.truncateAtWordBoundary = (str, length) => (
|
||||
lodashTruncate(str, {length: length, separator: /[.,:;]*\s+/})
|
||||
);
|
|
@ -6,7 +6,7 @@ html,
|
|||
body {
|
||||
display: block;
|
||||
margin: 0;
|
||||
background-color: darken($ui-blue, 8%);
|
||||
background-color: $ui-blue-dark;
|
||||
padding: 0;
|
||||
color: $type-gray;
|
||||
font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
|
||||
|
@ -22,34 +22,34 @@ h4 {
|
|||
padding: 0;
|
||||
|
||||
color: $header-gray;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 900;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 500;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h5 {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
font-size: .85rem;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
p {
|
||||
|
@ -69,12 +69,12 @@ p {
|
|||
|
||||
margin: 1.5em 0;
|
||||
border: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
background-color: lighten($ui-blue, 40);
|
||||
border-radius: .5rem;
|
||||
background-color: $ui-blue-10percent;
|
||||
padding: 1.25em;
|
||||
|
||||
&.orange {
|
||||
background-color: lighten($ui-orange, 30);
|
||||
background-color: $ui-orange-10percent;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,14 +85,14 @@ p {
|
|||
|
||||
b,
|
||||
strong {
|
||||
font-weight: 500;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
a {
|
||||
cursor: pointer;
|
||||
color: $ui-blue;
|
||||
font-weight: 500;
|
||||
font-weight: bold;
|
||||
|
||||
&:link,
|
||||
&:visited,
|
||||
|
@ -103,17 +103,15 @@ a {
|
|||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: darken($ui-blue, 15);
|
||||
color: $ui-blue-dark;
|
||||
}
|
||||
}
|
||||
|
||||
/* Classes */
|
||||
.empty {
|
||||
$bg-blue: #d9edf7;
|
||||
$bg-blue-accent: #bce8f1;
|
||||
border: 1px solid $bg-blue-accent;
|
||||
border: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
background-color: $bg-blue;
|
||||
background-color: $ui-blue-10percent;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
line-height: 2rem;
|
||||
|
@ -136,11 +134,11 @@ p {
|
|||
|
||||
p {
|
||||
font-size: 1rem;
|
||||
font-weight: 300;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: lighten($ui-blue, 30);
|
||||
background-color: $ui-blue-25percent;
|
||||
}
|
||||
|
||||
ol,
|
||||
|
@ -149,7 +147,7 @@ ul {
|
|||
|
||||
line-height: 1.5em;
|
||||
font-size: 1rem;
|
||||
font-weight: 300;
|
||||
font-weight: normal;
|
||||
|
||||
li {
|
||||
margin: .75em 0;
|
||||
|
@ -159,10 +157,10 @@ ul {
|
|||
dl {
|
||||
line-height: 1.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 300;
|
||||
font-weight: normal;
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dd {
|
||||
|
|
|
@ -88,7 +88,7 @@ module.exports.getDaySchedule = day => (dispatch => {
|
|||
cleanedRow[columns[i]] = cur[i];
|
||||
}
|
||||
}
|
||||
cleanedRow.uri = `/conference/2016/${cleanedRow.rowid}/details`;
|
||||
cleanedRow.uri = `/conference/2018/${cleanedRow.rowid}/details`;
|
||||
const timeSlot = cleanedRow.Chunk + cleanedRow.Start;
|
||||
if (typeof prev.timeSlots[timeSlot] === 'undefined') {
|
||||
prev.timeSlots[timeSlot] = [cleanedRow];
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
const defaults = require('lodash.defaults');
|
||||
const keyMirror = require('keymirror');
|
||||
const async = require('async');
|
||||
const merge = require('lodash.merge');
|
||||
|
||||
const api = require('../lib/api');
|
||||
const log = require('../lib/log');
|
||||
|
@ -13,16 +16,28 @@ module.exports.Status = keyMirror({
|
|||
module.exports.getInitialState = () => ({
|
||||
status: {
|
||||
project: module.exports.Status.NOT_FETCHED,
|
||||
credit: module.exports.Status.NOT_FETCHED,
|
||||
comments: module.exports.Status.NOT_FETCHED,
|
||||
faved: module.exports.Status.NOT_FETCHED,
|
||||
loved: module.exports.Status.NOT_FETCHED,
|
||||
original: module.exports.Status.NOT_FETCHED,
|
||||
parent: module.exports.Status.NOT_FETCHED,
|
||||
remixes: module.exports.Status.NOT_FETCHED,
|
||||
studios: module.exports.Status.NOT_FETCHED
|
||||
report: module.exports.Status.NOT_FETCHED,
|
||||
projectStudios: module.exports.Status.NOT_FETCHED,
|
||||
curatedStudios: module.exports.Status.NOT_FETCHED,
|
||||
studioRequests: {}
|
||||
},
|
||||
projectInfo: {},
|
||||
remixes: [],
|
||||
credit: {},
|
||||
comments: [],
|
||||
studios: []
|
||||
replies: {},
|
||||
faved: false,
|
||||
loved: false,
|
||||
original: {},
|
||||
parent: {},
|
||||
projectStudios: [],
|
||||
curatedStudios: [],
|
||||
currentStudioIds: []
|
||||
});
|
||||
|
||||
module.exports.previewReducer = (state, action) => {
|
||||
|
@ -39,18 +54,58 @@ module.exports.previewReducer = (state, action) => {
|
|||
return Object.assign({}, state, {
|
||||
remixes: action.items
|
||||
});
|
||||
case 'SET_CREDIT':
|
||||
case 'SET_ORIGINAL':
|
||||
return Object.assign({}, state, {
|
||||
credit: action.info
|
||||
original: action.info
|
||||
});
|
||||
case 'SET_PARENT':
|
||||
return Object.assign({}, state, {
|
||||
parent: action.info
|
||||
});
|
||||
case 'SET_PROJECT_STUDIOS':
|
||||
// also initialize currentStudioIds, to keep track of which studios
|
||||
// the project is currently in.
|
||||
return Object.assign({}, state, {
|
||||
projectStudios: action.items,
|
||||
currentStudioIds: action.items.map(item => item.id)
|
||||
});
|
||||
case 'SET_CURATED_STUDIOS':
|
||||
return Object.assign({}, state, {curatedStudios: action.items});
|
||||
case 'ADD_PROJECT_TO_STUDIO':
|
||||
// add studio id to our studios-that-this-project-belongs-to set.
|
||||
return Object.assign({}, state, {
|
||||
currentStudioIds: state.currentStudioIds.concat(action.studioId)
|
||||
});
|
||||
case 'REMOVE_PROJECT_FROM_STUDIO':
|
||||
return Object.assign({}, state, {
|
||||
currentStudioIds: state.currentStudioIds.filter(item => (
|
||||
item !== action.studioId
|
||||
))
|
||||
});
|
||||
case 'SET_COMMENTS':
|
||||
return Object.assign({}, state, {
|
||||
comments: action.items
|
||||
comments: [...state.comments, ...action.items] // TODO: consider a different way of doing this?
|
||||
});
|
||||
case 'SET_REPLIES':
|
||||
return Object.assign({}, state, {
|
||||
replies: merge({}, state.replies, action.replies)
|
||||
});
|
||||
case 'SET_LOVED':
|
||||
return Object.assign({}, state, {
|
||||
loved: action.info
|
||||
});
|
||||
case 'SET_FAVED':
|
||||
return Object.assign({}, state, {
|
||||
faved: action.info
|
||||
});
|
||||
case 'SET_FETCH_STATUS':
|
||||
state = JSON.parse(JSON.stringify(state));
|
||||
state.status[action.infoType] = action.status;
|
||||
return state;
|
||||
case 'SET_STUDIO_FETCH_STATUS':
|
||||
state = JSON.parse(JSON.stringify(state));
|
||||
state.status.studioRequests[action.studioId] = action.status;
|
||||
return state;
|
||||
case 'ERROR':
|
||||
log.error(action.error);
|
||||
return state;
|
||||
|
@ -69,8 +124,23 @@ module.exports.setProjectInfo = info => ({
|
|||
info: info
|
||||
});
|
||||
|
||||
module.exports.setCreditInfo = info => ({
|
||||
type: 'SET_CREDIT',
|
||||
module.exports.setOriginalInfo = info => ({
|
||||
type: 'SET_ORIGINAL',
|
||||
info: info
|
||||
});
|
||||
|
||||
module.exports.setParentInfo = info => ({
|
||||
type: 'SET_PARENT',
|
||||
info: info
|
||||
});
|
||||
|
||||
module.exports.setFaved = info => ({
|
||||
type: 'SET_FAVED',
|
||||
info: info
|
||||
});
|
||||
|
||||
module.exports.setLoved = info => ({
|
||||
type: 'SET_LOVED',
|
||||
info: info
|
||||
});
|
||||
|
||||
|
@ -79,17 +149,57 @@ module.exports.setRemixes = items => ({
|
|||
items: items
|
||||
});
|
||||
|
||||
module.exports.setProjectStudios = items => ({
|
||||
type: 'SET_PROJECT_STUDIOS',
|
||||
items: items
|
||||
});
|
||||
|
||||
module.exports.setComments = items => ({
|
||||
type: 'SET_COMMENTS',
|
||||
items: items
|
||||
});
|
||||
|
||||
module.exports.setReplies = replies => ({
|
||||
type: 'SET_REPLIES',
|
||||
replies: replies
|
||||
});
|
||||
|
||||
module.exports.setCuratedStudios = items => ({
|
||||
type: 'SET_CURATED_STUDIOS',
|
||||
items: items
|
||||
});
|
||||
|
||||
module.exports.addProjectToStudio = studioId => ({
|
||||
type: 'ADD_PROJECT_TO_STUDIO',
|
||||
studioId: studioId
|
||||
});
|
||||
|
||||
module.exports.removeProjectFromStudio = studioId => ({
|
||||
type: 'REMOVE_PROJECT_FROM_STUDIO',
|
||||
studioId: studioId
|
||||
});
|
||||
|
||||
module.exports.setFetchStatus = (type, status) => ({
|
||||
type: 'SET_FETCH_STATUS',
|
||||
infoType: type,
|
||||
status: status
|
||||
});
|
||||
|
||||
module.exports.getProjectInfo = id => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHING));
|
||||
api({
|
||||
module.exports.setStudioFetchStatus = (studioId, status) => ({
|
||||
type: 'SET_STUDIO_FETCH_STATUS',
|
||||
studioId: studioId,
|
||||
status: status
|
||||
});
|
||||
|
||||
module.exports.getProjectInfo = (id, token) => (dispatch => {
|
||||
const opts = {
|
||||
uri: `/projects/${id}`
|
||||
}, (err, body) => {
|
||||
};
|
||||
if (token) {
|
||||
Object.assign(opts, {authentication: token});
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHING));
|
||||
api(opts, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setFetchStatus('project', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError(err));
|
||||
|
@ -105,26 +215,215 @@ module.exports.getProjectInfo = id => (dispatch => {
|
|||
});
|
||||
});
|
||||
|
||||
module.exports.getCreditInfo = id => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('credit', module.exports.Status.FETCHING));
|
||||
module.exports.getOriginalInfo = id => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('original', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `/projects/${id}`
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setFetchStatus('credit', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setFetchStatus('original', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setFetchStatus('credit', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError('No credit info'));
|
||||
dispatch(module.exports.setFetchStatus('original', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError('No original info'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('credit', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setCreditInfo(body));
|
||||
dispatch(module.exports.setFetchStatus('original', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setOriginalInfo(body));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.getParentInfo = id => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('parent', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `/projects/${id}`
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setFetchStatus('parent', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setFetchStatus('parent', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError('No parent info'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('parent', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setParentInfo(body));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.getFavedStatus = (id, username, token) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('faved', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `/projects/${id}/favorites/user/${username}`,
|
||||
authentication: token
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setFetchStatus('faved', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setFetchStatus('faved', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError('No faved info'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('faved', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setFaved(body.userFavorite));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.getTopLevelComments = (id, offset) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `/comments/project/${id}`,
|
||||
params: {offset: offset || 0}
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError('No comment info'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setComments(body));
|
||||
dispatch(module.exports.getReplies(id, body.map(comment => comment.id)));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.getReplies = (projectId, commentIds) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.FETCHING));
|
||||
const fetchedReplies = {};
|
||||
async.eachLimit(commentIds, 10, (parentId, callback) => {
|
||||
api({
|
||||
uri: `/comments/project/${projectId}/${parentId}`
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
return callback(`Error fetching comment replies: ${err}`);
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
return callback('No comment reply information');
|
||||
}
|
||||
fetchedReplies[parentId] = body;
|
||||
callback(null, body);
|
||||
});
|
||||
}, err => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setReplies(fetchedReplies));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.setFavedStatus = (faved, id, username, token) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('faved', module.exports.Status.FETCHING));
|
||||
if (faved) {
|
||||
api({
|
||||
uri: `/projects/${id}/favorites/user/${username}`,
|
||||
authentication: token,
|
||||
method: 'POST'
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setError('Set favorites returned no data'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('faved', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setFaved(body.userFavorite));
|
||||
});
|
||||
} else {
|
||||
api({
|
||||
uri: `/projects/${id}/favorites/user/${username}`,
|
||||
authentication: token,
|
||||
method: 'DELETE'
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setError('Set favorites returned no data'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('faved', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setFaved(false));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.getLovedStatus = (id, username, token) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('loved', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `/projects/${id}/loves/user/${username}`,
|
||||
authentication: token
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setFetchStatus('loved', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setFetchStatus('loved', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError('No loved info'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('loved', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setLoved(body.userLove));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.setLovedStatus = (loved, id, username, token) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('loved', module.exports.Status.FETCHING));
|
||||
if (loved) {
|
||||
api({
|
||||
uri: `/projects/${id}/loves/user/${username}`,
|
||||
authentication: token,
|
||||
method: 'POST'
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setError('Set loved returned no data'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('loved', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setLoved(body.userLove));
|
||||
});
|
||||
} else {
|
||||
api({
|
||||
uri: `/projects/${id}/loves/user/${username}`,
|
||||
authentication: token,
|
||||
method: 'DELETE'
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setError('Set loved returned no data'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('loved', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setLoved(body.userLove));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.getRemixes = id => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('remixes', module.exports.Status.FETCHING));
|
||||
api({
|
||||
|
@ -148,3 +447,141 @@ module.exports.getRemixes = id => (dispatch => {
|
|||
dispatch(module.exports.setRemixes(body));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.getProjectStudios = id => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('projectStudios', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `/projects/${id}/studios`
|
||||
}, (err, body, res) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setFetchStatus('projectStudios', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setFetchStatus('projectStudios', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError('No projectStudios info'));
|
||||
return;
|
||||
}
|
||||
if (res.statusCode === 404) { // NotFound
|
||||
body = [];
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('projectStudios', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setProjectStudios(body));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.getCuratedStudios = username => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('curatedStudios', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `/users/${username}/studios/curate`
|
||||
}, (err, body, res) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setFetchStatus('curatedStudios', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setFetchStatus('curatedStudios', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError('No curated studios info'));
|
||||
return;
|
||||
}
|
||||
if (res.statusCode === 404) { // NotFound
|
||||
body = [];
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('curatedStudios', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setCuratedStudios(body));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.addToStudio = (studioId, projectId, token) => (dispatch => {
|
||||
dispatch(module.exports.setStudioFetchStatus(studioId, module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `/studios/${studioId}/project/${projectId}`,
|
||||
authentication: token,
|
||||
method: 'POST'
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setError('Add to studio returned no data'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setStudioFetchStatus(studioId, module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.addProjectToStudio(studioId));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.leaveStudio = (studioId, projectId, token) => (dispatch => {
|
||||
dispatch(module.exports.setStudioFetchStatus(studioId, module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `/studios/${studioId}/project/${projectId}`,
|
||||
authentication: token,
|
||||
method: 'DELETE'
|
||||
}, (err, body) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setError('Leave studio returned no data'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setStudioFetchStatus(studioId, module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.removeProjectFromStudio(studioId));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.updateProject = (id, jsonData, username, token) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `/projects/${id}`,
|
||||
authentication: token,
|
||||
method: 'PUT',
|
||||
json: jsonData
|
||||
}, (err, body, res) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setFetchStatus('project', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined') {
|
||||
dispatch(module.exports.setFetchStatus('project', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError('No project info'));
|
||||
return;
|
||||
}
|
||||
if (res.statusCode === 500) { // InternalServer
|
||||
dispatch(module.exports.setFetchStatus('project', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError('API Internal Server Error'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setProjectInfo(body));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.reportProject = (id, jsonData) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('report', module.exports.Status.FETCHING));
|
||||
// scratchr2 will fail if no thumbnail base64 string provided. We don't yet have
|
||||
// a way to get the actual project thumbnail in www/gui, so for now just submit
|
||||
// a minimal base64 png string.
|
||||
defaults(jsonData, {
|
||||
thumbnail: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC' +
|
||||
'0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII='
|
||||
});
|
||||
api({
|
||||
host: '',
|
||||
uri: `/site-api/projects/all/${id}/report/`,
|
||||
method: 'POST',
|
||||
json: jsonData,
|
||||
useCsrf: true
|
||||
}, (err, body, res) => {
|
||||
if (err || res.statusCode !== 200) {
|
||||
dispatch(module.exports.setFetchStatus('report', module.exports.Status.ERROR));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('report', module.exports.Status.FETCHED));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -27,20 +27,6 @@
|
|||
"view": "communityblocks-interviews/communityblocks-interviews",
|
||||
"title": "Community Blocks Beta Tester Interviews"
|
||||
},
|
||||
{
|
||||
"name": "conference-details-2016",
|
||||
"pattern": "^/conference/2016/:id/details/?$",
|
||||
"routeAlias": "/conference(?!/201[4-5])",
|
||||
"view": "conference/2016/details/details",
|
||||
"title": "Event Details"
|
||||
},
|
||||
{
|
||||
"name": "conference-expectations-2016",
|
||||
"pattern": "^/conference/2016/expect/?$",
|
||||
"routeAlias": "/conference(?!/201[4-5])",
|
||||
"view": "conference/2016/expect/expect",
|
||||
"title": "What to Expect"
|
||||
},
|
||||
{
|
||||
"name": "conference-index",
|
||||
"pattern": "^/conference/?(\\?.*)?$",
|
||||
|
@ -58,27 +44,47 @@
|
|||
"viewportWidth": "device-width"
|
||||
},
|
||||
{
|
||||
"name": "conference-index-2016",
|
||||
"pattern": "^/conference/2016/?$",
|
||||
"name": "conference-details-2018",
|
||||
"pattern": "^/conference/2018/:id/details/?$",
|
||||
"routeAlias": "/conference(?!/201[4-5])",
|
||||
"view": "conference/2016/index/index",
|
||||
"view": "conference/2018/details/details",
|
||||
"title": "Event Details"
|
||||
},
|
||||
{
|
||||
"name": "conference-expectations-2018",
|
||||
"pattern": "^/conference/2018/expect/?$",
|
||||
"routeAlias": "/conference(?!/201[4-5])",
|
||||
"view": "conference/2018/expect/expect",
|
||||
"title": "What to Expect"
|
||||
},
|
||||
{
|
||||
"name": "conference-index-2018",
|
||||
"pattern": "^/conference/2018/?$",
|
||||
"routeAlias": "/conference(?!/201[4-5])",
|
||||
"view": "conference/2018/index/index",
|
||||
"title": "Scratch Conference",
|
||||
"viewportWidth": "device-width"
|
||||
},
|
||||
{
|
||||
"name": "conference-plan-2016",
|
||||
"pattern": "^/conference/2016/plan/?$",
|
||||
"name": "conference-plan-2018",
|
||||
"pattern": "^/conference/2018/plan/?$",
|
||||
"routeAlias": "/conference(?!/201[4-5])",
|
||||
"view": "conference/2016/plan/plan",
|
||||
"view": "conference/2018/plan/plan",
|
||||
"title": "Plan Your Visit"
|
||||
},
|
||||
{
|
||||
"name": "conference-schedule-2016",
|
||||
"pattern": "^/conference/2016/schedule/?$",
|
||||
"name": "conference-schedule-2018",
|
||||
"pattern": "^/conference/2018/schedule/?$",
|
||||
"routeAlias": "/conference(?!/201[4-5])",
|
||||
"view": "conference/2016/schedule/schedule",
|
||||
"view": "conference/2018/schedule/schedule",
|
||||
"title": "Conference Schedule"
|
||||
},
|
||||
{
|
||||
"name": "connect",
|
||||
"pattern": "^/connect/?$",
|
||||
"routeAlias": "/connect/?$",
|
||||
"redirect": "https://eepurl.com/cws7_f"
|
||||
},
|
||||
{
|
||||
"name": "credits",
|
||||
"pattern": "^/info/credits/?$",
|
||||
|
@ -193,17 +199,23 @@
|
|||
},
|
||||
{
|
||||
"name": "preview",
|
||||
"pattern": "^/preview/?(\\d+)?/?$",
|
||||
"pattern": "^/preview(/editor|(/\\d+(/editor|/fullscreen)?)?)?/?$",
|
||||
"routeAlias": "/preview/?$",
|
||||
"view": "preview/preview",
|
||||
"title": "Scratch 3.0 Preview"
|
||||
},
|
||||
{
|
||||
"name": "preview-faq",
|
||||
"pattern": "^/preview-faq/?$",
|
||||
"routeAlias": "/preview-faq/?$",
|
||||
"name": "3faq",
|
||||
"pattern": "^/3faq/?$",
|
||||
"routeAlias": "/3faq/?$",
|
||||
"view": "preview-faq/preview-faq",
|
||||
"title": "Scratch 3.0 Preview FAQ"
|
||||
"title": "Scratch 3.0 FAQ"
|
||||
},
|
||||
{
|
||||
"name": "preview-faq-redirect",
|
||||
"pattern": "^/preview-faq/?$",
|
||||
"routeAlias": "/preview-faq",
|
||||
"redirect": "/3faq"
|
||||
},
|
||||
{
|
||||
"name": "privacypolicy",
|
||||
|
@ -289,12 +301,32 @@
|
|||
"view": "wedo2/wedo2",
|
||||
"title": "LEGO WeDo 2.0"
|
||||
},
|
||||
{
|
||||
"name": "ev3",
|
||||
"pattern": "^/ev3/?$",
|
||||
"routeAlias": "/ev3/?$",
|
||||
"view": "ev3/ev3",
|
||||
"title": "LEGO MINDSTORMS EV3"
|
||||
},
|
||||
{
|
||||
"name": "microbit",
|
||||
"pattern": "^/microbit/?$",
|
||||
"routeAlias": "/microbit/?$",
|
||||
"view": "microbit/microbit",
|
||||
"title": "micro:bit"
|
||||
},
|
||||
{
|
||||
"name": "donate-redirect",
|
||||
"pattern": "^/info/donate/?",
|
||||
"routeAlias": "/info/(cards|communityblocks-interviews|credits|faq|donate)/?$",
|
||||
"redirect": "https://secure.donationpay.org/scratchfoundation/"
|
||||
},
|
||||
{
|
||||
"name": "donate-redirect2",
|
||||
"pattern": "^/donate/?$",
|
||||
"routeAlias": "/donate/?$",
|
||||
"redirect": "https://secure.donationpay.org/scratchfoundation/"
|
||||
},
|
||||
{
|
||||
"name": "download-redirect",
|
||||
"pattern": "^/scratch2download/?$",
|
||||
|
|
|
@ -67,7 +67,7 @@ const About = () => (
|
|||
id="about.aroundTheWorldDescription"
|
||||
values={{
|
||||
translationLink: (
|
||||
<a href="http://wiki.scratch.mit.edu/wiki/How_to_Translate_Scratch">
|
||||
<a href="https://en.scratch-wiki.info/wiki/How_to_Translate_Scratch">
|
||||
<FormattedMessage id="about.translationLinkText" />
|
||||
</a>
|
||||
)
|
||||
|
@ -119,7 +119,7 @@ const About = () => (
|
|||
id="about.researchDescription"
|
||||
values={{
|
||||
researchLink: (
|
||||
<a href="/info/research">
|
||||
<a href="/research">
|
||||
<FormattedMessage id="about.researchLinkText" />
|
||||
</a>
|
||||
),
|
||||
|
@ -141,7 +141,7 @@ const About = () => (
|
|||
<h3><FormattedMessage id="about.learnMore" /></h3>
|
||||
<ul className="list">
|
||||
<li>
|
||||
<a href="/help"><FormattedMessage id="about.learnMoreHelp" /></a>
|
||||
<a href="/tips"><FormattedMessage id="about.learnMoreHelp" /></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/info/faq"><FormattedMessage id="about.learnMoreFaq" /></a>
|
||||
|
|
|
@ -57,9 +57,9 @@ const Components = () => (
|
|||
<span className="type-gray">$type-gray</span>
|
||||
<span className="type-white">$type-white</span>
|
||||
<span className="link-blue">$link-blue</span>
|
||||
<span className="splash-green">$splash-green</span>
|
||||
<span className="splash-pink">$splash-pink</span>
|
||||
<span className="splash-blue">$splash-blue</span>
|
||||
<span className="splash-green">$ui-aqua</span>
|
||||
<span className="splash-pink">$ui-purple</span>
|
||||
<span className="splash-blue">$ui-blue</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -101,15 +101,15 @@
|
|||
}
|
||||
|
||||
.splash-green {
|
||||
background-color: $splash-green;
|
||||
background-color: $ui-aqua;
|
||||
}
|
||||
|
||||
.splash-pink {
|
||||
background-color: $splash-pink;
|
||||
background-color: $ui-purple;
|
||||
}
|
||||
|
||||
.splash-blue {
|
||||
background-color: $splash-blue;
|
||||
background-color: $ui-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
display: block;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($ui-blue, 40);
|
||||
background-color: $ui-blue-25percent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
}
|
||||
|
||||
.conf2017-title-band {
|
||||
background-color: lighten($ui-blue, 10%);
|
||||
background-color: $ui-blue-dark;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
color: $type-white;
|
||||
|
|
121
src/views/conference/2018/details/details.jsx
Normal file
121
src/views/conference/2018/details/details.jsx
Normal file
|
@ -0,0 +1,121 @@
|
|||
const classNames = require('classnames');
|
||||
const connect = require('react-redux').connect;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const render = require('../../../../lib/render.jsx');
|
||||
|
||||
const detailsActions = require('../../../../redux/conference-details.js');
|
||||
|
||||
const Page = require('../../../../components/page/conference/2018/page.jsx');
|
||||
|
||||
require('./details.scss');
|
||||
|
||||
class ConferenceDetails extends React.Component {
|
||||
componentDidMount () {
|
||||
let pathname = window.location.pathname.toLowerCase();
|
||||
if (pathname[pathname.length - 1] === '/') {
|
||||
pathname = pathname.substring(0, pathname.length - 1);
|
||||
}
|
||||
const path = pathname.split('/');
|
||||
const detailsId = path[path.length - 2];
|
||||
this.props.dispatch(detailsActions.startGetDetails(detailsId));
|
||||
}
|
||||
render () {
|
||||
let backUri = '/conference/2018/schedule';
|
||||
if (!this.props.conferenceDetails.error && !this.props.conferenceDetails.fetching) {
|
||||
backUri = `${backUri}#${this.props.conferenceDetails.Day}`;
|
||||
}
|
||||
const classes = classNames(
|
||||
'inner',
|
||||
'details',
|
||||
{fetching: this.props.conferenceDetails.fetching}
|
||||
);
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="back">
|
||||
<a href={backUri}>
|
||||
← Back to Full Schedule
|
||||
</a>
|
||||
</div>
|
||||
{this.props.conferenceDetails.error ? [
|
||||
<h2 key="not-found">Agenda Item Not Found</h2>
|
||||
] : [
|
||||
<h2 key="details-title">{this.props.conferenceDetails.Title}</h2>,
|
||||
<ul
|
||||
className="logistics"
|
||||
key="details-logistics"
|
||||
>
|
||||
{this.props.conferenceDetails.fetching ? [] : [
|
||||
<li key="presenter">
|
||||
<img
|
||||
alt="presenter icon"
|
||||
src="/svgs/conference/schedule/presenter-icon.svg"
|
||||
/>
|
||||
{this.props.conferenceDetails.Presenter}
|
||||
</li>,
|
||||
<li key="start">
|
||||
<img
|
||||
alt="time icon"
|
||||
src="/svgs/conference/schedule/time-icon.svg"
|
||||
/>
|
||||
{this.props.conferenceDetails.Start} – {this.props.conferenceDetails.End}
|
||||
</li>,
|
||||
<li key="type">
|
||||
<img
|
||||
alt="event icon"
|
||||
src="/svgs/conference/schedule/event-icon.svg"
|
||||
/>
|
||||
{this.props.conferenceDetails.Type}
|
||||
</li>,
|
||||
<li key="location">
|
||||
<img
|
||||
alt="location icon"
|
||||
src="/svgs/conference/schedule/location-icon.svg"
|
||||
/>
|
||||
{this.props.conferenceDetails.Location}
|
||||
</li>
|
||||
]}
|
||||
</ul>,
|
||||
<div
|
||||
className="description"
|
||||
key="details-desc"
|
||||
>
|
||||
<p>
|
||||
{this.props.conferenceDetails.Description}
|
||||
</p>
|
||||
</div>,
|
||||
<div
|
||||
className="back"
|
||||
key="details-back"
|
||||
>
|
||||
{this.props.conferenceDetails.fetching ? [] : [
|
||||
<a
|
||||
href={backUri}
|
||||
key="details-back-uri"
|
||||
>
|
||||
← Back to Full Schedule
|
||||
</a>
|
||||
]}
|
||||
</div>
|
||||
]}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ConferenceDetails.propTypes = {
|
||||
conferenceDetails: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||
dispatch: PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
conferenceDetails: state.conferenceDetails
|
||||
});
|
||||
|
||||
const ConnectedDetails = connect(mapStateToProps)(ConferenceDetails);
|
||||
|
||||
render(
|
||||
<Page><ConnectedDetails /></Page>,
|
||||
document.getElementById('app'),
|
||||
{conferenceDetails: detailsActions.detailsReducer}
|
||||
);
|
56
src/views/conference/2018/details/details.scss
Normal file
56
src/views/conference/2018/details/details.scss
Normal file
|
@ -0,0 +1,56 @@
|
|||
@import "../../../../frameless";
|
||||
|
||||
#view {
|
||||
@media only screen and (max-width: $tablet - 1) {
|
||||
margin-top: 100px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $desktop - 1) {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
width: $cols8;
|
||||
|
||||
&.inner {
|
||||
margin-top: 2rem;
|
||||
|
||||
&.fetching {
|
||||
opacity: .6;
|
||||
}
|
||||
}
|
||||
|
||||
.back {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
&.logistics {
|
||||
margin: .25rem 0 2.5rem;
|
||||
padding-left: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: .25rem 0;
|
||||
|
||||
img {
|
||||
margin-right: .5rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
//8 columns
|
||||
@media only screen and (max-width: $desktop - 1) {
|
||||
.details {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
330
src/views/conference/2018/expect/expect.jsx
Normal file
330
src/views/conference/2018/expect/expect.jsx
Normal file
|
@ -0,0 +1,330 @@
|
|||
const React = require('react');
|
||||
const render = require('../../../../lib/render.jsx');
|
||||
|
||||
const FlexRow = require('../../../../components/flex-row/flex-row.jsx');
|
||||
const Page = require('../../../../components/page/conference/2018/page.jsx');
|
||||
const TitleBanner = require('../../../../components/title-banner/title-banner.jsx');
|
||||
|
||||
require('./expect.scss');
|
||||
|
||||
const ConferenceExpectations = () => (
|
||||
<div className="expect">
|
||||
<TitleBanner className="mod-conference">
|
||||
<h1>
|
||||
What to Expect
|
||||
</h1>
|
||||
<div className="title-icon">
|
||||
<img
|
||||
alt="expect-image"
|
||||
src="/images/conference/expect/what-to-expect.png"
|
||||
/>
|
||||
</div>
|
||||
</TitleBanner>
|
||||
<section className="inner profile">
|
||||
<FlexRow className="uneven">
|
||||
<div className="short">
|
||||
<img src="/images/conference/expect/2018/mitch.jpg" />
|
||||
<h4>Mitchel Resnick</h4>
|
||||
<p>
|
||||
Professor of Learning Research
|
||||
<br />
|
||||
Founder, MIT Scratch Team
|
||||
<br />
|
||||
MIT Media Lab
|
||||
</p>
|
||||
</div>
|
||||
<div className="long">
|
||||
<h2>Welcome to Scratch@MIT 2018!</h2>
|
||||
<p className="intro">
|
||||
The theme of this year’s Scratch conference is “The Next{' '}
|
||||
Generation”. In choosing this phrase, we had two different{' '}
|
||||
meanings in mind.
|
||||
</p>
|
||||
<p className="intro">
|
||||
The theme is motivated, in part, by our work on the next generation{' '}
|
||||
of Scratch. We plan to release this new version, called Scratch 3.0,{' '}
|
||||
later this year. Scratch 3.0 will expand how, what, and where children{' '}
|
||||
can create and learn with Scratch. At the conference, you’ll have lots{' '}
|
||||
of opportunities to experiment and explore with prototype versions of{' '}
|
||||
Scratch 3.0.
|
||||
</p>
|
||||
<p className="intro">
|
||||
But even as we develop the next generation of software, our top{' '}
|
||||
priority is always the next generation of children.
|
||||
</p>
|
||||
<p className="intro">
|
||||
We continue to be amazed and delighted by all of the ways that{' '}
|
||||
children around the world are creating and collaborating with Scratch.{' '}
|
||||
As we see the outpouring of creativity in the Scratch community, we{' '}
|
||||
become even more committed to developing a next generation of Scratch{' '}
|
||||
that is truly worthy of the next generation of children.
|
||||
</p>
|
||||
<p className="intro">
|
||||
At this summer’s Scratch conference, we look forward to hearing{' '}
|
||||
your stories of how children are creating and learning with Scratch,{' '}
|
||||
and how you are supporting them. Let’s work together to expand{' '}
|
||||
opportunities for all children, from all backgrounds, to imagine,{' '}
|
||||
create, and collaborate — so they can shape the world of tomorrow.
|
||||
</p>
|
||||
</div>
|
||||
</FlexRow>
|
||||
</section>
|
||||
<section className="keynote">
|
||||
<div className="inner">
|
||||
<div className="section-header">
|
||||
<h2>Keynotes</h2>
|
||||
</div>
|
||||
<FlexRow>
|
||||
<div className="card">
|
||||
<div className="date">
|
||||
<b>Thursday</b>
|
||||
</div>
|
||||
<h3>The Next Generation</h3>
|
||||
<img
|
||||
alt="Scratch Team Photo"
|
||||
src="/images/conference/expect/scratch-team.jpg"
|
||||
/>
|
||||
<p>
|
||||
<b>MIT Scratch Team</b>
|
||||
<br />
|
||||
<b>Mitchel Resnick (moderator)</b>
|
||||
</p>
|
||||
<p>
|
||||
Join us for an inside look at the next generation of Scratch —{' '}
|
||||
and a discussion of how Scratch is opening new opportunities for the next{' '}
|
||||
generation of children around the world.
|
||||
</p>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="date">
|
||||
<b>Friday</b>
|
||||
</div>
|
||||
<h3>Creative Is Not A Noun</h3>
|
||||
<img
|
||||
alt="Austin Kleon Photo"
|
||||
src="/images/conference/expect/2018/austin_kleon.png"
|
||||
/>
|
||||
<p>
|
||||
<b>Austin Kleon</b>
|
||||
</p>
|
||||
|
||||
<img
|
||||
alt="Karen Photo"
|
||||
className="moderator"
|
||||
src="/images/conference/expect/2018/karen.jpg"
|
||||
/>
|
||||
<p>
|
||||
<b>Karen Brennan (moderator)</b>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Writer and artist Austin Kleon (author of the bestsellers Steal Like An Artist{' '}
|
||||
and Show Your Work!) discusses his practice and shares 10 principles for anyone{' '}
|
||||
who wants to do more creative work in a connected world.
|
||||
</p>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="date">
|
||||
<b>Saturday</b>
|
||||
</div>
|
||||
<h3>Growing Up With Scratch</h3>
|
||||
<img
|
||||
alt="Isabella, JT, and Jocelyn Photo"
|
||||
src="/images/conference/expect/2018/growing-up-with-scratch-presenters.png"
|
||||
/>
|
||||
<p>
|
||||
<b>Isabella Bruyere, JT Galla, & Jocelyn Marencik</b>
|
||||
</p>
|
||||
|
||||
<img
|
||||
alt="Ricarose Photo"
|
||||
className="moderator"
|
||||
src="/images/conference/expect/2018/ricarose.png"
|
||||
/>
|
||||
|
||||
<p>
|
||||
<b>Ricarose Roque (moderator)</b>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
What is it like to grow up with Scratch? Three long-time Scratch community{' '}
|
||||
members share how they have used Scratch to express their interests, to make{' '}
|
||||
friends, and to lead initiatives in their communities.
|
||||
</p>
|
||||
</div>
|
||||
</FlexRow>
|
||||
</div>
|
||||
</section>
|
||||
<section className="inner schedule">
|
||||
<div className="section-header">
|
||||
<div className="title">
|
||||
<h2>Daily Schedules</h2>
|
||||
</div>
|
||||
<p className="callout">
|
||||
<img
|
||||
alt="July 25th Icon"
|
||||
src="/svgs/conference/expect/july25-icon.svg"
|
||||
/>
|
||||
<b>Wednesday at 6:00p</b> – Early check-in and opening reception
|
||||
</p>
|
||||
</div>
|
||||
<FlexRow>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
<img
|
||||
alt="July 26th Icon"
|
||||
src="/svgs/conference/expect/july26-icon.svg"
|
||||
/>
|
||||
<h3>Thursday</h3>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>8:30a-9:30a</b>
|
||||
<p>Breakfast (provided)</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>9:30a-10:30a</b>
|
||||
<p>Keynote Presentation</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>11:00a-12:30p</b>
|
||||
<p>Morning Workshops</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>12:30p-1:30p</b>
|
||||
<p>Lunch (provided)</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>2:00p-3:30p</b>
|
||||
<p>Afternoon Workshops</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>4:00p-5:30p</b>
|
||||
<p>Poster Sessions</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
<img
|
||||
alt="July 27th Icon"
|
||||
src="/svgs/conference/expect/july27-icon.svg"
|
||||
/>
|
||||
<h3>Friday</h3>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>8:30a-9:30a</b>
|
||||
<p>Breakfast (provided)</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>9:30a-10:30a</b>
|
||||
<p>Keynote Presentation</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>11:00a-12:00p</b>
|
||||
<p>Morning Workshops, Panels, and Ignite Talks</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>12:00p-1:00p</b>
|
||||
<p>Lunch (provided)</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>1:30p-2:30p</b>
|
||||
<p>Early Afternoon Workshops, Panels, and Ignite Talks</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>3:00p-4:00p</b>
|
||||
<p>Late Afternoon Workshops, Panels, and Ignite Talks</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>4:30p-6:00p</b>
|
||||
<p>Poster Sessions</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>6:00p-7:30p</b>
|
||||
<p>Conference Dinner (provided)</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
<img
|
||||
alt="July 28th Icon"
|
||||
src="/svgs/conference/expect/july28-icon.svg"
|
||||
/>
|
||||
<h3>Saturday</h3>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>8:30a-9:30a</b>
|
||||
<p>Breakfast (provided)</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>9:30a-10:30a</b>
|
||||
<p>Keynote Presentation</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>11:00a-12:00p</b>
|
||||
<p>Morning Workshops, Panels and Ignite Talks</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>12:00p-1:30p</b>
|
||||
<p>Closing Ceremony and Lunch (provided)</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>1:30p</b>
|
||||
<p>End of Conference</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</FlexRow>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
|
||||
render(<Page><ConferenceExpectations /></Page>, document.getElementById('app'));
|
198
src/views/conference/2018/expect/expect.scss
Normal file
198
src/views/conference/2018/expect/expect.scss
Normal file
|
@ -0,0 +1,198 @@
|
|||
@import "../../../../colors";
|
||||
@import "../../../../frameless";
|
||||
|
||||
.expect {
|
||||
.flex-row {
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
|
||||
.card {
|
||||
width: $cols4;
|
||||
|
||||
p {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile {
|
||||
img {
|
||||
border-radius: 8em;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top: 1.2rem;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $tablet - 1) {
|
||||
img {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
font-size: 1.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $desktop - 1) {
|
||||
.uneven {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.keynote {
|
||||
background-color: $ui-purple;
|
||||
padding: 48px 0 64px 0;
|
||||
width: 100%;
|
||||
|
||||
h2,
|
||||
h3,
|
||||
b,
|
||||
p {
|
||||
color: $ui-white;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
|
||||
&.moderator {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.date {
|
||||
b {
|
||||
border-radius: 20px;
|
||||
background-color: $ui-orange;
|
||||
padding: 5px 15px;
|
||||
font-size: .85rem;
|
||||
}
|
||||
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $desktop - 1) {
|
||||
.flex-row {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.card {
|
||||
margin-top: 25px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.schedule {
|
||||
.title {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.callout {
|
||||
display: flex;
|
||||
padding: .85rem;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
margin-right: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
width: $cols4;
|
||||
|
||||
th {
|
||||
display: flex;
|
||||
border-bottom: thin solid $ui-border;
|
||||
padding: 2.5%;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
display: flex;
|
||||
border-bottom: thin solid $ui-border;
|
||||
padding: 2.5%;
|
||||
height: 60px;
|
||||
align-items: center;
|
||||
|
||||
b {
|
||||
width: 40%;
|
||||
line-height: 1.7em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $mobile - 1) {
|
||||
.flex-row {
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: $mobile) and (max-width: $desktop - 1) {
|
||||
.flex-row {
|
||||
table {
|
||||
width: $cols6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $desktop - 1) {
|
||||
.flex-row {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
table {
|
||||
margin-top: 50px;
|
||||
text-align: left;
|
||||
|
||||
th {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
const FormattedDate = require('react-intl').FormattedDate;
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const React = require('react');
|
||||
const render = require('../../../../lib/render.jsx');
|
||||
|
||||
|
@ -8,246 +6,76 @@ const FlexRow = require('../../../../components/flex-row/flex-row.jsx');
|
|||
const Page = require('../../../../components/page/conference/2018/page.jsx');
|
||||
const TitleBanner = require('../../../../components/title-banner/title-banner.jsx');
|
||||
|
||||
require('../../../../components/forms/button.scss');
|
||||
require('./index.scss');
|
||||
|
||||
const ConferenceSplash = () => (
|
||||
<div className="index mod-2018">
|
||||
<div className="index mod-2016">
|
||||
<TitleBanner className="mod-conference">
|
||||
<h1>
|
||||
<FormattedMessage id="conference-2018.subtitle" />
|
||||
Scratch: The Next Generation
|
||||
</h1>
|
||||
<h3>
|
||||
<FormattedMessage id="conference-2018.dateDesc" />
|
||||
July 26-28, 2018 | Cambridge, MA, USA
|
||||
</h3>
|
||||
<h3>
|
||||
<a href="http://www.media.mit.edu/events/medialabtalk/">
|
||||
Watch the keynotes live 7/26-7/28 at 9:30 a.m. EDT
|
||||
</a>
|
||||
</h3>
|
||||
<p>
|
||||
<a href="https://scratch2018.eventbrite.com">
|
||||
<Button className="mod-register">
|
||||
<FormattedMessage id="conference-2018.registerNow" />
|
||||
<a href="/conference/2018/schedule">
|
||||
<Button>
|
||||
See the Schedule
|
||||
</Button>
|
||||
</a>
|
||||
</p>
|
||||
</TitleBanner>
|
||||
<div className="inner">
|
||||
<section className="info">
|
||||
<FlexRow className="uneven">
|
||||
<div className="long">
|
||||
<p>
|
||||
<FormattedMessage id="conference-2018.desc1" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<FormattedMessage id="conference-2018.desc2" />
|
||||
</p>
|
||||
</div>
|
||||
<div className="short">
|
||||
<p>
|
||||
<b><FormattedMessage id="conference-2018.date" /></b>{' '}
|
||||
{/* eslint-disable react/jsx-sort-props */}
|
||||
<FormattedDate
|
||||
value={new Date(2018, 6, 26)}
|
||||
year="numeric"
|
||||
month="long"
|
||||
day="2-digit"
|
||||
<section className="inner">
|
||||
<FlexRow>
|
||||
<div>
|
||||
<h3>
|
||||
<a href="/conference/2018/expect">
|
||||
<img
|
||||
alt="expect-image"
|
||||
src="/images/conference/expect/what-to-expect.png"
|
||||
/>
|
||||
{' - '}
|
||||
<FormattedDate
|
||||
value={new Date(2018, 6, 28)}
|
||||
year="numeric"
|
||||
month="long"
|
||||
day="2-digit"
|
||||
What to Expect
|
||||
</a>
|
||||
</h3>
|
||||
<p>
|
||||
Learn more about participating in Scratch@MIT
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>
|
||||
<a href="/conference/2018/plan">
|
||||
<img
|
||||
alt="plan-image"
|
||||
src="/images/conference/plan/plan-your-visit.png"
|
||||
/>
|
||||
{/* eslint-enable react/jsx-sort-props */}
|
||||
<br />
|
||||
<FormattedMessage id="conference-2018.dateDescMore" />
|
||||
<br />
|
||||
<b><FormattedMessage id="conference-2018.location" /></b>{' '}
|
||||
<FormattedMessage id="conference-2018.locationDetails" />
|
||||
</p>
|
||||
</div>
|
||||
</FlexRow>
|
||||
<FlexRow className="uneven">
|
||||
<div className="long">
|
||||
<h3 id="info"><FormattedMessage id="conference-2018.registrationTitle" /></h3>
|
||||
<p className="conf2018-panel-desc">
|
||||
<b><FormattedMessage id="conference-2018.registrationEarly" /></b>
|
||||
<br />
|
||||
<b><FormattedMessage id="conference-2018.registrationStandard" /></b>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://scratch2018.eventbrite.com">
|
||||
<Button className="mod-register">
|
||||
<FormattedMessage id="conference-2018.registerNow" />
|
||||
</Button>
|
||||
</a>
|
||||
</p>
|
||||
<h3 id="questions"><FormattedMessage id="conference-2018.questionsTitle" /></h3>
|
||||
<div>
|
||||
<p className="conf2018-question">
|
||||
<FormattedMessage id="conference-2018.submissionQ" />
|
||||
</p>
|
||||
<p className="conf2018-answer">
|
||||
<FormattedMessage id="conference-2018.submissionAns" />
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="conf2018-question">
|
||||
<FormattedMessage id="conference-2018.regQ" />
|
||||
</p>
|
||||
<p className="conf2018-answer">
|
||||
<FormattedMessage id="conference-2018.regAns" />
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="conf2018-question">
|
||||
<FormattedMessage id="conference-2018.accommodationsQ" />
|
||||
</p>
|
||||
<p className="conf2018-answer">
|
||||
<FormattedMessage
|
||||
id="conference-2018.accommodationsAns1"
|
||||
values={{
|
||||
marriottLink: (
|
||||
<a href="http://www.marriott.com/hotels/travel/boscb-boston-marriott-cambridge/">
|
||||
Boston Marriott Cambridge
|
||||
</a>
|
||||
),
|
||||
holidayinnLink: (
|
||||
<a href="http://www.hiexpress.com/hotels/us/en/reservation/searchresult?qAdlt=1&qBrs=6c.hi.ex.rs.ic.cp.in.sb.cw.cv.ul.vn&qChld=0&qDest=CAMBRIDGE%2CMA%2CUnited+States&qFRA=1&qGRM=0&qIta=99504425&qPSt=0&qRRSrt=rt&qRef=df&qRms=1&qRpn=1&qRpp=12&qSHp=1&qSmP=3&qSrt=sBR&qWch=0&srb_u=1&icdv=99504425&dp=true">
|
||||
Holiday Inn Express and Suites
|
||||
</a>
|
||||
),
|
||||
residenceinnLink: (
|
||||
<a href="http://www.marriott.com/hotels/travel/boscm-residence-inn-boston-cambridge/">
|
||||
Residence Inn
|
||||
</a>
|
||||
),
|
||||
lemeridienLink: (
|
||||
<a href="http://www.starwoodhotels.com/lemeridien/property/overview/index.html?propertyID=3253&language=en_US">
|
||||
Le Meridien
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p className="conf2018-answer">
|
||||
<FormattedMessage
|
||||
id="conference-2018.accommodationsAns2"
|
||||
values={{
|
||||
acLink: (
|
||||
<a href="http://www.marriott.com/meeting-event-hotels/group-corporate-travel/groupCorp.mi?resLinkData=Scratch%20Conference%5EBOSAR%60sccscca%7Csccsccb%60229%60USD%60false%604%607/25/18%607/28/18%607/4/18&app=resvlink&stop_mobi=yes">
|
||||
AC Hotel Boston Cambridge
|
||||
</a>
|
||||
),
|
||||
doubletreeLink: (
|
||||
<a href="https://secure3.hilton.com/en_US/dt/reservation/book.htm?inputModule=HOTEL&ctyhocn=BOSCODT&spec_plan=CDTMIT&arrival=20180725&departure=20180728&cid=OM,WW,HILTONLINK,EN,DirectLink&fromId=HILTONLINKDIRECT">
|
||||
DoubleTree by Hilton Hotel Boston - Downtown
|
||||
</a>
|
||||
),
|
||||
hotelbostonLink: (
|
||||
<a href="https://www.hotelboston.com/">
|
||||
Hotel Boston
|
||||
</a>
|
||||
),
|
||||
mitLink: (
|
||||
<a href="http://www.media.mit.edu/contact/accommodations">
|
||||
<FormattedMessage id="conference-2018.here" />
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p className="conf2018-answer">
|
||||
<FormattedMessage
|
||||
id="conference-2018.accommodationsAns3"
|
||||
values={{
|
||||
neuLink: (
|
||||
<a href="http://www.northeastern.edu/">
|
||||
Northeastern University
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p className="conf2018-question">
|
||||
<FormattedMessage id="conference-2018.suite" />
|
||||
</p>
|
||||
<p className="conf2018-answer">
|
||||
<FormattedMessage id="conference-2018.single" />
|
||||
{' - $80.00'}
|
||||
<FormattedMessage id="conference-2018.pp" />
|
||||
<br />
|
||||
<FormattedMessage id="conference-2018.double" />
|
||||
{' - $65.00'}
|
||||
<FormattedMessage id="conference-2018.pp" />
|
||||
</p>
|
||||
<p className="conf2018-answer">
|
||||
<FormattedMessage
|
||||
id="conference-2018.accommodationsAns4"
|
||||
values={{
|
||||
dormrequestLink: (
|
||||
<a href="https://docs.google.com/forms/d/e/1FAIpQLSd8LRQyz9ZLXcpvjmYrnpAlN0_RVyYsgObUXQveI9_WpoDabw/viewform?usp=sf_link">
|
||||
<FormattedMessage id="conference-2018.dormRequestText" />
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="conf2018-question">
|
||||
<FormattedMessage id="conference-2018.letterQ" />
|
||||
</p>
|
||||
<p className="conf2018-answer">
|
||||
<FormattedMessage
|
||||
id="conference-2018.letterAns"
|
||||
values={{
|
||||
emailLink: (
|
||||
<a href="mailto:conference@scratch.mit.edu">
|
||||
conference@scratch.mit.edu
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="conf2018-question">
|
||||
<FormattedMessage id="conference-2018.preConfQ" />
|
||||
</p>
|
||||
<p className="conf2018-answer">
|
||||
<FormattedMessage id="conference-2018.preConfAns" />
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="conf2018-question">
|
||||
<FormattedMessage id="conference-2018.bringQ" />
|
||||
</p>
|
||||
<p className="conf2018-answer">
|
||||
<FormattedMessage id="conference-2018.bringAns" />
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="conf2018-question">
|
||||
<FormattedMessage id="conference-2018.moreQ" />
|
||||
</p>
|
||||
<p className="conf2018-answer">
|
||||
<FormattedMessage
|
||||
id="conference-2018.moreAns"
|
||||
values={{
|
||||
emailLink: (
|
||||
<a href="mailto:conference@scratch.mit.edu">
|
||||
conference@scratch.mit.edu
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</FlexRow>
|
||||
</section>
|
||||
</div>
|
||||
Plan Your Visit
|
||||
</a>
|
||||
</h3>
|
||||
<p>
|
||||
Information on traveling, staying, and exploring around the Media Lab
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>
|
||||
<a href="/conference/2018/schedule">
|
||||
<img
|
||||
alt="schedule"
|
||||
src="/images/conference/schedule/2018/schedule.png"
|
||||
/>
|
||||
Schedule
|
||||
</a>
|
||||
</h3>
|
||||
<p>
|
||||
Full schedule of events and sessions
|
||||
</p>
|
||||
</div>
|
||||
</FlexRow>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
@import "../../../../colors";
|
||||
@import "../../../../frameless";
|
||||
|
||||
#view {
|
||||
background-color: $ui-light-gray;
|
||||
min-height: initial;
|
||||
}
|
||||
|
||||
.index {
|
||||
.title-banner {
|
||||
margin-bottom: 0;
|
||||
|
@ -18,8 +23,19 @@
|
|||
color: $type-white;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
a {
|
||||
text-decoration: underline;
|
||||
color: $type-white;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 3rem;
|
||||
margin-top: 1rem;
|
||||
|
||||
&.sub-button {
|
||||
margin-top: 1rem;
|
||||
|
@ -37,7 +53,9 @@
|
|||
a {
|
||||
|
||||
button {
|
||||
|
||||
background-color: $ui-white;
|
||||
color: $ui-blue;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,46 +83,23 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.flex-row {
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
|
||||
&.uneven {
|
||||
div {
|
||||
width: 28%;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 125px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $tablet - 1) {
|
||||
img {
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) {
|
||||
img {
|
||||
width: 70%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
text-align: left;
|
||||
|
||||
.conf2018-question {
|
||||
margin-bottom: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.conf2018-answer {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $desktop - 1) {
|
||||
.uneven {
|
||||
.short {
|
||||
width: 70%;
|
||||
}
|
||||
margin: .5rem;
|
||||
width: 125px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -117,133 +112,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.conf2018-panel {
|
||||
border-bottom: 1px solid $ui-border;
|
||||
}
|
||||
|
||||
.conf2018-panel.mod-last {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.flex-row.conf2018-panel-title {
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.conf2018-panel-desc {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: .75rem 1.25rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.conf2018-panel-row-icon-image {
|
||||
margin-top: .125rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.button.mod-register {
|
||||
padding: .75em 3.5em;
|
||||
|
||||
text-align: center;
|
||||
color: $type-white;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $mobile - 1) {
|
||||
.index.mod-2018 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.title-banner-image.mod-2018 {
|
||||
height: 10rem;
|
||||
}
|
||||
|
||||
.conf2018-panel,
|
||||
.title-banner-h3.mod-2018 {
|
||||
width: initial;
|
||||
}
|
||||
|
||||
.conf2018-panel {
|
||||
margin: auto .5rem;
|
||||
}
|
||||
|
||||
.title-banner-h3.mod-2018 {
|
||||
margin: 1rem .5rem .5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.flex-row.conf2018-panel-title {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.conf2018-panel-title-text {
|
||||
max-width: 14rem;
|
||||
}
|
||||
|
||||
.conf2018-panel-row > td {
|
||||
padding: .75rem .375rem .75rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) {
|
||||
.index.mod-2018 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.title-banner-image.mod-2018 {
|
||||
height: 10rem;
|
||||
}
|
||||
|
||||
.conf2018-panel,
|
||||
.title-banner-h3.mod-2018 {
|
||||
margin: auto .5rem ;
|
||||
width: initial;
|
||||
}
|
||||
|
||||
.title-banner-h3.mod-2018 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.flex-row.conf2018-panel-title {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.conf2018-panel-title-text {
|
||||
max-width: 18.75rem;
|
||||
}
|
||||
|
||||
.button.mod-register {
|
||||
padding: .75em 2em;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) {
|
||||
.index.mod-2018 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.title-banner-image.mod-2018 {
|
||||
height: 15rem;
|
||||
}
|
||||
|
||||
.conf2018-panel,
|
||||
.title-banner-h3.mod-2018 {
|
||||
margin: auto;
|
||||
width: 38.75rem;
|
||||
}
|
||||
|
||||
.title-banner-h3.mod-2018 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.button.mod-register {
|
||||
padding: .75em 1.25em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
{
|
||||
"conference-2018.title": "Scratch Conference 2018:",
|
||||
"conference-2018.subtitle": "The Next Generation",
|
||||
"conference-2018.dateDesc": "July 26-28, 2018 | Cambridge, MA, USA",
|
||||
"conference-2018.dateDescMore": " (with opening reception the evening of July 25)",
|
||||
"conference-2018.locationDetails": "MIT Media Lab, Cambridge, MA",
|
||||
"conference-2018.seeBelow": "Learn more about conference dates and locations below.",
|
||||
|
||||
"conference-2018.date": "When:",
|
||||
"conference-2018.location": "Where:",
|
||||
|
||||
"conference-2018.desc1": "Join us for the Scratch@MIT conference, a playful gathering of educators, researchers, developers, and other members of the worldwide Scratch community.",
|
||||
"conference-2018.desc2": "We're planning a very participatory conference, with an entire day of hands-on workshops and lots of opportunities for peer-to-peer discussion and collaboration. The conference is intended primarily for adults who support young people learning Scratch.",
|
||||
"conference-2018.registrationDate": "Registration opens March 1, 2018.",
|
||||
"conference-2018.registerNow": "Register Now!",
|
||||
"conference-2018.sessionDesc": "Interested in offering a session? We invite four types of proposals:",
|
||||
"conference-2018.sessionItem1Title": "Poster/demonstration (90 minutes).",
|
||||
"conference-2018.sessionItem1Desc": "Show off your project in an exhibition setting, alongside other presenters. You will be provided with display space for a poster and table space for a computer or handouts.",
|
||||
"conference-2018.sessionItem2Title": "Hands-on workshop (90 minutes).",
|
||||
"conference-2018.sessionItem2Desc": "Engage participants in hands-on activities, highlighting new ways of creating and collaborating with Scratch.",
|
||||
"conference-2018.sessionItem3Title": "Interactive panel (60 minutes).",
|
||||
"conference-2018.sessionItem3Desc": "Discuss a Scratch-related topic in a panel with three or more people. Your proposal should describe how you will engage the audience during the session.",
|
||||
"conference-2018.sessionItem4Title": "Ignite talk (5 minutes).",
|
||||
"conference-2018.sessionItem4Desc": "Share what you've been doing in a short, lively presentation.",
|
||||
|
||||
"conference-2018.deadline": "Deadline for proposals is February 5, 2018.",
|
||||
"conference-2018.proposal": " Submit Your Proposal",
|
||||
"conference-2018.proposalDeadline": "Deadline for proposals: February 5",
|
||||
"conference-2018.proposalAccept": "Notification of acceptance: March 1",
|
||||
|
||||
"conference-2018.registrationTitle": "Registration:",
|
||||
"conference-2018.registrationEarly": "Early Bird Registration (March 1-May 1): $200",
|
||||
"conference-2018.registrationStandard": "Standard Registration (after May 1): $300",
|
||||
|
||||
"conference-2018.questions": "Questions? Contact the Scratch Team at {emailLink}",
|
||||
"conference-2018.questionsTitle": "Questions:",
|
||||
"conference-2018.submissionQ": "I missed the submission deadline. Can I still submit a proposal for the conference?",
|
||||
"conference-2018.submissionAns": "We are no longer accepting proposal submissions.",
|
||||
"conference-2018.regQ": "I can only attend one day of the conference. Do you offer single-day registration?",
|
||||
"conference-2018.regAns": "Sorry, we are not offering single-day tickets.",
|
||||
"conference-2018.accommodationsQ": "I want to plan my visit. Do you have suggestions for accommodations?",
|
||||
"conference-2018.accommodationsAns1": "Yes, MIT partners with several hotels in the area who offer discounts to participants attending MIT events, including: {marriottLink} (0.4 miles from the MIT Media Lab), {holidayinnLink} (1.6 miles), {residenceinnLink} (0.3 miles), and {lemeridienLink} (0.9 miles). To reserve a room at one of these hotels, call the hotel and request the MIT discount. Advance booking is strongly recommended, as summer is a busy time in Boston. All MIT rates are subject to availability.",
|
||||
"conference-2018.accommodationsAns2": "If you are looking for additional accommodation options, we also recommend the {acLink} (7.1 miles), {doubletreeLink} (3.3 miles), and {hotelbostonLink} with the code MITSC2018 (5.3 mile). You might also consider home-share options such as Airbnb. Find an extended list of accommodations {mitLink}.",
|
||||
"conference-2018.here": "here",
|
||||
"conference-2018.accommodationsAns3": "Limited lodging is available in {neuLink} dorm rooms at the following rates:",
|
||||
"conference-2018.suite": "Suite",
|
||||
"conference-2018.single": "Single",
|
||||
"conference-2018.double": "Double",
|
||||
"conference-2018.pp": "/person/night",
|
||||
"conference-2018.accommodationsAns4": "Northeastern University dorms are open ONLY to individuals over 18 years of age. To request a dorm room, please complete the {dormrequestLink}. Northeastern is located in Boston, two miles from the conference site at MIT. It is a half-hour commute via public transportation, accessible by subway via the Green Line (the Northeastern stop on the E line) or the Orange Line (Ruggles Station stop).",
|
||||
"conference-2018.dormRequestText": "Dorm Room Request Form",
|
||||
"conference-2018.letterQ": "Can I get a visa letter?",
|
||||
"conference-2018.letterAns": "Yes. Contact us at {emailLink}, and we can email you a letter.",
|
||||
"conference-2018.preConfQ": "In previous years, there was an event on Wednesday evening before the conference. Will you be hosting something similar this year?",
|
||||
"conference-2018.preConfAns": "There will be an informal, optional reception the evening of Wednesday, July 25. Participants may register early at this time as well.",
|
||||
"conference-2018.bringQ": "What should I bring?",
|
||||
"conference-2018.bringAns": "Plan to bring your personal device (laptops are preferred) and power cord. Presenters should plan to bring all additional presentation materials (we will provide projectors and screens). Snacks and beverages will be available throughout the day.",
|
||||
"conference-2018.moreQ": "Have additional questions?",
|
||||
"conference-2018.moreAns": "Contact the Scratch Team at {emailLink}."
|
||||
}
|
343
src/views/conference/2018/plan/plan.jsx
Normal file
343
src/views/conference/2018/plan/plan.jsx
Normal file
|
@ -0,0 +1,343 @@
|
|||
const bindAll = require('lodash.bindall');
|
||||
const React = require('react');
|
||||
|
||||
const Button = require('../../../../components/forms/button.jsx');
|
||||
const FlexRow = require('../../../../components/flex-row/flex-row.jsx');
|
||||
const TitleBanner = require('../../../../components/title-banner/title-banner.jsx');
|
||||
|
||||
const render = require('../../../../lib/render.jsx');
|
||||
const Page = require('../../../../components/page/conference/2018/page.jsx');
|
||||
|
||||
|
||||
require('./plan.scss');
|
||||
|
||||
class ConferencePlan extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'toggleQuestion'
|
||||
]);
|
||||
this.state = {
|
||||
dorm: false
|
||||
};
|
||||
}
|
||||
toggleQuestion (element) {
|
||||
this.setState({element: !this.state[element]});
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<div className="plan">
|
||||
<TitleBanner className="mod-conference">
|
||||
<h1>
|
||||
Plan Your Visit
|
||||
</h1>
|
||||
<div className="title-icon">
|
||||
<img
|
||||
alt="plan-image"
|
||||
src="/images/conference/plan/plan-your-visit.png"
|
||||
/>
|
||||
</div>
|
||||
</TitleBanner>
|
||||
<div className="inner">
|
||||
<section className="lodging">
|
||||
<FlexRow className="uneven">
|
||||
<div className="long">
|
||||
<h2>Lodging</h2>
|
||||
<p>
|
||||
MIT partners with several hotels in the area who offer discounts to{' '}
|
||||
participants attending MIT events, including:
|
||||
</p>
|
||||
<FlexRow>
|
||||
<FlexRow className="column">
|
||||
<p>
|
||||
<a href="http://bit.ly/P0kTKy">
|
||||
Boston Marriott Cambridge
|
||||
</a>
|
||||
<br />
|
||||
<span>(Kendall Square, 0.4 miles from the MIT Media Lab)</span>
|
||||
</p>
|
||||
<p>
|
||||
<a href="http://bit.ly/2459rhL">
|
||||
Holiday Inn Express and Suites
|
||||
</a>
|
||||
<br />
|
||||
<span>(Lechmere Station, 1.6 miles)</span>
|
||||
</p>
|
||||
</FlexRow>
|
||||
<FlexRow className="column">
|
||||
<p>
|
||||
<a href="http://bit.ly/1qbQNmO">
|
||||
Residence Inn
|
||||
</a>
|
||||
<br />
|
||||
<span>(Kendall Square, 0.3 miles)</span>
|
||||
</p>
|
||||
<p>
|
||||
<a href="http://lemerid.ie/1Kt3TDF">
|
||||
Le Meridien
|
||||
</a>
|
||||
<br />
|
||||
<span>(between Central and Kendall Squares, 0.9 miles)</span>
|
||||
</p>
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
<p>
|
||||
To reserve a room at one of these hotels, call the hotel and request the{' '}
|
||||
"MIT discount". Advance booking is strongly recommended, as{' '}
|
||||
summer is a busy time in Boston. All MIT rates are subject to availability.
|
||||
</p>
|
||||
<p>
|
||||
If you are looking for additional accommodation options, we also recommend the {' '}
|
||||
<a href="http://www.marriott.com/meeting-event-hotels/group-corporate-travel/groupCorp.mi?resLinkData=Scratch%20Conference%5EBOSAR%60sccscca%7Csccsccb%60229%60USD%60false%604%607/25/18%607/28/18%607/4/18&app=resvlink&stop_mobi=yes">
|
||||
AC Hotel Boston Cambridge
|
||||
</a> (7.1 miles from the MIT Media Lab),{' '}
|
||||
<a href="https://secure3.hilton.com/en_US/dt/reservation/book.htm?inputModule=HOTEL&ctyhocn=BOSCODT&spec_plan=CDTMIT&arrival=20180725&departure=20180728&cid=OM,WW,HILTONLINK,EN,DirectLink&fromId=HILTONLINKDIRECT">
|
||||
DoubleTree by Hilton Hotel Boston - Downtown
|
||||
</a> (3.3 miles),{' '}
|
||||
and <a href="https://www.hotelboston.com/">
|
||||
Hotel Boston
|
||||
</a> with the code MITSC2018 (5.3 miles).{' '}
|
||||
</p>
|
||||
<p>
|
||||
You might also consider home-share options such as Airbnb.
|
||||
</p>
|
||||
</div>
|
||||
<div className="short">
|
||||
<img
|
||||
alt="Lodging Illustration"
|
||||
src="/images/conference/plan/lodging.png"
|
||||
/>
|
||||
</div>
|
||||
</FlexRow>
|
||||
</section>
|
||||
<section className="transportation">
|
||||
<FlexRow className="uneven">
|
||||
<div className="long">
|
||||
<h2>Transportation</h2>
|
||||
<p>
|
||||
The <a href="https://whereis.mit.edu/?go=E14">MIT Media Lab</a> is located{' '}
|
||||
in Kendall Square, Cambridge, MA, a 5-minute walk from the Kendall/MIT stop of{' '}
|
||||
MBTA Red Line subway. Cambridge is a bike-friendly, walkable city, and{' '}
|
||||
public transportation is encouraged. The MBTA provides free services from{' '}
|
||||
Boston Logan Airport to the South Station subway stop as well.
|
||||
</p>
|
||||
<p>
|
||||
Learn about{' '}
|
||||
<a href="https://www.media.mit.edu/posts/directions-and-parking/">
|
||||
driving, parking, and public transportation options
|
||||
</a> around the MIT Media Lab.
|
||||
</p>
|
||||
<p>
|
||||
<a href="http://web.mit.edu/facilities/transportation/parking/visitors/public_parking.html">
|
||||
Public parking facilities
|
||||
</a> are available near campus for a fee.
|
||||
</p>
|
||||
<p>
|
||||
Learn about additional{' '}
|
||||
<a href="http://www.cityofboston.gov/transportation/modes.asp">
|
||||
transportation options in Cambridge and Boston
|
||||
</a>.
|
||||
</p>
|
||||
</div>
|
||||
<div className="short">
|
||||
<img
|
||||
alt="Transportation Illustration"
|
||||
src="/images/conference/plan/transportation.png"
|
||||
/>
|
||||
</div>
|
||||
</FlexRow>
|
||||
</section>
|
||||
<section className="explore">
|
||||
<h2>Exploring Cambridge</h2>
|
||||
<div>
|
||||
<p>
|
||||
Boston is a city full of history and diverse neighborhoods. Check some{' '}
|
||||
of these attractions to experience the city’s rich cultural offerings:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="http://www.trolleytours.com/boston/">
|
||||
Beantown Trolley Tour
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.bostonducktours.com/">
|
||||
Boston Duck Tours
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.bostonteapartyship.com/">
|
||||
Boston Tea Party Ship & Museum
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.faneuilhallmarketplace.com/">
|
||||
Faneuil Hall Marketplace
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.thefreedomtrail.org/">
|
||||
Freedom Trail Walking Tours
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.hmnh.harvard.edu/">
|
||||
Harvard Museum of Natural History
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.icaboston.org/">
|
||||
Institute of Contemporary Art
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.gardnermuseum.org/home">
|
||||
Isabella Stewart Gardner Museum
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.jfklibrary.org/">
|
||||
John F. Kennedy Library & Museum
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://web.mit.edu/museum/">
|
||||
MIT Museum
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.mfa.org/">
|
||||
Museum of Fine Arts
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.mos.org/">
|
||||
Museum Of Science
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.neaq.org/index.php">
|
||||
New England Aquarium
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ussconstitutionmuseum.org/">
|
||||
USS Constitution
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
Try some Scratch Team favorites for snacking and dining around the Lab:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
A4 Pizza
|
||||
</li>
|
||||
<li>
|
||||
Abigail’s
|
||||
</li>
|
||||
<li>
|
||||
Bailey and Sage
|
||||
</li>
|
||||
<li>
|
||||
Clover
|
||||
</li>
|
||||
<li>
|
||||
Commonwealth
|
||||
</li>
|
||||
<li>
|
||||
Legal Seafood
|
||||
</li>
|
||||
<li>
|
||||
Meadhall
|
||||
</li>
|
||||
<li>
|
||||
Sebastian’s
|
||||
</li>
|
||||
<li>
|
||||
Tatte
|
||||
</li>
|
||||
<li>
|
||||
Za
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<section className="faq last">
|
||||
<FlexRow className="uneven">
|
||||
<div className="long">
|
||||
<h2>FAQ</h2>
|
||||
<dl>
|
||||
<dt>
|
||||
The conference is sold out. What can I do?
|
||||
</dt>
|
||||
<dd>
|
||||
Scratch@MIT is sold out and at capacity. Regrettably, we are{' '}
|
||||
unable to add any additional guests. Please keep in mind that{' '}
|
||||
you must have registered on Eventbrite to attend Scratch@MIT;{' '}
|
||||
people who are not registered / do not have a ticket will not be{' '}
|
||||
able to attend the conference.
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
I missed the submission deadline. Can I still submit a proposal for{' '}
|
||||
the conference?
|
||||
</dt>
|
||||
<dd>
|
||||
We are no longer accepting proposal submissions.
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
I can only attend one day of the conference. Do you offer single-day{' '}
|
||||
registration?
|
||||
</dt>
|
||||
<dd>
|
||||
Sorry, we are not offering single-day tickets.
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
Can I receive a visa letter?
|
||||
</dt>
|
||||
<dd>
|
||||
Yes. Contact us at{' '}
|
||||
<a href="mailto:conference@scratch.mit.edu">conference@scratch.mit.edu</a>{' '}
|
||||
and we can email you a letter.
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
In previous years, there was an event on Wednesday evening before the{' '}
|
||||
conference. Will you be hosting something similar this year?
|
||||
</dt>
|
||||
<dd>
|
||||
There will be an informal, optional reception the evening of Wednesday,{' '}
|
||||
July 25. Participants may register early at this time as well.
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
What should I bring?
|
||||
</dt>
|
||||
<dd>
|
||||
Plan to bring your personal device (laptops are preferred) and power cord.{' '}
|
||||
Presenters should plan to bring all additional presentation materials{' '}
|
||||
(we will provide projectors and screens). Snacks and beverages will be{' '}
|
||||
available throughout the day.
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div className="short">
|
||||
<h3>Have Additional Questions?</h3>
|
||||
<a href="mailto:conference@scratch.mit.edu">
|
||||
<Button>Email Us</Button>
|
||||
</a>
|
||||
</div>
|
||||
</FlexRow>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render(<Page><ConferencePlan /></Page>, document.getElementById('app'));
|
113
src/views/conference/2018/plan/plan.scss
Normal file
113
src/views/conference/2018/plan/plan.scss
Normal file
|
@ -0,0 +1,113 @@
|
|||
@import "../../../../colors";
|
||||
@import "../../../../frameless";
|
||||
|
||||
.plan {
|
||||
section {
|
||||
border-bottom: 2px solid $ui-border;
|
||||
|
||||
&.last {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
|
||||
&.uneven {
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $tablet - 1) {
|
||||
img {
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) {
|
||||
img {
|
||||
width: 70%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lodging {
|
||||
text-align: left;
|
||||
|
||||
@media only screen and (max-width: $desktop - 1) {
|
||||
.uneven {
|
||||
.short {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.transportation {
|
||||
.uneven {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $desktop - 1) {
|
||||
.flex-row {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.explore {
|
||||
div {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
max-height: 23rem;
|
||||
flex-flow: column wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $tablet - 1) {
|
||||
ul {
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $desktop - 1) {
|
||||
div {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.faq {
|
||||
dl {
|
||||
dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 8px 0 32px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.short {
|
||||
margin-top: 64px;
|
||||
border: 2px solid $ui-border;
|
||||
border-radius: 4px;
|
||||
background-color: $ui-white;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $tablet - 1) {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
205
src/views/conference/2018/schedule/schedule.jsx
Normal file
205
src/views/conference/2018/schedule/schedule.jsx
Normal file
|
@ -0,0 +1,205 @@
|
|||
const bindAll = require('lodash.bindall');
|
||||
const classNames = require('classnames');
|
||||
const connect = require('react-redux').connect;
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const scheduleActions = require('../../../../redux/conference-schedule.js');
|
||||
|
||||
const FlexRow = require('../../../../components/flex-row/flex-row.jsx');
|
||||
const SubNavigation = require('../../../../components/subnavigation/subnavigation.jsx');
|
||||
const TitleBanner = require('../../../../components/title-banner/title-banner.jsx');
|
||||
|
||||
const render = require('../../../../lib/render.jsx');
|
||||
const Page = require('../../../../components/page/conference/2018/page.jsx');
|
||||
|
||||
|
||||
require('./schedule.scss');
|
||||
|
||||
class ConferenceSchedule extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleScheduleChange',
|
||||
'renderChunkItems'
|
||||
]);
|
||||
}
|
||||
componentDidMount () {
|
||||
const day = window.location.hash.substr(1) || 'thursday';
|
||||
this.handleScheduleChange(day);
|
||||
}
|
||||
handleScheduleChange (day) {
|
||||
window.history.replaceState(history.state, '', `#${day}`);
|
||||
this.props.dispatch(scheduleActions.startGetSchedule(day));
|
||||
}
|
||||
renderChunkItems (timeSlot) {
|
||||
return timeSlot.map(item => {
|
||||
if (item.Presenter) {
|
||||
return (
|
||||
<a
|
||||
className="item-url"
|
||||
href={item.uri}
|
||||
>
|
||||
<div
|
||||
className="agenda-item"
|
||||
key={item.rowid}
|
||||
>
|
||||
<h3>{item.Title}</h3>
|
||||
<FlexRow>
|
||||
<p>
|
||||
<img
|
||||
alt="time icon"
|
||||
src="/svgs/conference/schedule/time-icon.svg"
|
||||
/>
|
||||
{item.Start} – {item.End}
|
||||
</p>
|
||||
<p>
|
||||
<img
|
||||
alt="location icon"
|
||||
src="/svgs/conference/schedule/location-icon.svg"
|
||||
/>
|
||||
{item.Location}
|
||||
</p>
|
||||
</FlexRow>
|
||||
<FlexRow>
|
||||
<p>
|
||||
<img
|
||||
alt="presenter icon"
|
||||
src="/svgs/conference/schedule/presenter-icon.svg"
|
||||
/>
|
||||
{item.Presenter}
|
||||
</p>
|
||||
<p>
|
||||
<img
|
||||
alt="event icon"
|
||||
src="/svgs/conference/schedule/event-icon.svg"
|
||||
/>
|
||||
{item.Type}
|
||||
</p>
|
||||
</FlexRow>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="agenda-item no-click"
|
||||
key={item.rowid}
|
||||
>
|
||||
<h3>{item.Title}</h3>
|
||||
<FlexRow>
|
||||
<p>
|
||||
<img
|
||||
alt="time icon"
|
||||
src="/svgs/conference/schedule/time-icon.svg"
|
||||
/>
|
||||
{item.Start} – {item.End}
|
||||
</p>
|
||||
<p>
|
||||
<img
|
||||
alt="location icon"
|
||||
src="/svgs/conference/schedule/location-icon.svg"
|
||||
/>
|
||||
{item.Location}
|
||||
</p>
|
||||
</FlexRow>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
render () {
|
||||
const tabClasses = {
|
||||
thursday: classNames({
|
||||
selected: (this.props.conferenceSchedule.day === 'thursday')
|
||||
}),
|
||||
friday: classNames({
|
||||
selected: (this.props.conferenceSchedule.day === 'friday')
|
||||
}),
|
||||
saturday: classNames({
|
||||
last: true,
|
||||
selected: (this.props.conferenceSchedule.day === 'saturday')
|
||||
})
|
||||
};
|
||||
const handleScheduleMethods = {
|
||||
thursday: () => {
|
||||
this.handleScheduleChange('thursday');
|
||||
},
|
||||
friday: () => {
|
||||
this.handleScheduleChange('friday');
|
||||
},
|
||||
saturday: () => {
|
||||
this.handleScheduleChange('saturday');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="schedule">
|
||||
<TitleBanner className="mod-conference">
|
||||
<h1>
|
||||
Schedule
|
||||
</h1>
|
||||
</TitleBanner>
|
||||
<SubNavigation>
|
||||
<li
|
||||
className={tabClasses.thursday}
|
||||
onClick={handleScheduleMethods.thursday}
|
||||
>
|
||||
<img
|
||||
alt="July 26th Icon"
|
||||
src="/svgs/conference/expect/july26-icon.svg"
|
||||
/>
|
||||
<span>Thursday</span>
|
||||
</li>
|
||||
<li
|
||||
className={tabClasses.friday}
|
||||
onClick={handleScheduleMethods.friday}
|
||||
>
|
||||
<img
|
||||
alt="July 27th Icon"
|
||||
src="/svgs/conference/expect/july27-icon.svg"
|
||||
/>
|
||||
<span>Friday</span>
|
||||
</li>
|
||||
<li
|
||||
className={tabClasses.saturday}
|
||||
onClick={handleScheduleMethods.saturday}
|
||||
>
|
||||
<img
|
||||
alt="July 28th Icon"
|
||||
src="/svgs/conference/expect/july28-icon.svg"
|
||||
/>
|
||||
<span>Saturday</span>
|
||||
</li>
|
||||
</SubNavigation>
|
||||
<div className="inner">
|
||||
{this.props.conferenceSchedule.timeSlots.map(timeSlot => ([
|
||||
<h2
|
||||
className="breaking-title"
|
||||
key={timeSlot.info.name}
|
||||
>
|
||||
<span>{timeSlot.info.name} – {timeSlot.info.time}</span>
|
||||
</h2>,
|
||||
this.renderChunkItems(timeSlot.items)
|
||||
]))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ConferenceSchedule.propTypes = {
|
||||
conferenceSchedule: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||
dispatch: PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
conferenceSchedule: state.conferenceSchedule
|
||||
});
|
||||
|
||||
const ConnectedSchedule = connect(mapStateToProps)(ConferenceSchedule);
|
||||
|
||||
render(
|
||||
<Page><ConnectedSchedule /></Page>,
|
||||
document.getElementById('app'),
|
||||
{conferenceSchedule: scheduleActions.scheduleReducer}
|
||||
);
|
150
src/views/conference/2018/schedule/schedule.scss
Normal file
150
src/views/conference/2018/schedule/schedule.scss
Normal file
|
@ -0,0 +1,150 @@
|
|||
@import "../../../../colors";
|
||||
@import "../../../../frameless";
|
||||
|
||||
.schedule {
|
||||
.title-banner {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.sub-nav {
|
||||
z-index: -1;
|
||||
box-shadow: 0 2px 5px $ui-dark-gray;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
border-top: 4px solid transparent;
|
||||
border-left: 2px solid $active-gray;
|
||||
border-radius: 0;
|
||||
padding: .75em 1em;
|
||||
color: $type-gray;
|
||||
font-size: 1rem;
|
||||
|
||||
&.last {
|
||||
border-right: 2px solid $active-gray;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&.selected {
|
||||
border-top: 4px solid $ui-orange;
|
||||
border-left: 2px solid $active-gray;
|
||||
box-shadow: none;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: .5em;
|
||||
width: 2em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.inner {
|
||||
h2 {
|
||||
&.breaking-title {
|
||||
margin: 4rem 0 2rem 0;
|
||||
border-bottom: 1px solid $ui-dark-gray;
|
||||
width: 100%;
|
||||
height: 1.7rem; // match the line-height for h2
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span {
|
||||
background-color: $background-color;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
&.item-url {
|
||||
display: block;
|
||||
|
||||
&:hover {
|
||||
background-color: $ui-blue-10percent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.agenda-item {
|
||||
margin: 1rem 0;
|
||||
border: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
padding: 1.25rem 2.25rem;
|
||||
|
||||
&.no-click {
|
||||
background-color: $ui-gray;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
margin: .5rem 0;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
width: 48%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: .5rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $mobile - 1) {
|
||||
.sub-nav {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.inner {
|
||||
h2 {
|
||||
&.breaking-title {
|
||||
margin: 2rem 0 2rem;
|
||||
height: 100%;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.agenda-item {
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $tablet - 1) {
|
||||
.inner {
|
||||
h2 {
|
||||
&.breaking-title {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
span {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.agenda-item {
|
||||
.flex-row {
|
||||
p {
|
||||
margin: .5rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -174,6 +174,14 @@ const Credits = () => (
|
|||
<span className="name">Tracy Tang</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<img
|
||||
alt="Bryce Avatar"
|
||||
src="//cdn.scratch.mit.edu/get_image/user/2029640_170x170.png"
|
||||
/>
|
||||
<span className="name">Bryce Taylor</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<img
|
||||
alt="Matthew Avatar"
|
||||
|
@ -198,6 +206,14 @@ const Credits = () => (
|
|||
<span className="name">Chris Willis-Ford</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<img
|
||||
alt="Kathy Avatar"
|
||||
src="//cdn.scratch.mit.edu/get_image/user/26779669_170x170.png"
|
||||
/>
|
||||
<span className="name">Kathy Wu</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<img
|
||||
alt="Julia Avatar"
|
||||
|
|
|
@ -55,8 +55,7 @@ class Download extends React.Component {
|
|||
downloadUrls = {
|
||||
mac: `${downloadPath}${this.state.swfVersion}.dmg`,
|
||||
mac105: `${downloadPath}${this.state.swfVersion}.air`,
|
||||
windows: `${downloadPath}${this.state.swfVersion}.exe`,
|
||||
linux: `${downloadPath}${this.state.swfVersion}.air`
|
||||
windows: `${downloadPath}${this.state.swfVersion}.exe`
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -143,12 +142,6 @@ class Download extends React.Component {
|
|||
<FormattedMessage id="download.download" />
|
||||
</a>
|
||||
</li>
|
||||
<li className="installation-downloads-item">
|
||||
<FormattedMessage id="download.linux" /> -
|
||||
{' '}<a href="http://airdownload.adobe.com/air/lin/download/2.6/AdobeAIRInstaller.bin">
|
||||
<FormattedMessage id="download.download" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="installation-column">
|
||||
|
@ -180,12 +173,6 @@ class Download extends React.Component {
|
|||
<FormattedMessage id="download.download" />
|
||||
</a>
|
||||
</li>
|
||||
<li className="installation-downloads-item">
|
||||
<FormattedMessage id="download.linux" /> -
|
||||
{' '}<a href={downloadUrls.linux}>
|
||||
<FormattedMessage id="download.download" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
]}
|
||||
{this.state.swfVersion === -1 ? [
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
@import "../../colors";
|
||||
@import "../../frameless";
|
||||
|
||||
$developer-spot: $splash-blue;
|
||||
|
||||
#view {
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -11,7 +9,7 @@ $developer-spot: $splash-blue;
|
|||
.title-banner {
|
||||
&.masthead {
|
||||
margin-bottom: 0;
|
||||
background-color: $developer-spot;
|
||||
background-color: $ui-blue-dark;
|
||||
padding-bottom: 0;
|
||||
|
||||
h1 {
|
||||
|
@ -32,10 +30,8 @@ $developer-spot: $splash-blue;
|
|||
}
|
||||
|
||||
.band {
|
||||
$band-color: #3baddd;
|
||||
|
||||
margin-top: 2rem;
|
||||
background-color: $band-color;
|
||||
background-color: $ui-white-15percent;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
|
@ -74,13 +70,17 @@ $developer-spot: $splash-blue;
|
|||
.installation-column {
|
||||
max-width: $cols4;
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
margin-right: .5rem;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.installation-column-number {
|
||||
margin: 2rem auto;
|
||||
border: 2px solid $active-gray;
|
||||
border-radius: 2rem;
|
||||
box-shadow: 0 0 0 .5rem lighten($ui-blue, 35);
|
||||
background-color: $ui-blue;
|
||||
width: 3.75rem;
|
||||
height: 3.75rem;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"download.title": "Scratch 2.0 Offline Editor",
|
||||
"download.intro": "You can install the Scratch 2.0 editor to work on projects without an internet connection. This version will work on Mac, Windows, and some versions of Linux (32 bit).",
|
||||
"download.intro": "You can install the Scratch 2.0 editor to work on projects without an internet connection. This version will work on Windows and MacOS.",
|
||||
"download.introMac": "<b>Note for Mac Users:</b> the latest version of Scratch 2.0 Offline requires Adobe AIR 20. To upgrade to Adobe AIR 20 manually, go <a href=\"https://get.adobe.com/air/\">here</a>.",
|
||||
"download.installation": "Installation",
|
||||
"download.airTitle": "Adobe AIR",
|
||||
|
@ -8,7 +8,6 @@
|
|||
"download.macOSX": "Mac OS X",
|
||||
"download.macOlder": "Mac OS 10.5 & Older",
|
||||
"download.windows": "Windows",
|
||||
"download.linux": "Linux",
|
||||
"download.download": "Download",
|
||||
"download.offlineEditorTitle": "Scratch Offline Editor",
|
||||
"download.offlineEditorBody": "Next download and install the Scratch 2.0 Offline Editor",
|
||||
|
|
274
src/views/ev3/ev3.jsx
Normal file
274
src/views/ev3/ev3.jsx
Normal file
|
@ -0,0 +1,274 @@
|
|||
const injectIntl = require('react-intl').injectIntl;
|
||||
const intlShape = require('react-intl').intlShape;
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const React = require('react');
|
||||
|
||||
|
||||
const Page = require('../../components/page/www/page.jsx');
|
||||
const render = require('../../lib/render.jsx');
|
||||
|
||||
const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||
|
||||
const OSChooser = require('../../components/os-chooser/os-chooser.jsx');
|
||||
|
||||
const ExtensionLanding = require('../../components/extension-landing/extension-landing.jsx');
|
||||
const ExtensionHeader = require('../../components/extension-landing/extension-header.jsx');
|
||||
const ExtensionRequirements = require('../../components/extension-landing/extension-requirements.jsx');
|
||||
const ExtensionSection = require('../../components/extension-landing/extension-section.jsx');
|
||||
const InstallScratchLink = require('../../components/extension-landing/install-scratch-link.jsx');
|
||||
const TipBox = require('../../components/extension-landing/tip-box.jsx');
|
||||
const ProjectCard = require('../../components/extension-landing/project-card.jsx');
|
||||
|
||||
const Steps = require('../../components/steps/steps.jsx');
|
||||
const Step = require('../../components/steps/step.jsx');
|
||||
|
||||
const OS_ENUM = require('../../components/extension-landing/os-enum.js');
|
||||
|
||||
require('../../components/extension-landing/extension-landing.scss');
|
||||
require('./ev3.scss');
|
||||
|
||||
class EV3 extends ExtensionLanding {
|
||||
render () {
|
||||
return (
|
||||
<div className="extension-landing ev3">
|
||||
<ExtensionHeader imageSrc="/images/ev3/ev3-illustration.png">
|
||||
<FlexRow className="column extension-copy">
|
||||
<h2><img src="/images/ev3/ev3.svg" />LEGO MINDSTORMS EV3</h2>
|
||||
<FormattedMessage
|
||||
id="ev3.headerText"
|
||||
values={{
|
||||
ev3Link: (
|
||||
<a
|
||||
href="https://shop.lego.com/en-US/LEGO-MINDSTORMS-EV3-31313"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
LEGO MINDSTORMS EV3
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</FlexRow>
|
||||
<ExtensionRequirements>
|
||||
<span>
|
||||
<img src="/svgs/extensions/windows.svg" />
|
||||
Windows 10+
|
||||
</span>
|
||||
<span>
|
||||
<img src="/svgs/extensions/mac.svg" />
|
||||
macOS 10.13+
|
||||
</span>
|
||||
<span>
|
||||
<img src="/svgs/extensions/bluetooth.svg" />
|
||||
Bluetooth
|
||||
</span>
|
||||
<span>
|
||||
<img src="/svgs/extensions/scratch-link.svg" />
|
||||
Scratch Link
|
||||
</span>
|
||||
</ExtensionRequirements>
|
||||
</ExtensionHeader>
|
||||
<OSChooser
|
||||
currentOS={this.state.OS}
|
||||
handleSetOS={this.onSetOS}
|
||||
/>
|
||||
<InstallScratchLink
|
||||
currentOS={this.state.OS}
|
||||
/>
|
||||
<ExtensionSection className="getting-started">
|
||||
<h2><FormattedMessage id="ev3.gettingStarted" /></h2>
|
||||
<FlexRow className="column getting-started-section">
|
||||
<h3><FormattedMessage id="ev3.connectingEV3" /></h3>
|
||||
<Steps>
|
||||
<Step number={1}>
|
||||
<div className="step-image">
|
||||
<img src="/images/ev3/ev3-connect-1.png" />
|
||||
</div>
|
||||
<p><FormattedMessage id="ev3.turnOnEV3" /></p>
|
||||
</Step>
|
||||
<Step number={2}>
|
||||
<div className="step-image">
|
||||
<img src="/images/ev3/ev3-connect-2.png" />
|
||||
</div>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="ev3.useScratch3"
|
||||
values={{
|
||||
scratch3Link: (
|
||||
<a
|
||||
href="https://beta.scratch.mit.edu/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Scratch 3.0
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</Step>
|
||||
<Step number={3}>
|
||||
<div className="step-image">
|
||||
<img src="/images/ev3/ev3-connect-3.png" />
|
||||
</div>
|
||||
<p><FormattedMessage id="ev3.addExtension" /></p>
|
||||
</Step>
|
||||
</Steps>
|
||||
<TipBox title={this.props.intl.formatMessage({id: 'ev3.firstTimeConnecting'})}>
|
||||
<p><FormattedMessage id="ev3.pairingDescription" /></p>
|
||||
<Steps>
|
||||
<Step>
|
||||
<div className="step-image">
|
||||
<img src="/images/ev3/ev3-accept-connection.png" />
|
||||
</div>
|
||||
<p><FormattedMessage id="ev3.acceptConnection" /></p>
|
||||
</Step>
|
||||
<Step>
|
||||
<div className="step-image">
|
||||
<img src="/images/ev3/ev3-pin.png" />
|
||||
</div>
|
||||
<p><FormattedMessage id="ev3.acceptPasscode" /></p>
|
||||
</Step>
|
||||
<Step>
|
||||
<div className="step-image">
|
||||
<img
|
||||
className="screenshot"
|
||||
src={`/images/ev3/${
|
||||
this.state.OS === OS_ENUM.WINDOWS ?
|
||||
'win-device-ready.png' :
|
||||
'mac-enter-passcode.png'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<p>
|
||||
{this.state.OS === OS_ENUM.WINDOWS ?
|
||||
<FormattedMessage id="ev3.windowsFinalizePairing" /> :
|
||||
<FormattedMessage id="ev3.macosFinalizePairing" />
|
||||
}
|
||||
</p>
|
||||
</Step>
|
||||
</Steps>
|
||||
</TipBox>
|
||||
</FlexRow>
|
||||
</ExtensionSection>
|
||||
<ExtensionSection className="blue things-to-try">
|
||||
<h2><FormattedMessage id="ev3.thingsToTry" /></h2>
|
||||
<h3><FormattedMessage id="ev3.makeMotorMove" /></h3>
|
||||
<Steps>
|
||||
<Step
|
||||
compact
|
||||
number={1}
|
||||
>
|
||||
<span className="step-description">
|
||||
<FormattedMessage
|
||||
id="ev3.plugMotorIn"
|
||||
values={{
|
||||
portA: (
|
||||
<strong><FormattedMessage id="ev3.portA" /></strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
<div className="step-image">
|
||||
<img src="/images/ev3/ev3-motor-port-a.png" />
|
||||
</div>
|
||||
</Step>
|
||||
<Step
|
||||
compact
|
||||
number={2}
|
||||
>
|
||||
<span className="step-description">
|
||||
<FormattedMessage
|
||||
id="ev3.clickMotorBlock"
|
||||
values={{
|
||||
motorBlockText: (
|
||||
<strong><FormattedMessage id="ev3.motorBlockText" /></strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
<div className="step-image">
|
||||
<img src="/images/ev3/motor-turn-block.png" />
|
||||
</div>
|
||||
</Step>
|
||||
</Steps>
|
||||
<hr />
|
||||
<h3><FormattedMessage id="ev3.starterProjects" /></h3>
|
||||
<Steps>
|
||||
<ProjectCard
|
||||
cardUrl="https://downloads.scratch.mit.edu/ev3/ev3-wave-hello.sb3"
|
||||
description={this.props.intl.formatMessage({id: 'ev3.waveHelloDescription'})}
|
||||
imageSrc="/images/ev3/starter-wave-hello.png"
|
||||
title={this.props.intl.formatMessage({id: 'ev3.waveHelloTitle'})}
|
||||
/>
|
||||
<ProjectCard
|
||||
cardUrl="https://downloads.scratch.mit.edu/ev3/ev3-distance-instrument.sb3"
|
||||
description={this.props.intl.formatMessage({id: 'ev3.distanceInstrumentDescription'})}
|
||||
imageSrc="/images/ev3/starter-distance-instrument.png"
|
||||
title={this.props.intl.formatMessage({id: 'ev3.distanceInstrumentTitle'})}
|
||||
/>
|
||||
<ProjectCard
|
||||
cardUrl="https://downloads.scratch.mit.edu/ev3/ev3-space-tacos.sb3"
|
||||
description={this.props.intl.formatMessage({id: 'ev3.spaceTacosDescription'})}
|
||||
imageSrc="/images/ev3/starter-flying-game.png"
|
||||
title={this.props.intl.formatMessage({id: 'ev3.spaceTacosTitle'})}
|
||||
/>
|
||||
</Steps>
|
||||
</ExtensionSection>
|
||||
<ExtensionSection className="faq">
|
||||
<h2><FormattedMessage id="ev3.troubleshootingTitle" /></h2>
|
||||
<h3 className="faq-title"><FormattedMessage id="ev3.makeSurePairedTitle" /></h3>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="ev3.makeSurePairedText"
|
||||
values={{
|
||||
pairingInstructionLink: (
|
||||
<a
|
||||
href="https://www.lego.com/en-us/service/help/products/themes-sets/mindstorms/connecting-your-lego-mindstorms-ev3-to-bluetooth-408100000007886"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage id="ev3.pairingInstructionText" />
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<h3 className="faq-title"><FormattedMessage id="ev3.closeScratchCopiesTitle" /></h3>
|
||||
<p>
|
||||
<FormattedMessage id="ev3.closeScratchCopiesText" />
|
||||
</p>
|
||||
<h3 className="faq-title"><FormattedMessage id="ev3.otherComputerConnectedTitle" /></h3>
|
||||
<p>
|
||||
<FormattedMessage id="ev3.otherComputerConnectedText" />
|
||||
</p>
|
||||
<h3 className="faq-title"><FormattedMessage id="ev3.updateFirmwareTitle" /></h3>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="ev3.updateFirmwareText"
|
||||
values={{
|
||||
firmwareUpdateLink: (
|
||||
<a
|
||||
href="https://education.lego.com/en-us/support/mindstorms-ev3/firmware-update"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage id="ev3.firmwareUpdateText" />
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</ExtensionSection>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EV3.propTypes = {
|
||||
intl: intlShape.isRequired
|
||||
};
|
||||
|
||||
const WrappedEV3 = injectIntl(EV3);
|
||||
|
||||
render(<Page><WrappedEV3 /></Page>, document.getElementById('app'));
|
8
src/views/ev3/ev3.scss
Normal file
8
src/views/ev3/ev3.scss
Normal file
|
@ -0,0 +1,8 @@
|
|||
@import "../../colors";
|
||||
|
||||
.ev3 {
|
||||
.extension-header {
|
||||
background-color: $ui-aqua;
|
||||
background-image: url("/images/ev3/ev3-pattern.svg");
|
||||
}
|
||||
}
|
38
src/views/ev3/l10n.json
Normal file
38
src/views/ev3/l10n.json
Normal file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"ev3.headerText": "{ev3Link} is an invention kit with motors and sensors you can use to build interactive robotic creations. Connecting it to Scratch expands the possibilities: build a robotic puppet and tell stories, make your own musical instruments and game controllers, or whatever else you can imagine.",
|
||||
"ev3.gettingStarted": "Getting Started",
|
||||
"ev3.connectingEV3": "Connecting EV3 to Scratch",
|
||||
"ev3.turnOnEV3": "Turn on your EV3 by holding down the center button.",
|
||||
"ev3.useScratch3": "Use the {scratch3Link} editor.",
|
||||
"ev3.addExtension": "Add the EV3 extension.",
|
||||
"ev3.firstTimeConnecting": "First time connecting your EV3?",
|
||||
"ev3.pairingDescription": "After clicking the connect button in Scratch, you will need to pair it with your computer:",
|
||||
"ev3.acceptConnection": "Accept the connection.",
|
||||
"ev3.acceptPasscode": "Accept the passcode.",
|
||||
"ev3.windowsFinalizePairing": "Wait for your device to be ready.",
|
||||
"ev3.macosFinalizePairing": "Enter the passcode on your computer.",
|
||||
"ev3.thingsToTry": "Things to Try",
|
||||
"ev3.makeMotorMove": "Make a motor move",
|
||||
"ev3.plugMotorIn": "Plug a motor into {portA} on the EV3 hub",
|
||||
"ev3.portA": "port A",
|
||||
"ev3.clickMotorBlock": "Find the {motorBlockText} block and click on it.",
|
||||
"ev3.motorBlockText": "\"motor A turn this way\"",
|
||||
"ev3.starterProjects": "Starter Projects",
|
||||
"ev3.waveHelloTitle": "Wave Hello",
|
||||
"ev3.waveHelloDescription": "Make a puppet robot and have a friendly chat.",
|
||||
"ev3.distanceInstrumentTitle": "Distance Instrument",
|
||||
"ev3.distanceInstrumentDescription": "Move your body in front of the sensor to make music.",
|
||||
"ev3.spaceTacosTitle": "Space Tacos",
|
||||
"ev3.spaceTacosDescription": "Build your own controller to catch tacos in space.",
|
||||
"ev3.troubleshootingTitle": "Troubleshooting",
|
||||
"ev3.makeSurePairedTitle": "Make sure your computer is paired with your EV3",
|
||||
"ev3.makeSurePairedText": "Your computer needs to be paired with your EV3 before it can connect to Scratch. We try to do this automatically the first time you add the EV3 extension, but if it isn't working you can try these {pairingInstructionLink}.",
|
||||
"ev3.pairingInstructionText": "bluetooth pairing instructions from LEGO",
|
||||
"ev3.closeScratchCopiesTitle": "Close other copies of Scratch",
|
||||
"ev3.closeScratchCopiesText": "Only one copy of Scratch can connect with the EV3 at a time. If you have Scratch open in other browser tabs, close it and try again.",
|
||||
"ev3.otherComputerConnectedTitle": "Make sure no other computer is connected to your EV3",
|
||||
"ev3.otherComputerConnectedText": "Only one computer can be connected to an EV3 at a time. If you have another computer connected to your EV3, disconnect the EV3 or close Scratch on that computer and try again.",
|
||||
"ev3.updateFirmwareTitle": "Try updating your EV3 firmware",
|
||||
"ev3.updateFirmwareText": "We recommend updating to EV3 firmware version 1.10E or above. See {firmwareUpdateLink}. We recommend following the instructions for \"Manual Firmware Update\".",
|
||||
"ev3.firmwareUpdateText": "firmware update instructions from LEGO"
|
||||
}
|
|
@ -209,7 +209,6 @@ class Explore extends React.Component {
|
|||
showViews={false}
|
||||
/>
|
||||
<Button
|
||||
className="white"
|
||||
onClick={this.handleGetExploreMore}
|
||||
>
|
||||
<FormattedMessage id="general.loadMore" />
|
||||
|
|
|
@ -114,6 +114,7 @@ $base-bg: $ui-white;
|
|||
.button {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 58.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,6 +129,12 @@ $base-bg: $ui-white;
|
|||
.sort-controls {
|
||||
width: $cols4;
|
||||
}
|
||||
|
||||
#projectBox {
|
||||
.button {
|
||||
width: $cols4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,6 +148,12 @@ $base-bg: $ui-white;
|
|||
.sort-controls {
|
||||
width: $cols6;
|
||||
}
|
||||
|
||||
#projectBox {
|
||||
.button {
|
||||
width: $cols6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,6 +174,9 @@ $base-bg: $ui-white;
|
|||
width: $cols9;
|
||||
}
|
||||
}
|
||||
.button {
|
||||
width: $cols9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
"faq.privacyCountry":"country",
|
||||
"faq.privacyBirthdate":"birth month and year - We use this to confirm ownership of the account if the owner loses the password and email or asks to close an account.",
|
||||
"faq.privacyGender":"gender",
|
||||
"faq.privacyEmail":"contact email address - If the account holder is younger than 13, we ask for the email address of their parent or guardian. We do not send email to this address except when someone requests to have the account password reset.",
|
||||
"faq.privacyEmail":"contact email address - If the account holder is younger than 16, we ask for the email address of their parent or guardian. We do not send email to this address except when someone requests to have the account password reset.",
|
||||
"faq.accountPublicInfo":"The username and country of the account holder are displayed publicly on their profile page. The birth month / year, email address, and gender associated with the account are not displayed publicly. We collect this info so we can know the age and gender of our users in aggregate, and for research purposes. We do not sell or rent information about our users to anyone.",
|
||||
"faq.dataCollectionTitle":"What data is collected from people while they use the website?",
|
||||
"faq.dataCollectionOne":"When a user logs in, the Scratch website asks their browser to put an <a href=\"http://en.wikipedia.org/wiki/HTTP_cookie\">http cookie</a> on their computer in order to remember that they are logged in while they browse different pages. We collect some data on where users click and which parts of the site they visit using Google Analytics. This \"click data\" helps us figure out ways to improve the website.",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue