├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── config
├── defaults.env
└── production.env
├── intl-config.json
├── npm_tasks
├── bin
│ └── build-html.js
├── build-config.js
├── build-html.js
├── get-pages.js
└── tests
│ ├── build-html.test.js
│ └── get-pages.test.js
├── package.json
├── src
├── colors.less
├── components
│ ├── action-menu
│ │ ├── action-menu.jsx
│ │ └── action-menu.less
│ ├── alert
│ │ ├── alert.jsx
│ │ └── alert.less
│ ├── basic-element
│ │ ├── basic-element.jsx
│ │ ├── basic-element.less
│ │ └── types
│ │ │ ├── image.jsx
│ │ │ ├── link.jsx
│ │ │ ├── page.jsx
│ │ │ ├── text.jsx
│ │ │ └── textedit.js
│ ├── button
│ │ └── button.less
│ ├── card
│ │ ├── card.jsx
│ │ └── card.less
│ ├── color-group
│ │ ├── color-group.jsx
│ │ └── color-group.less
│ ├── color-spectrum
│ │ ├── color-spectrum.jsx
│ │ └── color-spectrum.less
│ ├── d-pad
│ │ ├── d-pad.jsx
│ │ └── d-pad.less
│ ├── element-group
│ │ ├── element-group.jsx
│ │ └── element-group.less
│ ├── featured-tag-card
│ │ ├── featured-tag-card.jsx
│ │ └── featured-tag-card.less
│ ├── ftu
│ │ ├── ftu.jsx
│ │ └── ftu.less
│ ├── get-media
│ │ └── get-media.jsx
│ ├── link
│ │ ├── link.jsx
│ │ └── link.less
│ ├── loading
│ │ ├── loading.jsx
│ │ └── loading.less
│ ├── modal-confirm
│ │ ├── modal-confirm.jsx
│ │ └── modal-confirm.less
│ ├── modal-switch
│ │ ├── modal-switch.jsx
│ │ └── modal-switch.less
│ ├── option-panel
│ │ ├── option-panel.jsx
│ │ └── option-panel.less
│ ├── project-list
│ │ ├── project-list.jsx
│ │ └── project-list.less
│ ├── range
│ │ ├── range.jsx
│ │ └── range.less
│ ├── select
│ │ └── select.less
│ ├── shim
│ │ ├── shim.jsx
│ │ └── shim.less
│ ├── snackbar
│ │ ├── snackbar.jsx
│ │ └── snackbar.less
│ ├── spindicator
│ │ ├── spindicator.jsx
│ │ └── spindicator.less
│ ├── tabs
│ │ ├── tabs.jsx
│ │ └── tabs.less
│ └── text-input
│ │ ├── text-input.jsx
│ │ └── text-input.less
├── fonts.less
├── html
│ └── index.html
├── lib
│ ├── api.js
│ ├── cartesian.js
│ ├── color.js
│ ├── dispatcher.js
│ ├── errors.js
│ ├── i18n.js
│ ├── jsonUtils.js
│ ├── keyboard.js
│ ├── messages.js
│ ├── platform.js
│ ├── render.jsx
│ ├── router.js
│ ├── spec.js
│ ├── swipe.js
│ ├── touchhandler.js
│ ├── uuid.js
│ └── validators.js
├── locales
│ ├── bn-BD.yaml
│ ├── bn-IN.yaml
│ ├── en-GB.yaml
│ ├── en-US.yaml
│ ├── es-MX.yaml
│ ├── id-ID.yaml
│ └── pt-BR.yaml
├── main.less
├── normalize.less
├── pages
│ ├── discover
│ │ ├── discover.jsx
│ │ └── mock.json
│ ├── element
│ │ ├── element.jsx
│ │ ├── element.less
│ │ ├── font-selector.js
│ │ ├── image-editor.jsx
│ │ ├── link-editor.jsx
│ │ ├── page-editor.jsx
│ │ ├── text-editor.jsx
│ │ └── witheditable.js
│ ├── login
│ │ ├── form-input.jsx
│ │ ├── login.jsx
│ │ ├── login.less
│ │ ├── sign-in.jsx
│ │ └── sign-up.jsx
│ ├── make
│ │ ├── make.jsx
│ │ └── make.less
│ ├── page
│ │ ├── flattening.js
│ │ ├── loader.js
│ │ ├── page-controls.jsx
│ │ ├── page.jsx
│ │ └── page.less
│ ├── project-settings
│ │ ├── project-settings.jsx
│ │ └── project-settings.less
│ ├── project
│ │ ├── cartzoom.js
│ │ ├── dpad-logic.js
│ │ ├── form-pages.js
│ │ ├── loader.js
│ │ ├── pageadmin.js
│ │ ├── pageblock.jsx
│ │ ├── project.jsx
│ │ ├── project.less
│ │ ├── remix.js
│ │ ├── renderhelpers.js
│ │ ├── setdestination.js
│ │ └── transforms.js
│ ├── style-guide
│ │ ├── style-guide.jsx
│ │ └── style-guide.less
│ ├── tag-list
│ │ ├── tag-list.jsx
│ │ └── tag-list.less
│ ├── tinker
│ │ ├── tinker.jsx
│ │ └── tinker.less
│ ├── user-projects
│ │ ├── user-projects.jsx
│ │ └── user-projects.less
│ └── web-link
│ │ ├── web-link.jsx
│ │ └── web-link.less
├── static
│ ├── fonts
│ │ ├── FiraSans-Medium.woff
│ │ ├── FiraSans-Regular.woff
│ │ └── lamson-sharpie-webfont.woff
│ ├── img
│ │ ├── B-white.svg
│ │ ├── B.svg
│ │ ├── I-white.svg
│ │ ├── I.svg
│ │ ├── U-white.svg
│ │ ├── U.svg
│ │ ├── add.png
│ │ ├── align-center.svg
│ │ ├── align-left.svg
│ │ ├── align-right.svg
│ │ ├── avatar-icon.svg
│ │ ├── back-arrow.png
│ │ ├── brush.svg
│ │ ├── camera-gallery.svg
│ │ ├── camera.svg
│ │ ├── caret-down.svg
│ │ ├── cc.svg
│ │ ├── change-image.svg
│ │ ├── curved-arrow-wide.svg
│ │ ├── curved-arrow.svg
│ │ ├── default.svg
│ │ ├── demo.png
│ │ ├── external-url.svg
│ │ ├── flag.svg
│ │ ├── gray_and_white_checkers.gif
│ │ ├── link.svg
│ │ ├── material-check-on.png
│ │ ├── material-check.png
│ │ ├── more-dots.svg
│ │ ├── nub-white.svg
│ │ ├── nub.svg
│ │ ├── oval-droplet.svg
│ │ ├── oval-flag.svg
│ │ ├── oval-source.svg
│ │ ├── page-screenshot.png
│ │ ├── palette-icon.svg
│ │ ├── pencil-blue.svg
│ │ ├── pencil.svg
│ │ ├── plus.svg
│ │ ├── scrubber-white.svg
│ │ ├── scrubber.svg
│ │ ├── settings.svg
│ │ ├── slider.png
│ │ ├── take-photo.svg
│ │ ├── text.svg
│ │ ├── tinker.png
│ │ ├── toucan.svg
│ │ ├── trash.svg
│ │ ├── web-search.svg
│ │ ├── webmaker-icon.svg
│ │ ├── x.svg
│ │ ├── zoom-in.svg
│ │ ├── zoom-out-blue.svg
│ │ └── zoom-out.svg
│ └── js
│ │ └── Intl.js
├── tests
│ ├── api.test.js
│ ├── color-spectrum.test.js
│ ├── jsonUtils.test.js
│ ├── router.test.js
│ └── spec.test.js
└── variables.less
└── webpack.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig : http://EditorConfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | src/tests/**/*.js
2 | src/static/js/Intl.js
3 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "indent": [2, 2],
4 | "no-empty": [2],
5 | "no-console": [0],
6 | "comma-dangle": [2],
7 | "no-unused-vars": [0],
8 | "linebreak-style": [2, "unix"],
9 | "semi": [2, "always"]
10 | },
11 | "env": {
12 | "commonjs": true,
13 | "es6": true,
14 | "browser": true
15 | },
16 | "extends": "eslint:recommended",
17 | "ecmaFeatures": {
18 | "es6": true,
19 | "jsx": true,
20 | "experimentalObjectRestSpread": true
21 | },
22 | "plugins": [
23 | "react"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | config.js
4 | npm-debug.log*
5 | TODO.txt
6 | dest/
7 | .env
8 | src/locales/*.json
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '4.1.0'
4 | sudo: false
5 | cache:
6 | directories:
7 | - node_modules
8 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guidelines
2 |
3 | ## Reporting issues
4 |
5 | - **Search for existing issues.** Please check to see if someone else has reported the same issue.
6 | - **Share as much information as possible.** Please include your device model and OS version. Also, include steps to reproduce the bug.
7 |
8 | ## Code Style for Patches
9 |
10 | For basic file formatting rules, we use [EditorConfig](http://editorconfig.org/). There's an `.editorconfig` in the root of the project.
11 |
12 | Please ensure your editor either supports EditorConfig by default or has a plugin installed for it.
13 |
14 | ### JavaScript
15 |
16 | JS files must pass ESLint using the provided [.eslintrc](https://github.com/mozilla/webmaker-core/blob/develop/.eslintrc) settings.
17 |
18 | Run `npm test` before pushing a commit. It will validate your JS.
19 |
20 | #### Variable Naming
21 |
22 | - `lowerCamelCase` General variables
23 | - `UPPER_CASE` Constants
24 |
25 | ### HTML / JSX
26 |
27 | - 2 space indentation
28 | - Class names use hypenated case (e.g, `my-class-name`)
29 |
30 | ### LESS / CSS
31 |
32 | - 2 space indentation
33 | - Always a space after a property's colon (e.g, `display: block;` and not `display:block;`)
34 | - End all lines with a semi-colon
35 | - For multiple, comma-separated selectors, place each selector on it's own line
36 |
37 | ## Testing
38 |
39 | Please test your patch on an Android device if possible. If you don't have access to a physical device you can test using an Android emulator instead.
40 |
41 | ## Pull Requests
42 |
43 | - After opening your PR, add the `PR Needs Review` tag. One of our core contributors will then review your patch and either merge it or request further changes (also indicated by the `PR Needs Work` tag). If you are a core contributor, you may merge your own patch once it has passed peer review.
44 | - If possible, [squash your commits](http://davidwalsh.name/squash-commits-git).
45 | - Try to share which devices your code has been tested on when submitting a pull request.
46 |
--------------------------------------------------------------------------------
/config/defaults.env:
--------------------------------------------------------------------------------
1 | # Defaults
2 |
3 | CLIENT_ID='wm_id_zIPGbkEDB5Cv9dCzo7nS'
4 | API_URI='https://webmaker-api.herokuapp.com'
5 | LOGIN_URI='https://id.mofostaging.net'
6 |
--------------------------------------------------------------------------------
/config/production.env:
--------------------------------------------------------------------------------
1 | # Production
2 | # DO NOT COMMIT SECRETS
3 |
4 | API_URI='https://api.webmaker.org'
5 | LOGIN_URI='https://id.webmaker.org'
6 |
--------------------------------------------------------------------------------
/intl-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "supportedLocales": ["en-US", "en-GB", "pt-BR", "bn-BD", "id-ID","bn-IN"],
3 | "dest": "src/locales",
4 | "src": "src/locales",
5 | "type": "json"
6 | }
7 |
--------------------------------------------------------------------------------
/npm_tasks/bin/build-html.js:
--------------------------------------------------------------------------------
1 | var argv = require('minimist')(process.argv.slice(2));
2 | require('../build-html')(argv);
3 |
--------------------------------------------------------------------------------
/npm_tasks/build-config.js:
--------------------------------------------------------------------------------
1 | var habitat = require('habitat');
2 | var path = require('path');
3 | var fs = require('fs');
4 |
5 | var defaultPath = path.join(__dirname, '../config/defaults.env');
6 | var prodPath = path.join(__dirname, '../config/production.env');
7 |
8 | function fileErrors() {
9 | process.stderr.write(
10 | 'Looks like there is a problem with your config paths:\n' +
11 | '> ' + defaultPath + '\n' +
12 | '> ' + prodPath + '\n' +
13 | 'See npm_tasks/build-config.js\n'
14 | );
15 | process.exit(1);
16 | }
17 |
18 | // Check paths to make sure they exist
19 | try {
20 | if (!fs.statSync(defaultPath).isFile() || !fs.statSync(defaultPath)) {
21 | fileErrors();
22 | }
23 | } catch (e) {
24 | fileErrors();
25 | }
26 |
27 | // Local environment in .env overwrites everything else
28 | habitat.load('.env');
29 |
30 | var environment = habitat.get('NODE_ENV', '').toLowerCase();
31 |
32 |
33 | if (environment === 'production') {
34 | habitat.load(prodPath);
35 | }
36 |
37 | habitat.load(defaultPath);
38 |
39 | var config = {
40 | CLIENT_ID: habitat.get('CLIENT_ID'),
41 | API_URI: habitat.get('API_URI'),
42 | LOGIN_URI: habitat.get('LOGIN_URI')
43 | };
44 |
45 | process.stdout.write(
46 | '// THIS IS A GENERATED FILE. EDIT npm_tasks/build-config.js INSTEAD\n' +
47 | 'module.exports = ' + JSON.stringify(config) + ';\n'
48 | );
49 |
--------------------------------------------------------------------------------
/npm_tasks/build-html.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs-extra');
2 | var path = require('path');
3 | var getPages = require('./get-pages');
4 | require('colors');
5 |
6 | // For the command line utility, look in npm_tasks/bin
7 | module.exports = function (options) {
8 | options = options || {};
9 | var baseDir = options.baseDir || './dest/pages/';
10 | var template = options.template || fs.readFileSync('./src/html/index.html', {encoding: 'utf-8'});
11 | var pages = getPages();
12 | pages.forEach(function (page) {
13 | var html = template.replace('{{ js_src }}', '../../js/' + page + '.bundle.js');
14 | fs.outputFileSync(path.join(baseDir, page, '/index.html'), html);
15 | });
16 | console.log(('Built html for pages: ' + pages.join(', ') + '\n').green);
17 | };
18 |
--------------------------------------------------------------------------------
/npm_tasks/get-pages.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs-extra');
2 | var PAGES_DIR = './src/pages/';
3 | var path = require('path');
4 |
5 | module.exports = function (pagesDir) {
6 | pagesDir = pagesDir || PAGES_DIR;
7 | var pages = fs.readdirSync(pagesDir);
8 | return pages.filter(function (page) {
9 | var dirPath = path.join(pagesDir, page);
10 | if (!fs.statSync(dirPath).isDirectory()) return false;
11 | if (fs.readdirSync(dirPath).indexOf(page + '.jsx') === -1) return false;
12 | return true;
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/npm_tasks/tests/build-html.test.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs-extra');
2 | var should = require('should');
3 | var path = require('path');
4 | var tmp = require('tmp');
5 | var proxyquire = require('proxyquire');
6 |
7 | var fakePages= ['bar', 'baz', 'foo'];
8 | var fakeTemplate = '\n';
9 |
10 | var buildHtml = proxyquire('../build-html', {
11 | './get-pages': function () {
12 | return fakePages;
13 | }
14 | });
15 |
16 | tmp.setGracefulCleanup();
17 |
18 | describe('build-html (js)', function () {
19 | var fakeDir;
20 | beforeEach(function () {
21 | fakeDir = tmp.dirSync().name;
22 | buildHtml({
23 | baseDir: fakeDir,
24 | template: fakeTemplate
25 | });
26 | });
27 | it('should create directories in the base dir', function () {
28 | should.deepEqual(fs.readdirSync(fakeDir), fakePages);
29 | });
30 |
31 | fakePages.forEach(function (page) {
32 | it('should create an index.html for ' + page, function () {
33 | var filePath = path.join(fakeDir, page, 'index.html');
34 | should(fs.existsSync(filePath)).be.equal(true);
35 | });
36 | it('should contain the text ' + page + '.bundle.js', function () {
37 | var filePath = path.join(fakeDir, page, 'index.html');
38 | var output = fs.readFileSync(filePath, {encoding: 'utf-8'});
39 | should(output.match(page + '.bundle.js')).be.ok;
40 | });
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/npm_tasks/tests/get-pages.test.js:
--------------------------------------------------------------------------------
1 | var getPages = require('../get-pages');
2 | var fs = require('fs-extra');
3 | var path = require('path');
4 | var should = require('should');
5 | var tmp = require('tmp');
6 | tmp.setGracefulCleanup();
7 |
8 | function outputFakeFile(baseDir, name) {
9 | var f = fs.outputFileSync(path.join(baseDir, name), 'fake stuff 123\n');
10 | }
11 |
12 | describe('getPages', function () {
13 | var fakeDir;
14 | beforeEach(function () {
15 | fakeDir = tmp.dirSync().name;
16 | });
17 | it('should read the path directory in alpha order', function () {
18 | outputFakeFile(fakeDir, 'foo/foo.jsx');
19 | outputFakeFile(fakeDir, 'bar/bar.jsx');
20 | outputFakeFile(fakeDir, 'baz/baz.jsx');
21 | should.deepEqual(getPages(fakeDir), ['bar', 'baz', 'foo']);
22 | });
23 | it('should skip directories without properly formatted jsx files', function () {
24 | outputFakeFile(fakeDir, 'foo/foo.jsx');
25 | outputFakeFile(fakeDir, 'bar/bar.js');
26 | outputFakeFile(fakeDir, 'baz/index.jsx');
27 | fs.mkdir(path.join(fakeDir, 'qux'));
28 | should.deepEqual(getPages(fakeDir), ['foo']);
29 | });
30 | it('should return an empty array for an empty dir', function () {
31 | should.deepEqual(getPages(fakeDir), []);
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webmaker-core",
3 | "version": "1.6.1",
4 | "description": "Web core for Webmaker App",
5 | "scripts": {
6 | "start": "npm run build && npm-run-all --parallel webserver watch:**",
7 | "config": "node ./npm_tasks/build-config.js > src/config.js",
8 | "build": "npm-run-all build:**",
9 | "build:clean": "rimraf dest/ && mkdirp dest/",
10 | "build:static": "ncp src/static/ dest/",
11 | "build:l10njson": "yamlconvert intl-config.json",
12 | "build:js": "npm run config && webpack",
13 | "build:css": "npm run watch:css -- --no-watch",
14 | "build:html": "node ./npm_tasks/bin/build-html",
15 | "test": "npm run config && npm-run-all test:**",
16 | "test:mocha": "mocha -R spec --compilers js:babel/register \"./{src,npm_tasks}/**/*.test.js\"",
17 | "test:lint": "eslint --ext .js,.jsx src/",
18 | "watch:static": "watch \"npm run build:static\" src/static",
19 | "watch:js": "npm run build:js -- -d --watch",
20 | "watch:css": "autoless --source-map --autoprefix \"last 2 versions, android >= 4.2\" src dest",
21 | "watch:html": "watch \"npm run build:html\" src/html",
22 | "webserver": "live-server ./dest --port=4242"
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "https://github.com/mozilla/webmaker-core.git"
27 | },
28 | "author": "",
29 | "license": "MPL-2.0",
30 | "bugs": {
31 | "url": "https://github.com/mozilla/webmaker-core/issues"
32 | },
33 | "homepage": "https://github.com/mozilla/webmaker-core",
34 | "engines": {
35 | "node": "^4.1.0",
36 | "npm": "^2.7.4"
37 | },
38 | "devDependencies": {
39 | "autoless": "^0.1.7",
40 | "babel": "^5.3.3",
41 | "babel-core": "^5.8.25",
42 | "babel-loader": "^5.3.2",
43 | "colors": "^1.1.0",
44 | "eslint": "^1.3.1",
45 | "eslint-plugin-react": "^3.3.1",
46 | "fs-extra": "^0.24.0",
47 | "git-rev-sync": "^1.1.0",
48 | "habitat": "^3.1.2",
49 | "intl-locales-supported": "^1.0.0",
50 | "json-loader": "^0.5.1",
51 | "jsx-loader": "^0.13.2",
52 | "live-server": "^0.9.0",
53 | "minimist": "^1.1.1",
54 | "mkdirp": "^0.5.0",
55 | "mocha": "^2.2.1",
56 | "ncp": "^2.0.0",
57 | "npm-run-all": "^1.2.2",
58 | "proxyquire": "^1.4.0",
59 | "rimraf": "^2.3.2",
60 | "should": "^8.0.2",
61 | "tmp": "^0.0.28",
62 | "watch": "^0.16.0",
63 | "webpack": "^1.7.3",
64 | "yaml-intl-xml-json-converter": "0.0.7"
65 | },
66 | "dependencies": {
67 | "classnames": "2.2.1",
68 | "color": "0.10.1",
69 | "intl": "1.0.0",
70 | "lodash.defaults": "3.1.2",
71 | "lru-cache": "2.7.0",
72 | "owasp-password-strength-test": "1.3.0",
73 | "panzoom": "https://github.com/k88hudson/panzoom.git#b3b698d44ed12129c723f76efbd431678a34d96a",
74 | "react": "0.14.0",
75 | "react-addons-linked-state-mixin": "0.14.0",
76 | "react-addons-update": "0.14.0",
77 | "react-dom": "0.14.0",
78 | "react-hammerjs": "0.3.0",
79 | "react-intl": "1.2.0",
80 | "xhr": "2.2.0"
81 | },
82 | "jshintConfig": {
83 | "esnext": true
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/colors.less:
--------------------------------------------------------------------------------
1 | @blue: #669eff;
2 | @shadowBlue: #5793f2;
3 | @sapphire: #315d8a;
4 | @teal: #61d3c9;
5 | @slate: #6d7886;
6 | @darkSlate: #484B50;
7 | @heatherGrey: #cdd2dd;
8 | @lightGrey: #e9ebf0;
9 | @softGrey: #f2f6fc;
10 | @plum: #404467;
11 | @shadowPlum: #31344f;
12 | @brick: #EB5D52;
13 | @aqua: #9FD0E0;
14 | @green: #98CC3B;
15 | @yellow: #F0C337;
16 | @orange: #E26A1F;
17 | @purple: #8173E4;
18 | @black: #000;
19 | @white: #fff;
20 | @overlay: rgba(26, 34, 55, 70%);
21 |
--------------------------------------------------------------------------------
/src/components/action-menu/action-menu.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var classNames = require('classnames');
3 | var assign = require('react/lib/Object.assign');
4 | var Link = require('../link/link.jsx');
5 |
6 | var Menu = React.createClass({
7 | render: function () {
8 | return (
9 | {this.props.children}
10 |
);
11 | }
12 | });
13 |
14 | function makeButtonType(type) {
15 | return React.createClass({
16 | render: function () {
17 | var className = classNames(type, this.props.side, this.props.className, {
18 | off: this.props.off
19 | });
20 | var Tag = this.props.url ? Link : 'button';
21 | var props = assign({}, this.props, {className});
22 | return (
23 |
24 | {this.props.children}
25 | );
26 | }
27 | });
28 | }
29 |
30 | module.exports = {
31 | Menu,
32 | PrimaryButton: makeButtonType('primary-btn'),
33 | SecondaryButton: makeButtonType('secondary-btn'),
34 | FullWidthButton: makeButtonType('full-width-btn btn btn-teal')
35 | };
36 |
--------------------------------------------------------------------------------
/src/components/action-menu/action-menu.less:
--------------------------------------------------------------------------------
1 | /*********************************************************
2 | * Action Menu
3 | */
4 | .action-menu {
5 | @_bottomGutter: 10px;
6 | @_primarySize: 56px;
7 | @_fullWidthSize: 42px;
8 | @_secondarySize: 40px;
9 | @_spacing: 56px;
10 |
11 | position: fixed;
12 | left: 50%;
13 | bottom: @_bottomGutter;
14 | z-index: @ZINDEX_ACTION_OVERLAY;
15 | width: @_primarySize;
16 | margin-left: -(@_primarySize/2);
17 |
18 | &.full-width {
19 | left: 20px;
20 | right: 20px;
21 | width: auto;
22 | margin-left: 0;
23 | }
24 |
25 | &.off {
26 | transform: translateY(@_bottomGutter + @_primarySize);
27 | }
28 |
29 | .primary-btn,
30 | .secondary-btn,
31 | .full-width-btn {
32 | display: flex;
33 | align-items: center;
34 | justify-content: center;
35 | transition: transform 0.2s ease-in-out;
36 |
37 | .icon {
38 | display: block;
39 | }
40 | }
41 |
42 | .primary-btn,
43 | .secondary-btn {
44 | border-radius: 50%;
45 | border: none;
46 | }
47 |
48 | .primary-btn {
49 | width: @_primarySize;
50 | height: @_primarySize;
51 | background: @blue;
52 | color: @white;
53 | box-shadow: 0 4px 3px -2px rgba(0,0,0,0.2);
54 | position: absolute;
55 | bottom: 0;
56 | left: 0;
57 |
58 | .icon {
59 | height: 22px;
60 | }
61 |
62 | &.off {
63 | transform: translateY(@_bottomGutter + @_primarySize);
64 | }
65 | }
66 | .secondary-btn {
67 | position: absolute;
68 | left: 50%;
69 | margin-left: -(@_secondarySize/2);
70 | bottom: ((@_primarySize - @_secondarySize)/2);
71 | width: @_secondarySize;
72 | height: @_secondarySize;
73 | background: @white;
74 | color: @slate;
75 | box-shadow: 0 4px 3px -2px rgba(0,0,0,0.2);
76 | &.left {
77 | margin-left: -(@_secondarySize/2 + @_spacing);
78 | }
79 | &.right {
80 | margin-left: -(@_secondarySize/2 - @_spacing);
81 | }
82 | &.off {
83 | transform: translateY(@_bottomGutter + @_secondarySize * 2);
84 | }
85 |
86 | .icon {
87 | height: 15px;
88 | }
89 | }
90 | .btn.full-width-btn {
91 | height: @_fullWidthSize;
92 | padding: 0;
93 | position: absolute;
94 | bottom: 0;
95 | margin: 0;
96 | width: 100%;
97 |
98 | &.active,
99 | &:active {
100 | top: auto;
101 | bottom: -2px;
102 | }
103 |
104 | &.off {
105 | transform: translateY(@_bottomGutter + @_fullWidthSize);
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/components/alert/alert.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | var Alert = React.createClass({
4 | getInitialState: function () {
5 | return {
6 | isVisible: false
7 | };
8 | },
9 | show: function () {
10 | this.setState({
11 | isVisible: true
12 | });
13 | },
14 | hide: function () {
15 | this.setState({
16 | isVisible: false
17 | });
18 | },
19 | render: function () {
20 | return (
21 |
22 |
23 |
{this.props.children}
24 |
25 |
26 | );
27 | }
28 | });
29 |
30 | module.exports = Alert;
31 |
--------------------------------------------------------------------------------
/src/components/alert/alert.less:
--------------------------------------------------------------------------------
1 | .alert {
2 | align-items: center;
3 | background: @blue;
4 | border-radius: 5px;
5 | border: 1px solid rgba(0,67,104,0.36);
6 | color: @white;
7 | display: flex;
8 | margin: 10px 0;
9 | padding: 10px 20px;
10 |
11 | .text {
12 | flex-grow: 1;
13 | text-align: center;
14 | padding: 0 10px;
15 | }
16 |
17 | .dismiss {
18 | background: none;
19 | border: none;
20 | font-weight: 100;
21 | flex-shrink: 0;
22 | width: 10px;
23 | height: 15px;
24 | background: url(../img/x.svg) center center / contain no-repeat;
25 | }
26 |
27 | &.hidden {
28 | display: none;
29 | }
30 |
31 | .hidden {
32 | visibility: hidden;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/basic-element/basic-element.less:
--------------------------------------------------------------------------------
1 | .el-container {
2 | position: absolute;
3 | height: 1px;
4 | width: 100%;
5 | left: 50%;
6 | top: 50%;
7 | display: flex;
8 | align-items: center;
9 | justify-content: center;
10 | margin-left: -50%;
11 |
12 | .icon {
13 | position: relative;
14 | top: 2px;
15 | height: 1em;
16 | margin-right: 0.5em;
17 | }
18 | }
19 |
20 | .touch-overlay {
21 | position: fixed;
22 | top: 0;
23 | right: 0;
24 | bottom: 0;
25 | left: 0;
26 | z-index: @ZINDEX_MAX;
27 | }
28 |
29 | .el {
30 | flex-shrink: 0;
31 |
32 | user-select: none;
33 |
34 | position: relative;
35 | z-index: 0;
36 |
37 | &.current {
38 | margin: -1px;
39 | border: 1px dashed @white;
40 |
41 | &:after {
42 | z-index: -1;
43 | content: '';
44 | display: block;
45 | position: absolute;
46 | top: 0;
47 | left: 0;
48 | width: 100%;
49 | height: 100%;
50 | border: 1px dashed @blue;
51 | }
52 | }
53 | }
54 |
55 | /*********************************************************
56 | * Types
57 | */
58 |
59 | .el {
60 | box-sizing: content-box;
61 | // Text
62 | &.el-text {
63 | max-width: 320px;
64 | p {
65 | margin: 0;
66 | }
67 | }
68 |
69 | // Link
70 | &.el-link {
71 | .btn {
72 | margin: 0;
73 | //This line can probably be removed eventually, as box-shadow is being applied in the component properties now.
74 | //But removing it might affect existing projects, so hesitant to do so quite yet.
75 | box-shadow: inset 0px 2px 0px rgba(255,255,255, 0.3);
76 | display: flex;
77 | align-items: center;
78 |
79 | img {
80 | top: 1px;
81 | opacity: 0.8;
82 | height: 100%;
83 | margin: 0 0 0 8px;
84 | }
85 | }
86 | }
87 |
88 | // Image
89 | &.el-image {
90 | img {
91 | display: block;
92 | max-width: 300px;
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/components/basic-element/types/image.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var assign = require('react/lib/Object.assign');
3 | var Spec = require('../../../lib/spec');
4 |
5 | var BASE_DEFAULT_URL = 'https://stuff.webmaker.org/webmaker-android/default-images/';
6 | var TOTAL_PNGS = 10;
7 | var TOTAL_SVGS = 5;
8 |
9 | var spec = new Spec('image', assign({
10 | src: {
11 | category: 'attributes',
12 | validation: React.PropTypes.string,
13 | default: function () {
14 | var max = TOTAL_PNGS + TOTAL_SVGS;
15 | var randomInt = Math.floor(Math.random() * max);
16 | var ext;
17 | if (randomInt >= TOTAL_PNGS) {
18 | randomInt -= TOTAL_PNGS;
19 | ext = '.svg';
20 | } else {
21 | ext = '.jpg';
22 | }
23 | return BASE_DEFAULT_URL + randomInt + ext;
24 | }
25 | },
26 | alt: {
27 | category: 'attributes',
28 | validation: React.PropTypes.string,
29 | default: ''
30 | },
31 | opacity: {
32 | category: 'styles',
33 | validation: React.PropTypes.number,
34 | default: 1
35 | },
36 | borderWidth: {
37 | category: 'styles',
38 | validation: React.PropTypes.number,
39 | default: 0
40 | },
41 | borderColor: {
42 | category: 'styles',
43 | validation: React.PropTypes.string,
44 | editor: 'color',
45 | default: ''
46 | },
47 | borderRadius: {
48 | category: 'styles',
49 | validation: React.PropTypes.number,
50 | default: 0
51 | }
52 | }, Spec.getPositionProps()));
53 |
54 | module.exports = React.createClass({
55 |
56 | statics: {spec},
57 |
58 | propTypes: spec.getPropTypes(),
59 |
60 | getDefaultProps: function () {
61 | return spec.getDefaultProps();
62 | },
63 |
64 | render: function() {
65 | var props = this.props;
66 | var style = {
67 | opacity: props.opacity,
68 | borderStyle: 'solid',
69 | borderWidth: props.borderWidth,
70 | borderColor: props.borderColor,
71 | borderRadius: props.borderRadius
72 | };
73 | return
;
74 | }
75 | });
76 |
--------------------------------------------------------------------------------
/src/components/basic-element/types/link.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var assign = require('react/lib/Object.assign');
3 | var {getContrastingColor, darken} = require('../../../lib/color');
4 | var Spec = require('../../../lib/spec');
5 | var dispatcher = require('../../../lib/dispatcher');
6 | var Color = require("color");
7 |
8 | var spec = new Spec('link', assign({
9 | innerHTML: {
10 | category: 'attributes',
11 | validation: React.PropTypes.string,
12 | default: 'Button link'
13 | },
14 | targetPageId: {
15 | category: 'attributes',
16 | validation: React.PropTypes.string,
17 | default: ''
18 | },
19 | targetProjectId: {
20 | category: 'attributes',
21 | validation: React.PropTypes.string,
22 | default: ''
23 | },
24 | targetUserId: {
25 | category: 'attributes',
26 | validation: React.PropTypes.string,
27 | default: ''
28 | },
29 | targetWebURL: {
30 | category: 'attributes',
31 | validation: React.PropTypes.string,
32 | default: ''
33 | },
34 | href: {
35 | category: 'attributes',
36 | validation: React.PropTypes.string,
37 | default: ''
38 | },
39 | fontFamily: {
40 | category: 'styles',
41 | validation: React.PropTypes.string,
42 | default: 'sans-serif'
43 | },
44 | backgroundColor: {
45 | category: 'styles',
46 | validation: React.PropTypes.string,
47 | default: '#69A0FC',
48 | editor: 'color'
49 | },
50 | borderRadius: {
51 | category: 'styles',
52 | validation: React.PropTypes.number,
53 | default: 3
54 | },
55 | color: {
56 | category: 'styles',
57 | validation: React.PropTypes.string,
58 | editor: 'color'
59 | },
60 | boxShadow: {
61 | category: 'styles',
62 | validation: React.PropTypes.string,
63 | default: 'inset 0px 2px 0px rgba(255, 255, 255, 0.3)'
64 | }
65 | }, Spec.getPositionProps()));
66 |
67 | var Link = React.createClass({
68 |
69 | mixins: [
70 | require('./textedit')
71 | ],
72 |
73 | statics: {spec},
74 |
75 | propTypes: spec.getPropTypes(),
76 |
77 | getDefaultProps: function () {
78 | return assign(spec.getDefaultProps(), {
79 | active: false
80 | });
81 | },
82 |
83 | onClick: function (event) {
84 | if (this.state.editing) {
85 | this.activate();
86 | } else {
87 | dispatcher.fire('linkClicked', {
88 | props: this.props,
89 | originalEvent: event
90 | });
91 | }
92 | },
93 |
94 | render: function() {
95 | var props = this.props;
96 |
97 | var shadowOpacity = new Color(props.backgroundColor).alpha();
98 | var style = {
99 | borderRadius: props.borderRadius,
100 | backgroundColor: props.backgroundColor,
101 | border: `1px solid ${darken(props.backgroundColor, 0.4)}`,
102 | color: props.color || getContrastingColor(props.backgroundColor),
103 | fontFamily: props.fontFamily,
104 | whiteSpace: props.whiteSpace,
105 | boxShadow: `inset 0px 2px 0px rgba(255, 255, 255, ${0.3*shadowOpacity})`
106 | };
107 |
108 | var Element = this.props.activelink ? 'a' : 'span';
109 | var content = this.makeEditable(props.innerHTML, style);
110 |
111 | return (
112 |
113 | { content }
114 |
115 |
116 |
117 |
118 | );
119 | }
120 | });
121 |
122 | module.exports = Link;
123 |
--------------------------------------------------------------------------------
/src/components/basic-element/types/page.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var Spec = require('../../../lib/spec');
3 | var assign = require('react/lib/Object.assign');
4 |
5 | var spec = new Spec('page', {
6 | backgroundColor: {
7 | category: 'styles',
8 | validation: React.PropTypes.string,
9 | default: '#f2f6fc'
10 | }
11 | });
12 |
13 | var Page = React.createClass({
14 |
15 | statics: {spec},
16 |
17 | propTypes: spec.getPropTypes(),
18 |
19 | getDefaultProps: function () {
20 | return assign(spec.getDefaultProps(), {
21 | active: false
22 | });
23 | },
24 |
25 | render: function() {
26 | var style = {
27 | backgroundColor: this.props.backgroundColor,
28 | width: "100%",
29 | height: "100%"
30 | };
31 |
32 | return (
33 |
34 | );
35 | }
36 | });
37 |
38 | module.exports = Page;
39 |
--------------------------------------------------------------------------------
/src/components/basic-element/types/text.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var assign = require('react/lib/Object.assign');
3 | var Spec = require('../../../lib/spec');
4 | var HELLO_STRINGS = ['Hello', 'Bonjour', 'Halló', 'Hej', 'Olá', '¡Hola!', 'Ciao', 'Halo'];
5 |
6 | var spec = new Spec('text', assign({
7 | innerHTML: {
8 | category: 'attributes',
9 | validation: React.PropTypes.string,
10 | default: function () {
11 | var randomIndex = Math.floor(Math.random() * HELLO_STRINGS.length);
12 | return HELLO_STRINGS[randomIndex];
13 | }
14 | },
15 | fontFamily: {
16 | category: 'styles',
17 | validation: React.PropTypes.string,
18 | default: 'Roboto'
19 | },
20 | color: {
21 | category: 'styles',
22 | validation: React.PropTypes.string,
23 | default: '#E06A2C',
24 | editor: 'color'
25 | },
26 | backgroundColor: {
27 | category: 'styles',
28 | validation: React.PropTypes.string,
29 | default: 'transparent',
30 | editor: 'color'
31 | },
32 | fontSize: {
33 | category: 'styles',
34 | validation: React.PropTypes.number,
35 | default: 35
36 | },
37 | fontStyle: {
38 | category: 'styles',
39 | validation: React.PropTypes.string,
40 | default: 'normal'
41 | },
42 | fontWeight: {
43 | category: 'styles',
44 | validation: React.PropTypes.string,
45 | default: 'bold'
46 | },
47 | textDecoration: {
48 | category: 'styles',
49 | validation: React.PropTypes.string,
50 | default: 'none'
51 | },
52 | textAlign: {
53 | category: 'styles',
54 | validation: React.PropTypes.string,
55 | default: 'center'
56 | },
57 | padding: {
58 | category: 'styles',
59 | default: '0 10px'
60 | }
61 | }, Spec.getPositionProps()));
62 |
63 | module.exports = React.createClass({
64 | mixins: [
65 | require('./textedit')
66 | ],
67 |
68 | statics: {spec},
69 |
70 | propTypes: spec.getPropTypes(),
71 |
72 | getDefaultProps: function () {
73 | return spec.getDefaultProps();
74 | },
75 |
76 | render: function() {
77 | var props = this.props;
78 | var style = {};
79 |
80 | [
81 | 'fontFamily',
82 | 'color',
83 | 'fontWeight',
84 | 'fontSize',
85 | 'fontStyle',
86 | 'textDecoration',
87 | 'textAlign',
88 | 'backgroundColor',
89 | 'padding'
90 | ].forEach(function (prop) {
91 | style[prop] = props[prop];
92 | });
93 |
94 | var content = this.makeEditable(props.innerHTML, style);
95 | var onPClick = this.activate;
96 | if (this.state.editing) {
97 | onPClick = false;
98 | }
99 | return {content}
;
100 | }
101 | });
102 |
--------------------------------------------------------------------------------
/src/components/button/button.less:
--------------------------------------------------------------------------------
1 | /*********************************************************
2 | * Button
3 | */
4 | .btn {
5 | text-decoration: none;
6 | position: relative;
7 | padding: 12px 24px;
8 | margin-bottom: 10px;
9 | color: @slate;
10 | background: @white;
11 | border: none;
12 | border-radius: 3px;
13 | box-shadow: 0 2px 0 0 @heatherGrey;
14 | display: inline-flex;
15 | align-items: center;
16 | justify-content: center;
17 | .active {
18 | box-shadow: none;
19 | top: 2px;
20 | }
21 | &.btn-block {
22 | display: flex;
23 | width: 100%;
24 | }
25 | &:active {
26 | .active;
27 | }
28 | .icon {
29 | height: 1.2em;
30 | &:first-child {
31 | margin-right: 0.3em;
32 | }
33 | &:last-child {
34 | margin-left: 0.3em;
35 | }
36 | }
37 | &.btn-teal {
38 | background: @teal;
39 | color: @white;
40 | border-radius: 30px;
41 | box-shadow: 0 2px 0 0 darken(@teal, 20%);
42 | &:active {
43 | .active;
44 | }
45 | }
46 | &.btn-blue {
47 | background: @shadowBlue;
48 | color: @white;
49 | box-shadow: 0 2px 0 0 darken(@shadowBlue, 20%);
50 | &:active {
51 | .active;
52 | }
53 | }
54 | &.btn-rounded {
55 | background: #FDFDFE;
56 | border-radius: 40px;
57 | font-family: FiraSans-Medium;
58 | color: #71A5FF;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/card/card.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var Link = require('../link/link.jsx');
3 |
4 | var Card = React.createClass({
5 | statics: {
6 | DEFAULT_THUMBNAIL: '../../img/default.svg'
7 | },
8 | getDefaultProps: function(){
9 | return {
10 | showAuthor: true,
11 | showActions: false
12 | };
13 | },
14 | actionsClicked: function (e) {
15 | e.preventDefault();
16 | e.stopPropagation();
17 | this.props.onActionsClick.call(this, this.props);
18 | },
19 | onImageError: function() {
20 | var imageEl = this.refs.imageEl;
21 | imageEl.src = Card.DEFAULT_THUMBNAIL;
22 | imageEl.onerror = null;
23 | },
24 | componentDidMount: function () {
25 | var imageEl = this.refs.imageEl;
26 | imageEl.onerror = this.onImageError;
27 | imageEl.src = this.props.thumbnail || Card.DEFAULT_THUMBNAIL;
28 | },
29 | render: function () {
30 | function tagifyDescription (description) {
31 | //
32 | // ༼ つ ◕_◕ ༽つ <( HEY, YOU!!! )
33 | //
34 | // If this regex changes, be sure to update the
35 | // corresponding regex in api.webmaker.org's tagging code!
36 | var hashFinder = /(#[A-Za-z\d]+)/g;
37 |
38 | var words = description.split(hashFinder);
39 |
40 | if (words.length) {
41 | words.forEach((word, index) => {
42 | if (word.length)
43 | if (word.match(hashFinder)) {
44 | words[index] = React.createElement(Link, {key: index, href: `/pages/tag-list`, url: `/tags/${word.slice(1)}`}, word);
45 | } else {
46 | words[index] = React.createElement(`span`, {key: index}, word);
47 | }
48 | });
49 | }
50 |
51 | return words;
52 | }
53 |
54 | return (
55 |
56 |
57 |
58 |
![]()
59 |
60 |
61 |
62 |
63 |
64 |

65 |
66 |
{this.props.title}
67 |
{this.props.author.username}
68 |
69 |
70 |
73 |
74 |
75 |
76 | {tagifyDescription(this.props.description)}
77 |
78 |
79 |
80 |
81 | );
82 | }
83 | });
84 |
85 | module.exports = Card;
86 |
--------------------------------------------------------------------------------
/src/components/card/card.less:
--------------------------------------------------------------------------------
1 | .card {
2 | display: block;
3 | position: relative;
4 |
5 | width: calc(100% - 10px);
6 | margin: 0 auto 20px auto;
7 |
8 | color: @slate;
9 | text-decoration: none;
10 | background-color: white;
11 | box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.3);
12 |
13 | .avatar{
14 | margin-right: 10px;
15 | img{
16 | display: block;
17 | }
18 | }
19 |
20 | .thumbnail {
21 | padding: 0;
22 | height: 0;
23 | padding-bottom: 61.875%;
24 | img {
25 | display: block;
26 | position: relative;
27 |
28 | width: 100%;
29 | margin: 0 auto;
30 | background: #efefef;
31 | }
32 | }
33 |
34 | .meta {
35 | width: 100%;
36 | margin: 0 auto;
37 | padding: 10px;
38 | font-size: 1.2rem;
39 |
40 | .wrapper {
41 | display: flex;
42 | align-items: center;
43 | align-content: center;
44 | }
45 |
46 | .description {
47 | margin-top: 10px;
48 | font-size: 15px;
49 |
50 | a {
51 | color: @blue;
52 | text-decoration: none;
53 | }
54 | }
55 |
56 | .text {
57 | flex: 1;
58 | word-wrap: break-word;
59 | padding: 5px 0 0 0;
60 | }
61 |
62 | .title {
63 | line-height: 1.2rem;
64 | }
65 |
66 | .action {
67 | button {
68 | margin: 5px 0 0 0;
69 | padding: 5px 5px 5px 20px; // ~40x40px tap size
70 | border: 0;
71 | background: none;
72 | line-height: 0;
73 | img {
74 | width: 5px;
75 | }
76 | }
77 | }
78 | }
79 |
80 |
81 | .author {
82 | color: @slate;
83 | opacity: 0.6;
84 | font-size: 1rem;
85 | text-decoration: none;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/components/color-group/color-group.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var LinkedStateMixin = require('react-addons-linked-state-mixin');
3 | var classNames = require('classnames');
4 |
5 | var ColorGroup = React.createClass({
6 | statics: {
7 | defaultColors: ['transparent', '#FFF', '#99CA47', '#EFC246', '#E06A2C', '#69A0FC']
8 | },
9 | mixins: [LinkedStateMixin],
10 | getDefaultProps: function () {
11 | return {
12 | colors: this.defaultColors.slice(),
13 | id: 'value'
14 | };
15 | },
16 | getInitialState: function () {
17 | return {
18 | value: this.props.colors[0]
19 | };
20 | },
21 | getTinkerUrl: function () {
22 | var params = this.props.params;
23 | if (!params) {
24 | return;
25 | }
26 | return `/users/${params.user}/projects/${params.project}/pages/${params.page}/elements/${params.element}/propertyName/${this.props.id}`;
27 | },
28 | onChange: function (e) {
29 | if (this.valueLink) {
30 | this.valueLink.requestChange(e.target.value);
31 | }
32 | if (typeof this.props.onChange === 'function') {
33 | this.props.onChange(e.target.value);
34 | }
35 | },
36 |
37 | // Terrible hack to allow us to save before going to
38 | // tinker mode
39 | launchTinker: function (e) {
40 |
41 | // Call on change so that we update the border colour if needed
42 | if (typeof this.props.onChange === 'function') {
43 | this.props.onChange(this.valueLink.value);
44 | }
45 |
46 | if (!window.Platform) {
47 | return;
48 | }
49 |
50 | e.preventDefault();
51 |
52 | var launch = () => {
53 | window.Platform.setView(this.getTinkerUrl());
54 | };
55 |
56 | if (this.props.onLaunchTinker) {
57 | this.props.onLaunchTinker(launch);
58 | } else {
59 | launch();
60 | }
61 | },
62 |
63 | render: function () {
64 | var colors = this.props.colors;
65 | var linkState = this.props.linkState || this.linkState;
66 | this.valueLink = linkState(this.props.id);
67 |
68 | // If current color is custom, add it to the list
69 | if (this.valueLink.value && colors.indexOf(this.valueLink.value) === -1) {
70 | colors = colors.concat([this.valueLink.value]);
71 | }
72 |
73 | return (
74 |
75 | {colors.map(color => {
76 | var className = {
77 | 'color-swatch': true,
78 | checked: this.valueLink.value === color
79 | };
80 | var innerClassName = {
81 | 'color-swatch-inner': true,
82 | transparent: color === 'transparent',
83 | white: color === '#FFF'
84 | };
85 | return (
);
89 | })}
90 |
95 |
);
96 | }
97 | });
98 |
99 | module.exports = ColorGroup;
100 |
--------------------------------------------------------------------------------
/src/components/color-group/color-group.less:
--------------------------------------------------------------------------------
1 | /*********************************************************
2 | * Color
3 | */
4 | .color-group {
5 | @_swatchSize: 35px;
6 | @_swatchMargin: 15px;
7 | @_selectedSize: 12px;
8 |
9 | display: flex;
10 | align-items: center;
11 | .color-swatch {
12 | margin: 0;
13 | display: block;
14 | padding: 0;
15 | width: @_swatchSize;
16 | margin-right: @_swatchMargin;
17 | border: none;
18 | position: relative;
19 | background: none;
20 | &.checked::after {
21 | content: '';
22 | display: block;
23 | background: white;
24 | width: @_selectedSize;
25 | height: 0;
26 | padding-bottom: @_selectedSize;
27 | border-radius: 50%;
28 | position: absolute;
29 | top: 50%;
30 | left: 50%;
31 | margin-top: -(@_selectedSize/2);
32 | margin-left: -(@_selectedSize/2);
33 | box-shadow: 0 1px 1px 1px fade(#000, 10%);
34 | }
35 | .color-swatch-inner {
36 | cursor: pointer;
37 | display: block;
38 | position: relative;
39 | border-radius: 50%;
40 | width: 100%;
41 | height: 0;
42 | padding-bottom: 100%;
43 | background: white;
44 | box-shadow: inset 0 1px 1px 1px fade(#000, 10%);
45 | &.transparent, &.white {
46 | box-shadow: inset 0 0 1px 1px darken(#E9EBF0, 10%);
47 | }
48 | &.transparent::after {
49 | // This is the strikethrough
50 | content: '';
51 | display: block;
52 | width: 100%;
53 | height: 2px;
54 | background-color: @brick;
55 | position: absolute;
56 | top: 50%;
57 | left: 0;
58 | transform: rotate(-45deg);
59 | }
60 | }
61 | }
62 | .tinker-container {
63 | flex-grow: 1;
64 | justify-content: flex-end;
65 | .tinker {
66 | width: @_swatchSize;
67 | display: block;
68 | img {
69 | width: 100%;
70 | display: block;
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/components/color-spectrum/color-spectrum.less:
--------------------------------------------------------------------------------
1 | /*********************************************************
2 | * Spectrum color picker
3 | */
4 | //Default height of the picker, but gets overridden in tinker page for its layout
5 | .spectrum-container {
6 | @_spectrum-pickerHeight: 140px;
7 | @_rangeWidth: 40px;
8 | @_margin: 20px;
9 | position: relative;
10 | display: flex;
11 | .spectrum-left {
12 | position: relative;
13 | flex-grow: 1;
14 | }
15 | .saturation {
16 | position: relative;
17 | width: 100%;
18 | height: @_spectrum-pickerHeight;
19 | }
20 | .saturation-white,
21 | .saturation-black {
22 | position: absolute;
23 | top: 0;
24 | left: 0;
25 | width: 100%;
26 | height: 100%;
27 | }
28 | .saturation-white {
29 | background: linear-gradient(to right, #fff, rgba(204,154,129,0));
30 | }
31 | .saturation-black {
32 | background: linear-gradient(to top, #000, rgba(204,154,129,0));
33 | }
34 | .sl-marker {
35 | width: 20px;
36 | height: 20px;
37 | border-radius: 20px;
38 | border: 2px solid white;
39 | box-shadow: 0 0 0 2px fade(black, 10%);
40 | position: absolute;
41 | margin-left: -10px;
42 | margin-top: -10px;
43 | }
44 | .spectrum-right {
45 | position: relative;
46 | margin-left: @_margin;
47 | width: @_rangeWidth;
48 | background: linear-gradient(to bottom, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
49 | cursor: pointer;
50 | }
51 | .hue {
52 | -webkit-appearance: none;
53 | transform: rotate(90deg);
54 | transform-origin: 0 0;
55 | position: absolute;
56 | top: @_margin;
57 | width: @_spectrum-pickerHeight;
58 | height: @_rangeWidth;
59 | margin-top: -(@_rangeWidth/2);
60 | z-index: 1;
61 | background: transparent;
62 | }
63 | .hue::-webkit-slider-thumb {
64 | -webkit-appearance: none;
65 | border: none;
66 | width: 1px;
67 | height: 46px;
68 | cursor: pointer;
69 | background: transparent;
70 | &::after {
71 | position: absolute;
72 | content: '';
73 | width: 20px;
74 | height: 46px;
75 | margin-left: -10px;
76 | background: url(./img/slider.png) no-repeat;
77 | background-size: 20px 46px;
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/components/d-pad/d-pad.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | module.exports = React.createClass({
4 | propTypes: {
5 | onDirectionClick: React.PropTypes.func, // External handler for button clicks
6 | isVisible: React.PropTypes.bool
7 | },
8 | getInitialState: function () {
9 | return {
10 | showUp: true,
11 | showDown: true,
12 | showLeft: true,
13 | showRight: true
14 | };
15 | },
16 | onButtonClick: function (direction, event) {
17 | this.props.onDirectionClick.call(this, direction);
18 | },
19 | bulkSetVisibility: function (newState) {
20 | this.setState(newState);
21 | },
22 | positionAroundContainer: function (boundingRect) {
23 | if (!boundingRect) {
24 | return;
25 | }
26 |
27 | this.refs.btnUp.style.bottom = boundingRect.bottom - 3 + 'px';
28 | this.refs.btnDown.style.top = boundingRect.bottom - 3 + 'px';
29 | this.refs.btnLeft.style.right = boundingRect.right - 3 + 'px';
30 | this.refs.btnRight.style.left = boundingRect.right - 3 + 'px';
31 | },
32 | render: function () {
33 | return (
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | }
42 | });
43 |
--------------------------------------------------------------------------------
/src/components/d-pad/d-pad.less:
--------------------------------------------------------------------------------
1 | .dPad {
2 | button.moon {
3 | @moon-width: 33px;
4 |
5 | border-bottom-left-radius: 50%;
6 | border-bottom-right-radius: 50%;
7 | background: rgba(109, 120, 135, 0.9) url(./img/nub-white.svg) no-repeat;
8 | background-position: center 1.225 * @moon-width;
9 | background-size: 0.525 * @moon-width;
10 | width: @moon-width * 2;
11 | height: @moon-width * 2;
12 |
13 | &.up {
14 | top: -@moon-width - 2;
15 | margin-left: -@moon-width;
16 | }
17 |
18 | &.down {
19 | bottom: -@moon-width - 2;
20 | margin-left: -@moon-width;
21 | }
22 |
23 | &.left {
24 | left: -@moon-width - 2;
25 | margin-top: -@moon-width;
26 | }
27 |
28 | &.right {
29 | right: -@moon-width - 2;
30 | margin-top: -@moon-width;
31 | }
32 | }
33 |
34 | button {
35 | @nub-width: 30px;
36 |
37 | position: absolute;
38 | display: block;
39 | z-index: 9999;
40 | background: url(./img/nub.svg) no-repeat center center / 100%;
41 | border: 0;
42 | width: @nub-width;
43 | height: @nub-width;
44 |
45 | &.up {
46 | left: 50%;
47 | margin-left: -(@nub-width / 2);
48 | }
49 |
50 | &.down {
51 | left: 50%;
52 | margin-left: -(@nub-width / 2);
53 | transform: rotate(180deg);
54 | }
55 |
56 | &.left {
57 | top: 50%;
58 | margin-top: -(@nub-width / 2);
59 | transform: rotate(-90deg);
60 | }
61 |
62 | &.right {
63 | top: 50%;
64 | margin-top: -(@nub-width / 2);
65 | transform: rotate(90deg);
66 | }
67 |
68 | }
69 |
70 | &.hidden, .hidden {
71 | display: none;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/components/element-group/element-group.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var BasicElement = require('../../components/basic-element/basic-element.jsx');
3 | var assign = require('react/lib/Object.assign');
4 |
5 | var ElementGroup = React.createClass({
6 | getDefaultProps: function () {
7 | return {
8 | elements: {},
9 | currentElementId: -1,
10 | interactive: false
11 | };
12 | },
13 |
14 | /**
15 | * Generate the JSX for the element
16 | * @param {id} elementId the element's id in the this.props-passed `elements` dictionary
17 | * @param {object} properties The element property sheet from which to build the JSX representation
18 | * @return {JSX} an element's JSX representation
19 | */
20 | formElement: function(elementId, properties) {
21 |
22 | return (
23 |
24 |
25 |
26 | );
27 | },
28 |
29 | /**
30 | * Process an element's property sheet, transforming it into
31 | * a JSX object for use by React,
32 | * @param {id} elementId the element's id in the this.props-passed `elements` dictionary
33 | * @return {JSX} an element's JSX representation
34 | */
35 | processProperties: function(elementId) {
36 | var properties = this.props.elements[elementId];
37 |
38 | if (!properties || !properties.type) {
39 | return false;
40 | }
41 |
42 | properties = assign({}, properties, {
43 | isCurrent: +this.props.currentElementId === +elementId,
44 | interactive: this.props.interactive
45 | });
46 |
47 | // Add callbacks for interactive mode
48 | if (this.props.onTouchEnd) {
49 | properties.onTouchEnd = this.props.onTouchEnd(elementId);
50 | }
51 |
52 | if (this.props.onTap) {
53 | properties.onTap = this.props.onTap(elementId);
54 | }
55 |
56 | if (this.props.onUpdate) {
57 | properties.onUpdate = this.props.onUpdate(elementId);
58 | }
59 |
60 | return this.formElement(elementId, properties);
61 | },
62 |
63 | /**
64 | * Convert all passed element property sheets into JSX elemenets
65 | * @return {JSX[]} And array of JSX representations of each element in this.props.elemeents
66 | */
67 | formElements: function () {
68 | return Object.keys(this.props.elements).map(this.processProperties);
69 | },
70 |
71 | render: function () {
72 | return (
73 |
74 |
75 | { this.formElements() }
76 |
77 | );
78 | }
79 | });
80 |
81 | module.exports = ElementGroup;
82 |
--------------------------------------------------------------------------------
/src/components/element-group/element-group.less:
--------------------------------------------------------------------------------
1 | /*********************************************************
2 | * Positionables
3 | */
4 | .element-group {
5 | position: relative;
6 | overflow: visible;
7 | height: 100%;
8 | width: 100%;
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/featured-tag-card/featured-tag-card.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var Link = require('../link/link.jsx');
3 |
4 | var FeaturedTagCard = React.createClass({
5 | mixins: [
6 | require('react-intl').IntlMixin
7 | ],
8 | render: function () {
9 | return (
10 |
11 | {this.getIntlMessage('featured')}
12 | #{this.props.tag}
13 |
14 | );
15 | }
16 | });
17 |
18 | module.exports = FeaturedTagCard;
19 |
--------------------------------------------------------------------------------
/src/components/featured-tag-card/featured-tag-card.less:
--------------------------------------------------------------------------------
1 | .featured-tag-card{
2 | background-color: @teal;
3 | color: white;
4 | padding: 10px 10px 20px;
5 |
6 | .featured{
7 | text-transform: uppercase;
8 | font-size: .75rem;
9 | }
10 | .tag{
11 | text-align: center;
12 | font-size: 1.7rem;
13 | line-height: 1;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/ftu/ftu.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | var FTU = React.createClass({
4 | mixins: [require('react-intl').IntlMixin],
5 | propTypes: {
6 | singleMessage: React.PropTypes.string,
7 | rightMessage: React.PropTypes.string,
8 | leftMessage: React.PropTypes.string
9 | },
10 | getInitialState: function () {
11 | return {
12 | isVisible: false
13 | };
14 | },
15 | hide: function () {
16 | this.setState({
17 | isVisible: false
18 | });
19 | },
20 | show: function () {
21 | this.setState({
22 | isVisible: true
23 | });
24 | },
25 | onGotItClicked: function () {
26 | this.hide();
27 | this.props.onViewed();
28 | },
29 | render: function () {
30 | return (
31 |
32 |
33 |

34 |
{ this.props.singleMessage }
35 |

36 |
37 |
38 |
39 |
40 |

41 |
{ this.props.leftMessage }
42 |
43 |
44 |

45 |
{ this.props.rightMessage }
46 |
47 |
48 |
49 |
50 |
51 | );
52 | }
53 | });
54 |
55 | module.exports = FTU;
56 |
--------------------------------------------------------------------------------
/src/components/ftu/ftu.less:
--------------------------------------------------------------------------------
1 | .ftu {
2 | background-image: linear-gradient(-180deg, rgba(76,136,239,0.94) 25%, rgba(76,136,239,0.50) 93%);
3 | color: @white;
4 | margin: 0;
5 | padding: 0;
6 | z-index: @ZINDEX_MAX;
7 | position: absolute;
8 | top: 0;
9 | left: 0;
10 | width: 100%;
11 | height: 100%;
12 | position: fixed;
13 | text-align: center;
14 |
15 | &.hidden {
16 | display: none;
17 | }
18 |
19 | .helpers {
20 | display: none;
21 | justify-content: center;
22 | font-family: "Sharpie", "Chalkboard", "Comic Sans", serif, sans-serif;
23 | padding: 10px;
24 |
25 | &.active {
26 | display: flex;
27 | }
28 |
29 | &.helpers-single {
30 | align-items: flex-start;
31 |
32 | .invisible {
33 | visibility: hidden;
34 | }
35 |
36 | p {
37 | padding-top: 10px;
38 | }
39 | }
40 |
41 | div {
42 | text-align: center;
43 | padding: 0 20px 10px 20px;
44 | flex: 1;
45 |
46 | &:nth-child(1) img {
47 | transform: scaleX(-1);
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/components/get-media/get-media.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var classNames = require('classnames');
3 |
4 | var GetMedia = React.createClass({
5 | propTypes: {
6 | // Show or hide the component
7 | show: React.PropTypes.bool,
8 |
9 | // Runs when either "Gallery" or "Camera" is clicked
10 | onStartMedia: React.PropTypes.func,
11 |
12 | // Runs if window.Platform is not available (i.e. in a browser)
13 | onNoCamera: React.PropTypes.func,
14 |
15 | // Runs run an image URI is ready
16 | onImageReady: React.PropTypes.func,
17 |
18 | // Runs if the user exits out of the
19 | // camera or the menu
20 | onCancel: React.PropTypes.func
21 | },
22 | componentDidMount: function () {
23 | // Expose image handler to Android
24 | window.imageReady = this.imageReady;
25 | window.onMediaCancel = this.onMediaCancel;
26 | },
27 | imageReady: function (uri) {
28 | if (this.props.onImageReady) {
29 | this.props.onImageReady(uri);
30 | }
31 | },
32 | onMediaCancel: function () {
33 | if (this.props.onCancel) {
34 | this.props.onCancel();
35 | }
36 | },
37 | openMedia: function (type) {
38 | if (window.Platform) {
39 | if (type === 'gallery') {
40 | window.Platform.getFromMedia();
41 | } else {
42 | window.Platform.getFromCamera();
43 | }
44 | } else if (this.props.onNoCamera) {
45 | this.props.onNoCamera();
46 | } else if (this.props.onCancel) {
47 | this.props.onCancel();
48 | }
49 | },
50 | onCameraClick: function () {
51 | if (this.props.onStartMedia) this.props.onStartMedia();
52 | this.openMedia('camera');
53 | },
54 | onGalleryClick: function () {
55 | if (this.props.onStartMedia) this.props.onStartMedia();
56 | this.openMedia('gallery');
57 | },
58 | render: function () {
59 | return (
60 |
61 |
62 |
66 |
70 |
71 |
);
72 | }
73 | });
74 |
75 | module.exports = GetMedia;
76 |
--------------------------------------------------------------------------------
/src/components/link/link.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var assign = require('react/lib/Object.assign');
3 |
4 | var Link = React.createClass({
5 | getDefaultProps: function () {
6 | return {
7 | tagName: 'a'
8 | };
9 | },
10 | render: function () {
11 | var className = this.props.className ? (this.props.className + ' link') : 'link';
12 | var props = assign({}, this.props, {
13 | className,
14 | onClick: (e) => {
15 | if (window.Platform) {
16 | e.preventDefault();
17 |
18 | // if there is a pre-navigation handling hook, call that before continuing
19 | if (this.props.preNavigation && typeof this.props.preNavigation === 'function') {
20 | this.props.preNavigation();
21 | }
22 |
23 | if (this.props.external) {
24 | window.Platform.openExternalUrl(this.props.external);
25 | } else if (this.props.url) {
26 | window.Platform.setView(this.props.url);
27 | }
28 | }
29 | }
30 | });
31 | if (this.props.external) {
32 | props.target = '_blank';
33 | props.href = this.props.href || this.props.external;
34 | }
35 | return React.createElement(this.props.tagName, props);
36 | }
37 | });
38 |
39 | module.exports = Link;
40 |
--------------------------------------------------------------------------------
/src/components/link/link.less:
--------------------------------------------------------------------------------
1 | .link {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/loading/loading.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var classNames = require('classnames');
3 |
4 | var Loading = React.createClass({
5 | mixins: [require('react-intl').IntlMixin],
6 | getDefaultProps: function () {
7 | return {
8 | on: false
9 | };
10 | },
11 | render: function () {
12 | return (
13 |
14 |
15 |
16 |
17 |
{this.getIntlMessage('loading')}
18 |
19 |
);
20 | }
21 | });
22 |
23 | module.exports = Loading;
24 |
--------------------------------------------------------------------------------
/src/components/loading/loading.less:
--------------------------------------------------------------------------------
1 | .loading {
2 | position: fixed;
3 | z-index: @ZINDEX_LOADING;
4 | top: 0;
5 | left: 0;
6 | width: 100%;
7 | height: 100%;
8 | background: transparent;
9 | opacity: 0;
10 | visibility: hidden;
11 | transition: opacity 0.2s ease;
12 | &.on {
13 | opacity: 1;
14 | visibility: visible;
15 | }
16 | }
17 |
18 | .loading-bar {
19 | position: relative;
20 | width: 100%;
21 | height: 6px;
22 | background-color: lighten(@blue, 20%);
23 | border-bottom: 1px solid fade(@white, 10%);
24 | }
25 | .bar {
26 | content: "";
27 | display: inline;
28 | position: absolute;
29 | width: 0;
30 | height: 100%;
31 | left: 50%;
32 | text-align: center;
33 | }
34 | .bar:nth-child(1) {
35 | background-color: lighten(@blue, 10%);
36 | animation: loading 3s linear infinite;
37 | }
38 | .bar:nth-child(2) {
39 | background-color: lighten(@blue, 5%);
40 | animation: loading 3s linear 1s infinite;
41 | }
42 | .bar:nth-child(3) {
43 | background-color: @blue;
44 | animation: loading 3s linear 2s infinite;
45 | }
46 |
47 | @keyframes loading {
48 | from {
49 | left: 50%;
50 | width: 0;
51 | z-index: 100;
52 | }
53 | 33.3333% {
54 | left: 0;
55 | width: 100%;
56 | z-index: 10;
57 | }
58 | to {
59 | left: 0;
60 | width: 100%;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/components/modal-confirm/modal-confirm.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var Shim = require('../shim/shim.jsx');
3 | var dispatcher = require('../../lib/dispatcher');
4 |
5 | var ModalConfirm = React.createClass({
6 | getInitialState: function () {
7 | return {
8 | header: '',
9 | body: '',
10 | attribution: undefined,
11 | icon: '',
12 | buttons: [],
13 | callback: null
14 | };
15 | },
16 | show: function () {
17 | this.refs.shim.show();
18 | },
19 | hide: function () {
20 | this.refs.shim.hide();
21 | },
22 | onConfirmClick: function (button) {
23 | this.hide();
24 |
25 | if (button.callback) {
26 | button.callback.call();
27 | }
28 | },
29 | componentDidMount: function () {
30 | dispatcher.on('modal-confirm:show', (event) => {
31 | if (event.config.icon) {
32 | event.config.icon = '../../img/' + event.config.icon;
33 | }
34 |
35 | this.setState(React.__spread(this.getInitialState(), event.config));
36 | this.show();
37 | });
38 |
39 | dispatcher.on('modal-confirm:hide', (event) => {
40 | this.hide();
41 | });
42 | },
43 | render: function () {
44 | var buttons = this.state.buttons.map((button, i) => {
45 | return (
46 |
);
52 | });
53 |
54 | return (
55 |
56 |
57 |
58 | {this.state.header}
59 |
60 |

61 |
62 |
63 |
64 |
{this.state.body}
65 | {buttons}
66 |
67 |

68 |
{this.state.attribution}
69 |
70 |
71 |
72 |
73 | );
74 | }
75 | });
76 |
77 | module.exports = ModalConfirm;
78 |
--------------------------------------------------------------------------------
/src/components/modal-confirm/modal-confirm.less:
--------------------------------------------------------------------------------
1 | .modal-confirm {
2 | .window {
3 | box-shadow: 0px 0px 30px rgba(0,0,0,0.3);
4 | border-radius: 5px;
5 |
6 | header {
7 | padding: 5px 20px 0 20px;
8 | background: @shadowPlum;
9 | border-top-left-radius: 5px;
10 | border-top-right-radius: 5px;
11 | display: flex;
12 | align-items: center;
13 |
14 | div {
15 | font-size: 18px;
16 | font-weight: 400;
17 | color: @white;
18 | padding: 5px 0;
19 | }
20 |
21 | .text {
22 | flex: 1;
23 | }
24 |
25 | .icon {
26 | img {
27 | width: 40px;
28 | }
29 | }
30 | }
31 |
32 | .content {
33 | background: @white;
34 | padding: 20px;
35 | border-bottom-right-radius: 5px;
36 | border-bottom-left-radius: 5px;
37 |
38 | p {
39 | font-size: 16px;
40 | margin-top: 0;
41 | color: #333;
42 | }
43 |
44 | button {
45 | background: @lightGrey;
46 | }
47 |
48 | .attribution {
49 | img {
50 | width: 18px;
51 | margin-right: 5px;
52 | opacity: 0.3;
53 | position: relative;
54 | top: 3px;
55 | }
56 | color: @heatherGrey;
57 | text-align: center;
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/components/modal-switch/modal-switch.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var Shim = require('../shim/shim.jsx');
3 | var dispatcher = require('../../lib/dispatcher');
4 |
5 | var ModalConfirm = React.createClass({
6 | getInitialState: function () {
7 | return {
8 | actions: [],
9 | callback: null
10 | };
11 | },
12 | show: function () {
13 | this.refs.shim.show();
14 | },
15 | hide: function () {
16 | this.refs.shim.hide();
17 | },
18 | onButtonClick: function (event) {
19 | if (this.state.callback) {
20 | this.state.callback.call(this, {
21 | label: event
22 | });
23 | }
24 |
25 | this.hide();
26 | },
27 | componentDidMount: function () {
28 | dispatcher.on('modal-switch:show', (event) => {
29 | this.setState(React.__spread(this.getInitialState(), event.config));
30 | this.show();
31 | });
32 |
33 | dispatcher.on('modal-switch:hide', (event) => {
34 | this.hide();
35 | });
36 | },
37 | render: function () {
38 | var buttons = this.state.actions.map((action, index) => {
39 | return (
40 |
41 | );
42 | });
43 |
44 | return (
45 |
46 |
47 | {buttons}
48 |
49 |
50 | );
51 | }
52 | });
53 |
54 | module.exports = ModalConfirm;
55 |
--------------------------------------------------------------------------------
/src/components/modal-switch/modal-switch.less:
--------------------------------------------------------------------------------
1 | .modal-switch {
2 | .window {
3 | box-shadow: 0px 0px 30px rgba(0,0,0,0.3);
4 | border-radius: 5px;
5 | background: @white;
6 | border-radius: 5px;
7 | width: 100%;
8 |
9 | button {
10 | display: block;
11 | background: none;
12 | width: 100%;
13 | border: 0;
14 | border-bottom: 1px solid @heatherGrey;
15 | text-align: left;
16 | line-height: 22px;
17 | padding: 20px;
18 | color: darken(@slate, 20%);
19 |
20 | &:last-child {
21 | border-bottom: 0;
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/option-panel/option-panel.less:
--------------------------------------------------------------------------------
1 | .option-panel {
2 | display: flex;
3 | .label {
4 | margin: 0;
5 | position: relative;
6 | display: flex;
7 | align-items: center;
8 | justify-content: center;
9 | flex: 1;
10 | background: #FFF;
11 | height: 44px;
12 | box-shadow: 0 2px 0 0 #CDCFD3;
13 | border-left: 1px solid fade(#CDCFD3, 50%);
14 | &.selected {
15 | background: #69A0FC;
16 | box-shadow: 0 2px 0 0 #4881DF;
17 | border-left: 1px solid fade(#4881DF, 50%);
18 | }
19 | &:first-child {
20 | border-radius: 3px 0 0 3px;
21 | }
22 | &:last-child {
23 | border-radius: 0 3px 3px 0;
24 | }
25 | .icon {
26 | height: 15px;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/project-list/project-list.less:
--------------------------------------------------------------------------------
1 | .projectList {
2 | display: block;
3 | position: relative;
4 | width: 100%;
5 | height: 100%;
6 | margin: 20px auto 0;
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/range/range.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var LinkedStateMixin = require('react-addons-linked-state-mixin');
3 |
4 | module.exports = React.createClass({
5 | mixins: [LinkedStateMixin],
6 | getDefaultProps: function () {
7 | return {
8 | id: 'value',
9 | max: 100,
10 | min: 0,
11 | step: 1,
12 | unit: '',
13 | percentage: false
14 | };
15 | },
16 | getInitialState: function () {
17 | return {
18 | value: typeof this.props.value !== 'undefined' ? this.props.value : 100
19 | };
20 | },
21 | onChange: function (e) {
22 | var value = parseFloat(e.target.value);
23 | this.valueLink.requestChange(value);
24 | if (this.props.onChange) {
25 | this.props.onChange(value);
26 | }
27 | },
28 | getDisplayValue: function(){
29 | if (this.props.percentage) {
30 | return `${Math.round(this.props.value/this.props.max * 100)}%`;
31 | } else {
32 | return `${this.props.value}${this.props.unit}`;
33 | }
34 | },
35 | render: function () {
36 | var linkState = this.props.linkState || this.linkState;
37 | var valueLink = this.valueLink = linkState(this.props.id);
38 |
39 | var currentValue = parseFloat(valueLink.value);
40 |
41 | return (
42 |
43 |
44 |
{this.getDisplayValue()}
45 |
46 | );
47 | }
48 | });
49 |
--------------------------------------------------------------------------------
/src/components/range/range.less:
--------------------------------------------------------------------------------
1 | /*********************************************************
2 | * Range
3 | */
4 | @range_height: 2px;
5 | @range_vertical-margin: 20px;
6 | @range_total-height: @range_height + @range_vertical-margin*2;
7 | .range {
8 | display: flex;
9 | align-items: center;
10 | input[type=range] {
11 | -webkit-appearance: none;
12 | padding: 0;
13 | height: @range_height;
14 | background: @heatherGrey;
15 | margin: @range_vertical-margin 0;
16 | &::before {
17 | content: '';
18 | }
19 | }
20 |
21 | input[type=range]::-webkit-slider-thumb {
22 | -webkit-appearance: none;
23 | border: none;
24 | height: 30px;
25 | width: 30px;
26 | border-radius: 36px;
27 | background: #FFF;
28 | cursor: pointer;
29 | // Generally, it's a good idea to avoid transparency
30 | // in moving parts where possible to improve animation performance
31 | box-shadow: 0px 1px 0 1px darken(@lightGrey, 4%);
32 | }
33 |
34 | .range-summary {
35 | width: 3.5em;
36 | text-align: right;
37 | &.min {
38 | opacity: 0.3;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/select/select.less:
--------------------------------------------------------------------------------
1 | .select {
2 | -webkit-appearance: none;
3 | -moz-appearance: none;
4 | appearance: none;
5 | &:extend(.btn);
6 | &:active {
7 | &:extend(.btn:active);
8 | }
9 | width: 100%;
10 | background: center right 12px #FFF url(./img/caret-down.svg) no-repeat;
11 | background-size: 11px 7px;
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/shim/shim.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | var Shim = React.createClass({
4 | getInitialState: function () {
5 | return {
6 | isActive: false
7 | };
8 | },
9 | show: function () {
10 | this.setState({
11 | isActive: true
12 | });
13 | },
14 | hide: function () {
15 | this.setState({
16 | isActive: false
17 | });
18 | },
19 | onShimClick: function () {
20 | if (this.props.onClick) {
21 | this.props.onClick.call();
22 | }
23 | },
24 | render: function () {
25 | return (
26 |
27 | {this.props.children}
28 |
29 | );
30 | }
31 | });
32 |
33 | module.exports = Shim;
34 |
--------------------------------------------------------------------------------
/src/components/shim/shim.less:
--------------------------------------------------------------------------------
1 | .shim {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100%;
7 | background: @overlay;
8 | z-index: @ZINDEX_MAX;
9 | display: flex;
10 | padding: 30px;
11 | justify-content: center;
12 | align-items: center;
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/snackbar/snackbar.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var dispatcher = require('../../lib/dispatcher');
3 |
4 | var Snackbar = React.createClass({
5 | getInitialState: function() {
6 | return {
7 | message: this.props.message || "",
8 | hidden: true
9 | };
10 | },
11 |
12 | componentDidMount: function () {
13 | dispatcher.on('reportError', (event) => {
14 | this.setState({
15 | message: event.message,
16 | additionals: event.additionals,
17 | hidden: false
18 | },function() {
19 | // TODO: it may be possible to rewrite this so that it
20 | // hooks into a CSS transition end, although the
21 | // transition would be a little wonky
22 | setTimeout(_ => this.hide(), 3000);
23 | });
24 | });
25 | },
26 |
27 | render: function () {
28 | var className = "snackbar";
29 | if (this.state.hidden) {
30 | className = "hidden " + className;
31 | }
32 | return (
{ this.state.message }
);
33 | },
34 |
35 | /**
36 | * Hide the snackbar from view
37 | */
38 | hide: function() {
39 | this.setState({
40 | hidden: true
41 | });
42 | }
43 | });
44 |
45 | module.exports = Snackbar;
46 |
--------------------------------------------------------------------------------
/src/components/snackbar/snackbar.less:
--------------------------------------------------------------------------------
1 | .container div.snackbar {
2 | @barheight: 5em;
3 | width: 100%;
4 | position: fixed;
5 | z-index: 2147483647;
6 | bottom: 0;
7 | left: 0;
8 | right: 0;
9 | background: #444;
10 | line-height: @barheight;
11 | text-align: center;
12 | color: #EEE;
13 | height: @barheight;
14 | min-height: 0;
15 | transition: height 0.25s linear;
16 |
17 | &.hidden {
18 | height: 0;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/spindicator/spindicator.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var dispatcher = require('../../lib/dispatcher');
3 |
4 | var Spindicator = React.createClass({
5 | getInitialState: function () {
6 | return {
7 | isVisible: false
8 | };
9 | },
10 | show: function () {
11 | this.setState({
12 | isVisible: true
13 | });
14 | },
15 | hide: function () {
16 | this.setState({
17 | isVisible: false
18 | });
19 | },
20 | componentDidMount: function () {
21 | dispatcher.on('apiLagging', (event) => {
22 | this.show();
23 | });
24 |
25 | dispatcher.on('apiCallFinished', (event) => {
26 | this.hide();
27 | });
28 | },
29 | render: function () {
30 | return (
31 |
34 | );
35 | }
36 | });
37 |
38 | module.exports = Spindicator;
39 |
--------------------------------------------------------------------------------
/src/components/spindicator/spindicator.less:
--------------------------------------------------------------------------------
1 | #spindicator {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100%;
7 | background: rgba(24, 22, 30, 0.75);
8 | transition: all 0.3s cubic-bezier(0.79, 0.14, 0.15, 0.86) 0s;
9 | z-index: @ZINDEX_MAX;
10 |
11 | .loader {
12 | position: relative;
13 | top: 43%;
14 | left: 41%;
15 | display: inline-block;
16 | width: 80px;
17 | height: 80px;
18 | border: 5px solid white;
19 | border-radius: 50%;
20 | animation: spin 0.5s infinite linear;
21 | }
22 |
23 | .loader::before,
24 | .loader::after {
25 | left: -5px;
26 | top: -5px;
27 | display: none;
28 | position: absolute;
29 | content: '';
30 | width: inherit;
31 | height: inherit;
32 | border: inherit;
33 | border-radius: inherit;
34 | }
35 |
36 |
37 | .spinner {
38 | border-color: transparent;
39 | border-top-color: rgba(0,0,0,0.3);
40 | animation-duration: 3s;
41 | }
42 |
43 | .spinner::after {
44 | display: block;
45 | border-color: transparent;
46 | border-top-color: #E9EBF0;
47 | animation: spin 1.5s infinite ease-in-out;
48 | }
49 |
50 | @keyframes spin {
51 | 0% {
52 | transform: rotate(0deg);
53 | }
54 | 100% {
55 | transform: rotate(360deg);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/components/tabs/tabs.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var classNames = require('classnames');
3 |
4 | var Tabs = React.createClass({
5 | getInitialState: function () {
6 | return {
7 | selectedIndex: 0
8 | };
9 | },
10 | setTab: function (i) {
11 | return () => {
12 | this.setState({selectedIndex: i});
13 | };
14 | },
15 | render: function () {
16 | var className = classNames('tabs', this.props.className);
17 | var tabs = this.props.tabs;
18 | return (
19 |
20 | {tabs.map((tab, i) => {
21 | var className = classNames({
22 | 'tab-btn': true,
23 | 'selected': this.state.selectedIndex === i
24 | });
25 | return ();
26 | })}
27 |
28 |
29 | {tabs.map((tab, i) => {
30 | var className = classNames({
31 | 'tab-body': true,
32 | 'selected': this.state.selectedIndex === i
33 | });
34 | return (
{tab.body}
);
35 | })}
36 |
37 |
);
38 | }
39 | });
40 |
41 | module.exports = Tabs;
42 |
--------------------------------------------------------------------------------
/src/components/tabs/tabs.less:
--------------------------------------------------------------------------------
1 | /*********************************************************
2 | * Tabs
3 | */
4 | @tabs_iconHeight: 24px;
5 | @tabs_tab-btn-padding: 10px;
6 | @tabs_tab-menu-bottom-margin: 20px;
7 | @tabs_total-height: @tabs_iconHeight + @tabs_tab-btn-padding * 2 + @tabs_tab-menu-bottom-margin;
8 | .tabs {
9 | .tab-menu {
10 | margin: 0 0 @tabs_tab-menu-bottom-margin -20px;
11 | padding: 0;
12 | list-style: none;
13 | display: flex;
14 | background: #31344f;
15 | position: fixed;
16 | width: 100%;
17 | li {
18 | margin: 0;
19 | padding: 0;
20 | flex: 1;
21 | text-align: center;
22 | box-shadow: inset -2px 0 0 @plum;
23 | &:last-child {
24 | box-shadow: none;
25 | }
26 | }
27 | .tab-btn {
28 | background: none;
29 | border: none;
30 | display: flex;
31 | align-items: center;
32 | justify-content: center;
33 | padding: @tabs_tab-btn-padding;
34 | width: 100%;
35 | box-shadow: 0 2px 0 #404467;
36 | &.selected {
37 | box-shadow: 0 2px 0 @white;
38 | .icon {
39 | opacity: 1;
40 | }
41 | }
42 | .icon {
43 | opacity: 0.3;
44 | display: block;
45 | height: @tabs_iconHeight;
46 | }
47 | }
48 | }
49 | .tab-body {
50 | display: none;
51 | &.selected {
52 | display: block;
53 | }
54 | .editor-scroll{
55 | padding-top: 0;
56 | padding-bottom: 50px;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/text-input/text-input.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var LinkedStateMixin = require('react-addons-linked-state-mixin');
3 |
4 | var TextInput = React.createClass({
5 | mixins: [LinkedStateMixin],
6 | propTypes: {
7 | maxlength: React.PropTypes.number.isRequired,
8 | minlength: React.PropTypes.number,
9 | onEnterPressed: React.PropTypes.func,
10 | placeholder: React.PropTypes.string,
11 | type: React.PropTypes.string
12 | },
13 | getInitialState: function () {
14 | return {
15 | text: '',
16 | isTooLong: false
17 | };
18 | },
19 | onChange: function (e) {
20 | this.valueLink.requestChange(e.target.innerHTML);
21 |
22 | this.setState({
23 | inputLength: e.target.innerText.length,
24 | isTooLong: e.target.innerText.length > this.props.maxlength ? true : false
25 | });
26 | },
27 | validate: function () {
28 | if ((!this.props.minlength || this.valueLink.value.length > this.props.minlength) &&
29 | this.valueLink.value.length <= this.props.maxlength) {
30 | return true;
31 | } else {
32 | return false;
33 | }
34 | },
35 | onKeyDown: function (event) {
36 | // Fire callback (if defined) if enter is pressed
37 | if (event.keyCode === 13) {
38 | // Prevent newlines
39 | event.preventDefault();
40 |
41 | if (this.props.onEnterPressed) {
42 | this.props.onEnterPressed.call(this, {
43 | value: event.target.value
44 | });
45 | }
46 | }
47 | },
48 | blur: function () {
49 | this.refs.input.blur();
50 | },
51 | render: function () {
52 | var linkState = this.props.linkState || this.linkState;
53 | this.valueLink = linkState(this.props.id);
54 |
55 | return (
56 |
57 |
58 |
59 |
66 |
67 |
{this.valueLink.value.length} / {this.props.maxlength}
68 |
69 | );
70 | }
71 | });
72 |
73 | module.exports = TextInput;
74 |
--------------------------------------------------------------------------------
/src/components/text-input/text-input.less:
--------------------------------------------------------------------------------
1 | .text-input {
2 | label {
3 | font-size: 16px;
4 | color: @slate;
5 | margin-bottom: 1/3em;
6 | display: block;
7 | }
8 |
9 | .input {
10 | display: block;
11 | border: 0;
12 | border-bottom: 2px solid @heatherGrey;
13 | font-size: 22px;
14 | width: 100%;
15 | background: none;
16 | padding: 0.2em 0;
17 | -webkit-text-fill-color: @black;
18 | color: @blue; // Caret color
19 | margin-bottom: 1/3em;
20 |
21 | &:focus {
22 | outline: 0;
23 | border-bottom: 2px solid @blue;
24 | }
25 |
26 | &::-webkit-input-placeholder {
27 | -webkit-text-fill-color: darken(@heatherGrey, 10%);
28 | }
29 | }
30 |
31 | .indicator {
32 | text-align: right;
33 | font-size: 14px;
34 | color: @slate;
35 | }
36 |
37 | &.maxed {
38 | .indicator {
39 | color: @brick;
40 | }
41 |
42 | input {
43 | border-bottom: 2px solid @brick;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/fonts.less:
--------------------------------------------------------------------------------
1 | @fonts-path: './fonts';
2 |
3 | @font-face {
4 | font-family: 'Fira Sans';
5 | src: local('Fira Sans Regular'),
6 | url('@{fonts-path}/FiraSans-Regular.woff') format('woff');
7 | font-weight: 400;
8 | font-style: normal;
9 | }
10 |
11 | @font-face {
12 | font-family: 'Fira Sans';
13 | src: local('Fira Sans Medium'),
14 | url('@{fonts-path}/FiraSans-Medium.woff') format('woff');
15 | font-weight: 500;
16 | font-style: normal;
17 | }
18 |
19 | @font-face {
20 | font-family: 'Sharpie';
21 | src: local('Sharpie'),
22 | url('@{fonts-path}/lamson-sharpie-webfont.woff') format('woff');
23 | font-weight: 400;
24 | font-style: normal;
25 | }
26 |
--------------------------------------------------------------------------------
/src/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Webmaker
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/lib/color.js:
--------------------------------------------------------------------------------
1 | var Color = require('color');
2 | var minimumContrast = 2;
3 |
4 | module.exports = {
5 | getContrastingColor: (function() {
6 | var white = Color().rgb(255, 255, 255),
7 | whiteCSS = white.rgbString(),
8 | black = Color().rgb(0, 0, 0),
9 | blackCSS = black.rgbString();
10 |
11 | return function(input) {
12 | var c = Color(input),
13 | wc = white.contrast(c),
14 | bc = black.contrast(c);
15 |
16 | return (wc < bc && wc >= minimumContrast) ? whiteCSS : blackCSS;
17 | };
18 | }()),
19 | darken: function(baseColor, shade){
20 | var color = Color(baseColor);
21 | color.darken(shade);
22 |
23 | return color.rgbString();
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/src/lib/dispatcher.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | fire: function (eventName, eventData) {
3 | var customEvent = new CustomEvent(eventName, {
4 | detail: eventData
5 | });
6 |
7 | window.dispatchEvent(customEvent);
8 | },
9 | on: function (eventName, callback) {
10 | window.addEventListener(eventName, function(e) {
11 | callback.call(this, e.detail);
12 | });
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/src/lib/errors.js:
--------------------------------------------------------------------------------
1 | var dispatcher = require('./dispatcher');
2 | var debug = false;
3 |
4 | var ErrorReporter = function() {
5 | var array = Array.prototype.slice.call(arguments);
6 | if (debug) {
7 | console.log.apply(console, arguments);
8 | }
9 | dispatcher.fire('reportError', {
10 | message: array[0],
11 | additionals: array.slice(1)
12 | });
13 | };
14 |
15 | module.exports = ErrorReporter;
16 |
--------------------------------------------------------------------------------
/src/lib/i18n.js:
--------------------------------------------------------------------------------
1 | var assign = require('react/lib/Object.assign');
2 | var messages = require('./messages');
3 | var locale = navigator.language.split('-');
4 | locale = locale[1] ? `${locale[0]}-${locale[1].toUpperCase()}` : navigator.language;
5 |
6 | var strings = messages[locale] ? messages[locale] : messages['en-US'];
7 |
8 | module.exports = {
9 | intlData: {
10 | locales : ['en-US'],
11 | // Sometimes we will include a language with partial translation
12 | // and we need to make sure the object that we pass to `intlData`
13 | // contains all keys based on the `en-US` messages.
14 | messages: assign(messages['en-US'], strings)
15 | },
16 | defaultLang: 'en-US',
17 | currentLanguage: locale,
18 | isSupportedLanguage: function(lang) {
19 | return !!messages[lang];
20 | },
21 | intlDataFor: function(lang) {
22 | // we need to make sure we transform the given locale to the right format first
23 | // so we can access the right locale in our dictionary for example: pt-br should be transformed to pt-BR
24 | var locale = lang.split('-');
25 | locale = locale[1] ? `${locale[0]}-${locale[1].toUpperCase()}` : lang;
26 | var strings = messages[locale] ? messages[locale] : messages['en-US'];
27 |
28 | return {locales: [locale], messages: assign(messages['en-US'], strings)};
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/src/lib/jsonUtils.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | jsonToFormEncoded: function (json) {
3 | if (!json) {
4 | return;
5 | }
6 | return Object.keys(json).map(key => {
7 | return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]);
8 | }).join('&');
9 | },
10 | parseJSON: function (string) {
11 | var result = {};
12 | if (string && typeof string === 'object') {
13 | return string;
14 | }
15 | if (string && typeof string === 'string') {
16 | try { result = JSON.parse(string); } catch (e) { console.error(e); }
17 | }
18 | return result;
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/src/lib/keyboard.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | focusNextInputByTabIndex: function () {
3 | var current = document.activeElement;
4 | var elements = document.getElementsByTagName('input');
5 | var idx = parseInt(current.getAttribute('tabIndex'), 10) + 1;
6 |
7 | for (var i = 0; i < elements.length; i++) {
8 | var t = parseInt(elements[i].getAttribute('tabIndex'), 10);
9 | if (t === idx) {
10 | elements[i].focus();
11 | }
12 | }
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/src/lib/messages.js:
--------------------------------------------------------------------------------
1 | // This is essentially bulk require
2 | var req = require.context('../locales', true, /\.json.*$/);
3 | var exports = {};
4 |
5 | req.keys().forEach(function (file) {
6 | var locale = file.replace('./', '').replace('.json', '');
7 | exports[locale] = req(file);
8 | });
9 |
10 | module.exports = exports;
11 |
--------------------------------------------------------------------------------
/src/lib/render.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var ReactDOM = require('react-dom');
3 |
4 | var Spindicator = require('../components/spindicator/spindicator.jsx');
5 | var ModalConfirm = require('../components/modal-confirm/modal-confirm.jsx');
6 | var ModalSwitch = require('../components/modal-switch/modal-switch.jsx');
7 | var Snackbar = require('../components/snackbar/snackbar.jsx');
8 | var FTU = require('../components/ftu/ftu.jsx');
9 |
10 | var platform = require('./platform');
11 | var intlData = require('./i18n').intlData;
12 | var dispatcher = require('./dispatcher');
13 |
14 | var Base = React.createClass({
15 | mixins: [
16 | require('./router')
17 | ],
18 | componentDidMount: function () {
19 | var showFTU = (message) => {
20 | this.setState({
21 | ftuSingleMessage: message,
22 | ftuRightMessage: null,
23 | ftuLeftMessage: null
24 | });
25 |
26 | this.refs.ftu.show();
27 | };
28 |
29 | dispatcher.on('secondPageCreated', () => {
30 | showFTU(intlData.messages['ftu-share-project']);
31 | });
32 |
33 | if (this.props.route.displayName === 'Project' && this.state.params.mode === 'play') {
34 | var projectsViewed = parseInt(platform.getSharedPreferences('ftu::projects-viewed', true) || 0, 10);
35 |
36 | projectsViewed++;
37 |
38 | // Trigger FTU
39 | if (projectsViewed === 2) {
40 | showFTU(intlData.messages['ftu-remix']);
41 | }
42 |
43 | platform.setSharedPreferences('ftu::projects-viewed', projectsViewed, true);
44 | }
45 |
46 | if( this.props.route.displayName === 'Discover' && !platform.getSharedPreferences('ftu::seen-primary-ftu', true) ) {
47 | this.refs.ftu.show();
48 | }
49 | },
50 | onResume: function () {
51 | this.setState({isVisible: true});
52 | },
53 | onPause: function () {
54 | this.setState({isVisible: false});
55 | },
56 | onBackPressed: function() {
57 | if (this.state.onBackPressed) {
58 | return this.state.onBackPressed();
59 | } else {
60 | platform.goBack();
61 | }
62 | },
63 | jsComm: function (event) {
64 | if (this[event]) {
65 | this[event]();
66 | }
67 | },
68 | getInitialState: function () {
69 | // Expose to android
70 | window.jsComm = this.jsComm;
71 | return {
72 | isVisible: true,
73 | onBackPressed: false,
74 | ftuSingleMessage: null,
75 | ftuRightMessage: intlData.messages['ftu-make-your-own'],
76 | ftuLeftMessage: intlData.messages['ftu-explore']
77 | };
78 | },
79 | onFTUViewed: function () {
80 | platform.setSharedPreferences('ftu::seen-primary-ftu', 'true', true);
81 | },
82 | update: function (state) {
83 | this.setState(state);
84 | },
85 | render: function () {
86 | var Route = this.props.route;
87 | return (
88 |
94 |
95 |
96 |
97 |
98 |
99 |
);
100 | }
101 | });
102 |
103 | module.exports = function (Route) {
104 | ReactDOM.render(
, document.getElementById('app'));
105 | };
106 |
--------------------------------------------------------------------------------
/src/lib/router.js:
--------------------------------------------------------------------------------
1 | var {parseJSON} = require('./jsonUtils');
2 |
3 | module.exports = {
4 |
5 | android: window.Platform,
6 |
7 | getRouteParams: function () {
8 | var params = {};
9 |
10 | if (this.android) {
11 | // Check to see if route params exist & create cache key
12 | var routeParams = this.android.getRouteParams();
13 | var key = 'route::params';
14 |
15 | // If they do, save them. If not, fetch from storage
16 | if (routeParams !== '{}') {
17 | params = parseJSON(routeParams);
18 | this.android.setMemStorage(key, routeParams);
19 | } else {
20 | var hit = this.android.getMemStorage(key);
21 | params = parseJSON(hit);
22 | }
23 | } else {
24 | params = {
25 | mode: 'play', // 'edit', 'play', 'link'
26 | user: 1,
27 | project: 1,
28 | page: 1,
29 | element: 1,
30 | tag: 'test',
31 | propertyName: 'borderColor'
32 | };
33 | }
34 |
35 | return params;
36 | },
37 |
38 | getRouteData: function () {
39 | var data = {};
40 |
41 | if (this.android) {
42 | data = parseJSON(this.android.getRouteData());
43 | this.android.clearRouteData();
44 | }
45 |
46 | return data;
47 | },
48 |
49 | // #getUserSession ()
50 | //
51 | // Returns user session from android if it exists,
52 | // or else an empty object.
53 | // if window.Platform doesn't exist, returns test data
54 | // e.g.
55 | // {
56 | // user: {id: 1, username: 'foo', ...},
57 | // token: 'validToken'
58 | // }
59 | getUserSession: function () {
60 | var session = {};
61 |
62 | if (this.android) {
63 | session = parseJSON(this.android.getUserSession());
64 | } else {
65 | // For testing
66 | session = {
67 | user: {
68 | id: 1,
69 | username: 'jonatmozilla',
70 | avatar: 'https://secure.gravatar.com/avatar/c1b6f037ee7e440940d3c0c6ebeb8ba2?d=https%3A%2F%2Fstuff.webmaker.org%2Favatars%2Fwebmaker-avatar-200x200.png',
71 | prefLocale: 'en-US'
72 | },
73 | token: 'validToken'
74 | };
75 | }
76 |
77 | return session;
78 | },
79 |
80 | getInitialState: function () {
81 | var session = this.getUserSession();
82 | return {
83 | params: this.getRouteParams(),
84 | routeData: this.getRouteData(),
85 | user: session.user || {},
86 | token: session.token || ''
87 | };
88 | }
89 | };
90 |
--------------------------------------------------------------------------------
/src/lib/swipe.js:
--------------------------------------------------------------------------------
1 | module.exports = function calculateSwipe(startX, startY, endX, endY) {
2 | var THRESHOLD = 100;
3 | var MINIMUM_DISTANCE = 50;
4 |
5 | var distance = Math.sqrt(Math.pow(startX - endX, 2) + Math.pow(startY - endY, 2));
6 |
7 | if (distance >= MINIMUM_DISTANCE) {
8 | if (startX > endX && Math.abs(startY - endY) < THRESHOLD) {
9 | return('LEFT');
10 | } else if (startX < endX && Math.abs(startY - endY) < THRESHOLD) {
11 | return('RIGHT');
12 | } else if (startY < endY && Math.abs(startX - endX) < THRESHOLD) {
13 | return('DOWN');
14 | } else if (startY > endY && Math.abs(startX - endX) < THRESHOLD) {
15 | return('UP');
16 | }
17 | } else {
18 | return false;
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/src/lib/uuid.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Mad science micro-sized UUID (v4) module.
3 | *
4 | * Adapted from: https://gist.github.com/jed/982883
5 | *
6 | * @package webmaker
7 | * @author Andrew Sliwinski
8 | */
9 |
10 | /* jshint ignore:start */
11 | // jscs:disable
12 | function b(
13 | a // placeholder
14 | ) {
15 | return a // if the placeholder was passed, return
16 | ? ( // a random number from 0 to 15
17 | a ^ // unless b is 8,
18 | Math.random() // in which case
19 | * 16 // a random number from
20 | >> a / 4 // 8 to 11
21 | ).toString(16) // in hexadecimal
22 | : ( // or otherwise a concatenated string:
23 | [1e7] + // 10000000 +
24 | -1e3 + // -1000 +
25 | -4e3 + // -4000 +
26 | -8e3 + // -80000000 +
27 | -1e11 // -100000000000,
28 | ).replace( // replacing
29 | /[018]/g, // zeroes, ones, and eights with
30 | b // random hex digits
31 | );
32 | }
33 |
34 | module.exports = b;
35 | /* jshint ignore:end */
36 | // jscs:enable
37 |
--------------------------------------------------------------------------------
/src/lib/validators.js:
--------------------------------------------------------------------------------
1 | var owasp = require('owasp-password-strength-test');
2 |
3 | // this removes the repeating character required test
4 | // https://github.com/cadecairos/PassTest/blob/master/index.js
5 | owasp.tests.required.splice(2, 1);
6 |
7 | //https://github.com/mozilla/id.webmaker.org/blob/develop/web/server.js#L13
8 | owasp.config({
9 | minLength: 8,
10 | maxLength: 256,
11 | minPhraseLength: 20,
12 | minOptionalTestsToPass: 2,
13 | allowPassphrases: true
14 | });
15 |
16 | module.exports = {
17 |
18 | validators: {
19 | email: {
20 | regex: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/i,
21 | message: 'Please use a valid email address.'
22 | },
23 | password: {
24 | test: function (input) {
25 | input = input || '';
26 | return owasp.test(input).strong;
27 | },
28 | message: 'Your password must be at least 8 characters and contain at least 1 number and 1 letter'
29 | },
30 | username: {
31 | regex: /^[a-zA-Z0-9\-]{1,20}$/,
32 | message: 'Must be 1-20 characters long and use only "-" and alphanumeric symbols.'
33 | }
34 | },
35 |
36 | // Looks at this.fields, and returns an object of errors
37 | // keyed on field.name
38 | //
39 | // e.g. this.fields should look like:
40 | // [{
41 | // name: 'username',
42 | // label: 'Username',
43 | // required: true,
44 | // validations: 'username'
45 | // },
46 | // {
47 | // name: 'email',
48 | // label: 'Email',
49 | // type: 'email',
50 | // required: true,
51 | // validations: 'email'
52 | // },
53 | // {
54 | // name: 'password',
55 | // label: 'Password',
56 | // type: 'password',
57 | // required: true,
58 | // validations: ['passwordLength', 'lowerCase', 'upperCase', 'numbers']
59 | // }]
60 |
61 | getValidationErrors: function () {
62 | var errors = {};
63 | var fields = this.fields;
64 |
65 | if (!fields) {
66 | return errors;
67 | }
68 |
69 | fields.forEach(fieldData => {
70 |
71 | var field = fieldData.name;
72 | var value = this.state[field];
73 | var isRequired = fieldData.required;
74 | var isEmpty = !value && value !== 0;
75 | var validations = fieldData.validations;
76 |
77 | // Required
78 | if (isRequired && isEmpty) {
79 | errors.requiredFieldsMissing = true;
80 | }
81 |
82 | // Custom Validation
83 | if (validations) {
84 | if (typeof validations === 'string') {
85 | validations = [validations];
86 | }
87 | validations.forEach(type => {
88 | var validation = this.validators[type];
89 | var errorMessage = validation.message;
90 | var test = validation.test || ((input) => validation.regex.test(input));
91 |
92 | // Test the value if it is non-emptynpm tun
93 | if (!isEmpty && !test(value)) {
94 | errors[field] = errors[field] || [];
95 | errors[field].push(errorMessage);
96 | }
97 |
98 | });
99 | }
100 |
101 | });
102 |
103 | return errors;
104 | }
105 | };
106 |
--------------------------------------------------------------------------------
/src/locales/bn-BD.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | bn-BD:
3 | username: ইউজারনেম
4 | password: পাসওয়ার্ড
5 | email: ইমেইল
6 | email_me_updates: ওয়েবমেকার সম্পর্কে খবরাখবর আমাকে ইমেইল করো
7 | join_webmaker: ওয়েবমেকারে যোগ দিন
8 | privacy_policy: প্রাইভেসি পলিসি
9 | signin: সাইন ইন
10 | errorUsernameOrPassword: মনে হচ্ছে আপনার ইউজারনেম বা পাসওয়ার্ডে কোন সমস্যা হয়েছে।
11 | Red: লাল
12 | Green: সবুজ
13 | Blue: নীল
14 | Opacity: সচ্ছতা
15 | log_out: লগ আউট
16 | title: শিরোনাম
17 | done: সম্পন্ন
18 | edit_label: লেবেল সম্পাদনা
19 | change_link_dest: লিঙ্ক গন্তব্যের পরিবর্তন
20 | set_link_dest: লিঙ্ক গন্তব্য নির্ধারণ করুন
21 | corner_radius: কোণের মাপ
22 | Font: ফন্ট
23 | background_color: ব্যাকগ্রাউন্ডের রঙ
24 | error_update_element: উপকরণ হালনাগাদে সমস্যা হয়েছে
25 | error_element: উপকরণ লোডিং এ সমস্যা হয়েছে
26 | error_element_404: কোন উপকরণ পাওয়া যায়নি
27 | error_delete_element: উপকরণ মুছে ফেলতে সমস্যা হয়েছে
28 |
--------------------------------------------------------------------------------
/src/locales/bn-IN.yaml:
--------------------------------------------------------------------------------
1 | bn-IN:
2 | username: ব্যবহারকারীর নাম
3 | password: 'পাসওয়ার্ড '
4 | email: ইমেল
5 | helpText: পাসওয়ার্ড রিসেট
6 | email_me_updates: ওয়েবমেকার সম্পর্কে আপডেট আমায় ইমেইল করুন।
7 | join_webmaker: Webmaker-এ যোগ দিন
8 | by_joining: যুক্ত হয়ে, আমি মোজিলা ওয়েবমেকারের {linkTerms} এবং {linkPrivacyPolicy} এ সম্মতি প্রদান করছি।
9 | privacy_policy: গোপনীয়তা নীতি
10 | terms: শর্তাবলী
11 | already_join_signin: ইতিমধ্যে যোগ দিয়েছেন? {signinLink}
12 | signin: 'সাইন ইন '
13 | dont_have_account: কোন অ্যাকাউন্ট নেই? {joinWebmakerLink}
14 | errorUsernameOrPassword: আপনার ব্যবহারকারী নাম বা পাসওয়ার্ড দিয়ে একটি সমস্যা হতে পারে বলে মনে হচ্ছে।
15 | color_editor: 'রঙ: {rgbaValue};'
16 | Red: লাল
17 | Green: সবুজ
18 | Blue: নীল
19 | Opacity: অস্বচ্ছতা
20 | my_project: আমার প্রকল্প
21 | log_out: প্রস্থান
22 | create_a_project: + প্রকল্প তৈরি করুন
23 | open_style_guide: উন্মুক্ত স্টাইল গাইড
24 | reset_share_preferences: শেয়ার করার পছন্দ রিসেট করুন
25 | loading: লোডিং!
26 | title: শিরোনাম
27 | done: সম্পূর্ণ
28 | edit_label: ট্যাগ সম্পাদনা করুন।
29 | change_link_dest: লিঙ্ক গন্তব্য পরিবর্তন করুন।
30 | set_link_dest: লিঙ্ক গন্তব্য সেট করুন।
31 | corner_radius: দৃষ্টিকোন ব্যাসার্ধ
32 | Font: ফন্ট
33 | background_color: পটভূমির রঙ
34 | no_project_found: দুঃখিত, কোন প্রকল্প পাওয়া যায়নি।
35 | error_featured: প্রকল্প উদ্ঘাটনে সমস্যা হয়েছে
36 | error_featured_404: কোন উদ্ঘাটিত প্রকল্প পাওয়া যায়নি
37 | error_update_element: উপাদান আপডেট করার সময় একটি ত্রুটি ছিল।
38 | error_element: উপাদান লোড করার সময় ত্রুটি।
39 | error_element_404: উপাদান খুঁজে পাওয়া যায়নি।
40 | error_create_make: প্রকল্প তৈরিতে চেষ্টা করা হয়েছে যখন কোন সেশন পাওয়া যায়নি
41 | error_create_element: উপকরণ তৈরিতে সমস্যা হয়েছে
42 | error_delete_element: উপাদান মুছে ফেলার সময় সমস্যা হয়েছে।
43 | error_page: পাতা লোড হতে কোন সমস্যা হয়েছে
44 | error_page_404: পাতা লোড করার জন্য পাওয়া যায়নি
45 | error_404_element_save: উপকরণ সংরক্ষণের জন্য পাওয়া যায়নি
46 | cc: ক্রিয়েটিভ কমন্স
47 | ccLicenseSection: কন্টেন্ট {creativecommonsLink} নামের একটি ক্রিয়েটিভ কমন্স লাইসেন্সের আওতায় প্রকাশিত হয়েছে।
48 | ccLicenseSectionSub: এর অর্থ হচ্ছে অন্য কেউ আপনার কৃতিত্ব স্বীকার করে আপনার কন্টেন্ট শেয়ার, অভিযোজন, এবং রিমিক্স করতে পারবে এবং একইভাবে তাদের সৃষ্টিকর্মও শেয়ার করতে পারবে, যেভাবে এই লাইসেন্সে বর্ণনা করা হয়েছে।
49 | ccAttribution: Attribution-ShareAlike 3.0 Unported
50 |
--------------------------------------------------------------------------------
/src/locales/en-GB.yaml:
--------------------------------------------------------------------------------
1 | en-GB:
2 | username: Username
3 | password: Password
4 | email: Email
5 | helpText: Reset Password
6 | email_me_updates: Email me updates about Webmaker
7 | join_webmaker: Join Webmaker
8 | by_joining: By joining, I agree to Mozilla Webmaker’s {linkTerms} and {linkPrivacyPolicy}
9 | privacy_policy: Privacy Policy
10 | terms: Terms
11 | already_join_signin: Already joined? {signinLink}
12 | signin: Sign in
13 | dont_have_account: Don’t have an account? {joinWebmakerLink}
14 | errorUsernameOrPassword: Looks like there might be a problem with your username or password.
15 | color_editor: 'colour: {rgbaValue};'
16 | Red: Red
17 | Green: Green
18 | Blue: Blue
19 | Opacity: Opacity
20 | my_project: My project
21 | log_out: Log Out
22 | create_a_project: + Create a Project
23 | open_style_guide: Open Style Guide
24 | reset_share_preferences: Reset Shared Preferences
25 | loading: Loading!
26 | title: Title
27 | done: Done
28 | edit_label: Edit Label
29 | change_link_dest: Change Link Destination
30 | set_link_dest: Set Link Destination
31 | corner_radius: Corner Radius
32 | Font: Text
33 | background_color: Background Colour
34 | no_project_found: Sorry, no projects found.
35 | error_featured: Error getting discovery projects
36 | error_featured_404: No discovery projects found
37 | error_update_element: There was an error updating the element
38 | error_element: Error loading element
39 | error_element_404: No element found
40 | error_create_make: Tried to create project when no session was found
41 | error_create_element: There was an error creating an element
42 | error_delete_element: There was a problem deleting the element
43 | error_page: There was an error getting the page to load
44 | error_page_404: Could not find the page to load
45 | error_404_element_save: Could not find the element to save
46 | cc: Creative Commons
47 | ccLicenseSection: Content published under a Creative Commons license called {creativecommonsLink}.
48 | ccLicenseSectionSub: This means that other people can share, adapt, and remix your content if they give you credit and share their work in the same way, as all of this is described in the license.
49 | ccAttribution: Attribution-ShareAlike 3.0 Unported
50 |
--------------------------------------------------------------------------------
/src/locales/en-US.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | en-US:
3 | create_account: Create a Webmaker Account
4 | pick_username: Pick a username
5 | username: Username
6 | username_email: Username or Email
7 | set_password: Set password
8 | password: Password
9 | email: Email
10 | helpText: Reset Password
11 | email_me_updates: Email me updates about Webmaker
12 | join_webmaker: Join Webmaker
13 | by_joining: By joining, I agree to Mozilla Webmaker’s {linkTerms} and {linkPrivacyPolicy}
14 | privacy_policy: Privacy Policy
15 | terms: Terms
16 | already_join_signin: Already joined? {signinLink}
17 | signin: Sign in
18 | signin_webmaker: Sign in to Webmaker
19 | dont_have_account: Don’t have an account? {joinWebmakerLink}
20 | errorUsernameOrPassword: Looks like there might be a problem with your username or password.
21 | color_editor: 'color: {rgbaValue};'
22 | text_color: Text Color
23 | Red: Red
24 | Green: Green
25 | Blue: Blue
26 | Opacity: Opacity
27 | my_project: My project
28 | log_out: Log out
29 | create_a_project: + Create a Project
30 | open_style_guide: Open Style Guide
31 | reset_share_preferences: Reset Shared Preferences
32 | loading: Loading!
33 | title: Title
34 | done: Done
35 | edit_label: Edit Label
36 | change_link_dest: Change Link Destination
37 | set_link_dest: Set Link Destination
38 | corner_radius: Corner Radius
39 | Font: Font
40 | background_color: Background Color
41 | no_project_found: Sorry, no projects found.
42 | error_featured: Error getting discovery projects
43 | error_featured_404: No discovery projects found
44 | error_update_element: There was an error updating the element
45 | error_element: Error loading element
46 | error_element_404: No element found
47 | error_create_make: Tried to create project when no session was found
48 | error_create_element: There was an error creating an element
49 | error_delete_element: There was a problem deleting the element
50 | error_page: There was an error getting the page to load
51 | error_page_404: Could not find the page to load
52 | error_404_element_save: Could not find the element to save
53 | error_featured_tags: Could not fetch featured tags
54 | cc: Creative Commons
55 | ccLicenseSection: Your Webmaker content is published under the Creative Commons {creativecommonsLink} as described in the {termsofuse}.
56 | ccLicenseSectionSub: This means that others may remix, tweak, and build upon your work even for commercial purposes, as long as they credit you and license their new creations under the identical terms.
57 | ccAttribution: Attribution-ShareAlike 3.0 Unported license
58 | ccTermsOfUse: terms of use
59 | error_discovery_get: Error getting discovery projects
60 | badTitle: Title must be between {minLength} and {maxLength} characters
61 | badDescription: Description must be less than {maxLength} characters
62 | ftu-explore: Explore stories around the world
63 | ftu-make-your-own: ...or make your own!
64 | ftu-share-project: Share this project with anyone!
65 | ftu-remix: Make your own version of this – hit 'Remix!'
66 | need_help: Need Help?
67 | link_editor_help: You can link your button to another page or to a destination on the web
68 | link_header: Link Destination
69 | dest_btn_page: A page in this project
70 | dest_btn_web: Web URL
71 | got_it: OK, got it!
72 | web_link_intro: A link can take us to any page or image on the web with an "address" or URL.
73 | web_link_label: URL
74 | invalid_url: Please enter a valid URL.
75 | follow_link: Follow Link
76 | hashtag: Hashtag
77 | featured: Featured
78 |
--------------------------------------------------------------------------------
/src/locales/es-MX.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | es-MX:
3 | username: Usuario
4 | password: Contraseña
5 | email: Correo electrónico
6 | helpText: Reiniciar contraseña
7 | email_me_updates: Enviarme actualizaciones acerca de Webmaker
8 | join_webmaker: Únete a Webmaker
9 | by_joining: Al unirte, aceptas los {linkTerms} y {linkPrivacyPolicy}
10 | privacy_policy: Política de privacidad
11 | terms: Términos
12 | already_join_signin: ¿Ya te uniste? {signinLink}
13 | signin: Entrar
14 | dont_have_account: ¿No tienes una cuenta? {joinWebmakerLink}
15 | errorUsernameOrPassword: Parece que podría haber un probelam con tu usuario o contraseña.
16 | color_editor: 'color: {rgbaValue};'
17 | Red: Rojo
18 | Green: Verde
19 | Blue: Azul
20 | Opacity: Opacidad
21 | my_project: Mi proyecto
22 | log_out: Salir
23 | create_a_project: + Crear un proyecto
24 | open_style_guide: Abrir la guía de estilo
25 | reset_share_preferences: Reiniciar preferencias compartidas
26 | loading: ¡Cargando!
27 | title: Título
28 | done: Hecho
29 | edit_label: Editar etiqueta
30 | change_link_dest: Cambiar destino del link
31 | set_link_dest: Definir destino del link
32 | corner_radius: Radio de las esquinas
33 | Font: Fuente
34 | background_color: Color de fondo
35 | no_project_found: Lo sentimos, no encontramos ningún proyecto.
36 | error_featured: Error descubriendo proyectos
37 | error_featured_404: No se encontraron proyectos en descubre
38 | error_update_element: Hubo un error actualizando el elementp
39 | error_element: Error cargando el elemento
40 | error_element_404: Elemento no encontrado
41 | error_create_make: Intentamos crear un proyecto cuando no encotramos una sesión
42 | error_create_element: Hubo un error creando un elemento
43 | error_delete_element: Hubo un problema eliminando el elemento
44 | error_page: Hubo un error al obtener la página para cargar
45 | error_page_404: No se pudo encontrar la página para leer
46 | error_404_element_save: No se pudo encontrar el elemento para guardar
47 | cc: Creative Commons
48 | ccLicenseSection: Contenido publicado bajo una licencia llamada Creative Commons {creativecommonsLink}.
49 | ccLicenseSectionSub: Esto significa que otra gente puede compartir, adaptar, y mezclar tu
50 | contenido si ellos te dan crédito y comparten su trabajo del mismo modo, todo esto
51 | se describe en la licencia.
52 | ccAttribution: Reconocimiento-CompartirIgual 3.0 Unported
53 | error_discovery_get: Error obteniendo proyectos en descubre
54 |
--------------------------------------------------------------------------------
/src/locales/id-ID.yaml:
--------------------------------------------------------------------------------
1 | id-ID:
2 | username: Nama pengguna
3 | password: Kata kunci
4 | email: Surel
5 | helpText: Tetapkan ulang Kata kunci
6 | email_me_updates: Kirim saya surel info terbaru tentang Webmaker
7 | join_webmaker: Bergabung dengan Webmaker
8 | by_joining: Dengan bergabung, saya menyetujui {linkTerms} dan {linkPrivacyPolicy} dari Mozilla Webmaker
9 | privacy_policy: Kebijakan Privasi
10 | terms: Syarat
11 | already_join_signin: Sudah bergabung? {signinLink}
12 | signin: Masuk
13 | dont_have_account: Tidak punya akun? {joinWebmakerLink}
14 | errorUsernameOrPassword: Sepertinya ada kesalahan pada nama pengguna atau kata sandi Anda.
15 | color_editor: 'warna: {rgbaValue};'
16 | Red: Merah
17 | Green: Hijau
18 | Blue: Biru
19 | Opacity: Tingkat pengaburan
20 | my_project: Proyek saya
21 | log_out: Keluar
22 | create_a_project: + Buat Proyek
23 | open_style_guide: Buka Panduan Gaya
24 | reset_share_preferences: Tetapkan ulang Preferensi Bersama
25 | loading: Memuat!
26 | title: Judul
27 | done: Selesai
28 | edit_label: Atur Label
29 | change_link_dest: Ubah Tujuan Tautan
30 | set_link_dest: Atur Tujuan Tautan
31 | corner_radius: Radius Sudut
32 | Font: Jenis Huruf
33 | background_color: Warna Latar
34 | no_project_found: Maaf, proyek tidak ditemukan.
35 | error_featured: Ada kesalahan dalam mendapatkan proyek temuan
36 | error_featured_404: Tidak ada proyek temuan ditemukan
37 | error_update_element: Ada kesalahan dalam memperbarui elemen
38 | error_element: Kesalahan dalam memuat elemen
39 | error_element_404: Elemen tidak ditemukan
40 | error_create_make: Telah coba membuat proyek ketika tiada sesi ditemukan
41 | error_create_element: Ada kesalahan dalam membuat elemen
42 | error_delete_element: Ada masalah dalam menghapus elemen
43 | error_page: Ada kesalahan dalam memuat halaman
44 | error_page_404: Tidak dapat menemukan halaman untuk dimuat
45 | error_404_element_save: Tidak dapat menemukan elemen untuk disimpan
46 | cc: Creative Commons
47 | ccLicenseSection: Konten diterbitkan dibawah lisensi Creative Commons bernama {creativecommonsLink}.
48 | ccLicenseSectionSub: Ini berarti orang lain dapat membagi, mengadaptasi dan mengubah-suai konten Anda jika mereka memberi kredit kepada Anda dan membagikan pekerjaan mereka dengan cara yang sama, sebagaimana hal tersebut dijelaskan di dalam lisensi.
49 | ccAttribution: Atribusi-BerbagiSerupa 3.0 Tanpa Adaptasi
50 |
--------------------------------------------------------------------------------
/src/locales/pt-BR.yaml:
--------------------------------------------------------------------------------
1 | pt-BR:
2 | username: Nome de usuário
3 | password: Senha
4 | email: E-mail
5 | helpText: Resetar senha
6 | email_me_updates: Me envie atualizações por e-mail sobre o Webmaker
7 | join_webmaker: Faça parte do Webmaker
8 | by_joining: Ao entrar, Eu concordo com os {linkTerms} e {linkPrivacyPolicy} do Mozilla Webmaker
9 | privacy_policy: Política de privacidade
10 | terms: Condições
11 | already_join_signin: Já faz parte? {signinLink}
12 | signin: Entrar
13 | dont_have_account: Não tem uma conta? {joinWebmakerLink}
14 | errorUsernameOrPassword: Parece que existe um problema com o seu nome de usuário ou senha.
15 | color_editor: 'Cor: {rgbaValue};'
16 | Red: Vermelho
17 | Green: Verde
18 | Blue: Azul
19 | Opacity: Opacidade
20 | my_project: Meu projeto
21 | log_out: Sair
22 | create_a_project: +Criar um Projeto
23 | open_style_guide: Guia Aberta de Estilo
24 | reset_share_preferences: Reinicializar preferências compartilhadas
25 | loading: Carregando!
26 | title: Título
27 | done: Concluído
28 | edit_label: Editar rótulo
29 | change_link_dest: Trocar destino do link
30 | set_link_dest: Definir destino do link
31 | corner_radius: Raio do canto
32 | Font: Fonte
33 | background_color: Cor de fundo
34 | no_project_found: Desculpa, não foram encontrados projetos.
35 | error_featured: Erro ao procurar projetos em destaques
36 | error_featured_404: Não há projetos em destaques encontrados
37 | error_update_element: Houve um erro ao atualizar o elemento
38 | error_element: Erro ao carregar o elemento
39 | error_element_404: Nenhum elemento encontrado
40 | error_create_make: Tentou criar um projeto quando estava em um sessão não encontrada
41 | error_create_element: Houve algum erro ao criar um elemento
42 | error_delete_element: Houve um problema ao remover o elemento
43 | error_page: Houve um erro ao carregar a página
44 | error_page_404: Não conseguimos encontrar a página carregada
45 | error_404_element_save: Não conseguimos encontrar o elemento salvo
46 | cc: Creative Commons
47 | ccLicenseSection: Conteúdo publicado sob uma licença chamada Creative Commons {creativecommonsLink}.
48 | ccLicenseSectionSub: Isso significa que outras pessoas podem compartilhar, adaptar e remixar o seu conteúdo, com isso devem dar os créditos e compartilhar o seu trabalho,do mesmo modo, como tudo isto está descrito na licença.
49 | ccAttribution: Compartilhada pela mesma licença atrubuída 3.0 não adaptada
50 |
--------------------------------------------------------------------------------
/src/main.less:
--------------------------------------------------------------------------------
1 | @import "normalize";
2 | @import "variables";
3 | @import "colors";
4 | @import "fonts";
5 |
6 | // Components -----------------------------------------------------------------
7 |
8 | @import "components/action-menu/action-menu";
9 | @import "components/alert/alert";
10 | @import "components/basic-element/basic-element";
11 | @import "components/button/button";
12 | @import "components/card/card";
13 | @import "components/color-group/color-group";
14 | @import "components/color-spectrum/color-spectrum";
15 | @import "components/element-group/element-group";
16 | @import "components/link/link";
17 | @import "components/loading/loading";
18 | @import "components/modal-confirm/modal-confirm";
19 | @import "components/modal-switch/modal-switch";
20 | @import "components/option-panel/option-panel";
21 | @import "components/range/range";
22 | @import "components/select/select";
23 | @import "components/shim/shim";
24 | @import "components/snackbar/snackbar";
25 | @import "components/spindicator/spindicator";
26 | @import "components/tabs/tabs";
27 | @import "components/text-input/text-input";
28 | @import "components/d-pad/d-pad";
29 | @import "components/ftu/ftu";
30 | @import "components/project-list/project-list";
31 | @import "components/featured-tag-card/featured-tag-card";
32 |
33 | // Pages ----------------------------------------------------------------------
34 |
35 | @import "pages/user-projects/user-projects";
36 | @import "pages/element/element";
37 | @import "pages/login/login";
38 | @import "pages/make/make";
39 | @import "pages/page/page";
40 | @import "pages/project/project";
41 | @import "pages/project-settings/project-settings";
42 | @import "pages/style-guide/style-guide";
43 | @import "pages/tinker/tinker";
44 | @import "pages/web-link/web-link";
45 | @import "pages/tag-list/tag-list";
46 |
47 | *,
48 | *:after,
49 | *:before {
50 | box-sizing: border-box;
51 | -webkit-backface-visibility: hidden;
52 | -webkit-tap-highlight-color: rgba(0,0,0,0);
53 | -webkit-tap-highlight-color: transparent;
54 | }
55 |
56 | html {
57 | height: 100%;
58 | }
59 |
60 | body {
61 | height: 100%;
62 | margin: 0;
63 | padding: 0;
64 | font-family: "FiraSans", "Fira Sans", "Fira Sans OT", sans-serif;
65 | }
66 |
67 | code, pre {
68 | font-family: menlo, monospace;
69 | font-size: inherit;
70 | }
71 |
72 | #app {
73 | height: 100%;
74 | }
75 |
76 | .container {
77 | height: 100%;
78 | user-select: none;
79 |
80 | > div {
81 | background: @lightGrey;
82 | min-height: 100%;
83 | }
84 | }
85 |
86 | // Screen readers only
87 | .sr-only {
88 | position: absolute;
89 | width: 1px;
90 | height: 1px;
91 | padding: 0;
92 | margin: -1px;
93 | overflow: hidden;
94 | clip: rect(0 0 0 0);
95 | border: 0;
96 | }
97 |
98 | .text-center {
99 | text-align: center;
100 | }
101 | .text-right {
102 | text-align: right;
103 | }
104 |
--------------------------------------------------------------------------------
/src/pages/discover/discover.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var api = require('../../lib/api');
3 | var reportError = require('../../lib/errors');
4 | var i18n = require('../../lib/i18n');
5 |
6 | var render = require('../../lib/render.jsx');
7 | var Card = require('../../components/card/card.jsx');
8 | var Loading = require('../../components/loading/loading.jsx');
9 | var ProjectList = require ('../../components/project-list/project-list.jsx');
10 |
11 | var lang = i18n.isSupportedLanguage(i18n.currentLanguage) ? i18n.currentLanguage : i18n.defaultLang;
12 |
13 | var Discover = React.createClass({
14 | mixins: [require('react-intl').IntlMixin],
15 | render: function () {
16 | return (
17 |
18 | );
19 | }
20 | });
21 |
22 | render(Discover);
23 |
--------------------------------------------------------------------------------
/src/pages/element/element.less:
--------------------------------------------------------------------------------
1 | @element-label_margin-bottom: 10px;
2 | @editor-form-group_margin-bottom: 20px;
3 |
4 | #editor {
5 | color: @slate;
6 | height: 100%;
7 | position: relative;
8 | }
9 | .editor{
10 | height: 100%;
11 | }
12 |
13 | .get-media {
14 | .get-media-overlay {
15 | position: fixed;
16 | top: 0;
17 | left: 0;
18 | height: 100%;
19 | width: 100%;
20 | z-index: 998;
21 | background: #1A2237;
22 | opacity: 0;
23 | visibility: hidden;
24 | transition: all 0.2s ease-in-out;
25 | &.active {
26 | visibility: visible;
27 | opacity: 0.7;
28 | }
29 | }
30 |
31 | .get-media-controls {
32 | position: fixed;
33 | top: 0;
34 | left: 0;
35 | bottom: 0;
36 | right: 0;
37 | width: 170px;
38 | height: 300px;
39 | z-index: 999;
40 | margin: auto;
41 |
42 | opacity: 0;
43 | visibility: hidden;
44 | transition: all 0.2s ease-in-out;
45 |
46 | &.active {
47 | visibility: visible;
48 | opacity: 1.0;
49 | }
50 |
51 | p {
52 | color: @slate;
53 | }
54 |
55 | button, a {
56 | display: block;
57 | position: relative;
58 | width: 170px;
59 | height: 140px;
60 | margin-bottom: 20px;
61 |
62 | border: none;
63 | outline: none;
64 | background-color: @white;
65 | border-radius: 5px;
66 | box-shadow: 0px 6px 24px 0px rgba(0,0,0,0.3);
67 |
68 | .icon {
69 | width: 50px;
70 | height: 50px;
71 | display: block;
72 | margin: 20px auto 0 auto;
73 | }
74 | }
75 | }
76 | }
77 |
78 |
79 | /*********************************************************
80 | * Editor preview and options
81 | */
82 |
83 | // These are outside the id
84 | // because adding them inside #editor would make
85 | // them too specific and therefore really difficult to override
86 | @editor-preview-height: 150px;
87 | .editor-preview {
88 | box-shadow: 0px 4px 10px 0px rgba(84,94,102,0.30);
89 | text-align: center;
90 | background: @softGrey;
91 | height: 150px;
92 | display: flex;
93 | align-items: center;
94 | justify-content: center;
95 | overflow-y: scroll;
96 | position: fixed;
97 | top: 0;
98 | height: 150px;
99 | z-index: 2;
100 | width: 100%;
101 | img {
102 | box-sizing: content-box;
103 | height: 70%;
104 | }
105 | p {
106 | max-width: 320px;
107 | font-size: 1.6rem;
108 | }
109 | }
110 |
111 | .editor-scroll{
112 | background-image: url("./img/scrubber.svg");
113 | background-position-x: 100%;
114 | background-repeat: no-repeat repeat;
115 | padding: 20px 40px 20px 0;
116 | }
117 | .editor-options {
118 | font-size: 15px;
119 | margin-top: @editor-preview-height;
120 | padding: 0 0 0 20px;
121 | height: calc(~"100% - " @editor-preview-height);
122 | overflow-y: scroll;
123 | label, input {
124 | width: 100%;
125 | display: block;
126 | }
127 | label {
128 | margin-bottom: @element-label_margin-bottom;
129 | }
130 | .form-group {
131 | margin-bottom: @editor-form-group_margin-bottom;
132 | &:last-child {
133 | margin-bottom: 0;
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/pages/element/font-selector.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | /**
4 | * This is a mixin due to the fact that we rely on this.linkState,
5 | * which handles the automatic state binding to whatever component
6 | * is using this font selection component for changing font-family
7 | */
8 | module.exports = {
9 | /**
10 | * Generate the dropdown selector for fonts, with each font option
11 | * styled in the appropriate font.
12 | * @return {[type]}
13 | */
14 | generateFontSelector: function() {
15 | var fonts = ["Roboto", "Bitter", "Pacifico"];
16 | var options = fonts.map(name => {
17 | // setting style on an ;
19 | });
20 | return ;
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/src/pages/element/page-editor.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var LinkedStateMixin = require('react-addons-linked-state-mixin');
3 | var ColorGroup = require('../../components/color-group/color-group.jsx');
4 |
5 | var colorChoices = ["#f2f6fc","#484B50","#6D7886","#98CC3B","#E26A1F","#8173E4"];
6 |
7 | var PageEditor = React.createClass({
8 | mixins: [
9 | LinkedStateMixin,
10 | require('react-intl').IntlMixin
11 | ],
12 | getInitialState: function () {
13 | return {
14 | type: "page",
15 | backgroundColor: this.props.element.styles.backgroundColor || '#f2f6fc'
16 | };
17 | },
18 | componentDidUpdate: function (prevProps, prevState) {
19 | var state = this.state;
20 |
21 | // Update state if parent properties change
22 | if (this.props.element !== prevProps.element) {
23 | state = this.getInitialState();
24 | this.setState(state);
25 | }
26 |
27 | // Cache edits if internal state changes
28 | if (this.state !== prevState) {
29 | this.props.cacheEdits(state);
30 | }
31 | },
32 | componentWillMount: function () {
33 | },
34 | render: function () {
35 | return (
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | );
49 | }
50 | });
51 |
52 | module.exports = PageEditor;
53 |
--------------------------------------------------------------------------------
/src/pages/element/witheditable.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | editText: function(evt) {
3 | if (evt) {
4 | evt.stopPropagation();
5 | evt.preventDefault();
6 | }
7 | this.refs.element.toggleEditing();
8 | // calls our setEditing function after changing state
9 | },
10 | stopEditing: function(evt) {
11 | this.refs.element.stopEditing();
12 | },
13 | setEditing: function(boolval) {
14 | this.setState({
15 | editing: boolval
16 | });
17 | },
18 | updateText: function (text) {
19 | this.setState({
20 | innerHTML: text
21 | });
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/src/pages/login/form-input.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
3 | var Link = require('../../components/link/link.jsx');
4 |
5 | var FormInput = React.createClass({
6 | mixins: [require('react-intl').IntlMixin],
7 | getDefaultProps: function () {
8 | return {
9 | type: 'text',
10 | tabIndex: 0
11 | };
12 | },
13 | checkForReturn: function (e) {
14 | if (e.keyCode === 13) {
15 | if (typeof this.props.onReturn === 'function') {
16 | this.props.onReturn();
17 | }
18 | }
19 | },
20 | render: function () {
21 | var helpText;
22 | if(this.props.helpText) {
23 | helpText = ();
24 | }
25 | return (
26 |
27 |
28 |
29 |
30 |
31 | {this.props.errors && this.props.errors.join(' ')}
32 |
33 |
{helpText}
34 |
);
35 | }
36 | });
37 |
38 | module.exports = FormInput;
39 |
--------------------------------------------------------------------------------
/src/pages/login/login.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var render = require('../../lib/render.jsx');
3 |
4 | var Loading = require('../../components/loading/loading.jsx');
5 | var SignIn = require('./sign-in.jsx');
6 | var SignUp = require('./sign-up.jsx');
7 |
8 | //
9 | // View that renders sign-in and sign-up forms
10 | var Login = React.createClass({
11 | mixins: [require('react-intl').IntlMixin, require('../../lib/router')],
12 |
13 | // Props:
14 | // (none)
15 |
16 | // State:
17 | // mode
18 | // ['sign-in', 'sign-up']
19 | // Indicates which form is currently visible
20 | // loading
21 | // boolean
22 | // Turns the UI-blocking loader on/off
23 | getInitialState: function () {
24 | var mode = this.getRouteParams().mode;
25 | if (['sign-up', 'sign-in'].indexOf(mode) === -1) {
26 | mode = 'sign-up';
27 | }
28 | return {
29 | mode,
30 | loading: false
31 | };
32 | },
33 | componentWillMount: function () {
34 | this.props.update({
35 | onBackPressed: () => {
36 | window.Platform.goToHomeScreen();
37 | }
38 | });
39 | },
40 | setParentState: function (state) {
41 | this.setState(state);
42 | },
43 | render: function () {
44 | return ;
50 | }
51 | });
52 |
53 | render(Login);
54 |
--------------------------------------------------------------------------------
/src/pages/login/login.less:
--------------------------------------------------------------------------------
1 | #login {
2 | padding: 20px 0 0 0;
3 | background: @blue;
4 | color: @white;
5 |
6 | h3 {
7 | margin: 0 0 20px 0;
8 | text-align: center;
9 | font-weight: 400;
10 | font-size: 20px;
11 | }
12 |
13 | .logo {
14 | text-align: center;
15 | img {
16 | width: 80px;
17 | }
18 | }
19 |
20 | .login-options {
21 | padding: 10px 0;
22 | font-size: 15px;
23 | label, input {
24 | width: 85%;
25 | display: block;
26 | margin: 12px auto;
27 | }
28 | .form-group {
29 | margin: 0;
30 | border-bottom: 2px solid fade(@white, 15%);
31 | }
32 | >div:first-of-type {
33 | .form-group {
34 | border-top: 2px solid fade(@white, 15%);
35 | }
36 | }
37 | .checkbox {
38 | display: flex;
39 | align-items: center;
40 | margin-top: 20px;
41 | font-size: 14px;
42 | span {
43 | padding: 2px 0 0 0;
44 | }
45 | .checkbox-ui {
46 | flex-shrink: 0;
47 | display: block;
48 | margin-right: 5px;
49 | height: 24px;
50 | width: 24px;
51 | background: url(./img/material-check.png) center center / contain no-repeat;
52 | }
53 | input:checked + .checkbox-ui {
54 | background-image: url(./img/material-check-on.png);
55 | }
56 | input {
57 | .sr-only;
58 | }
59 | }
60 | }
61 |
62 | a {
63 | color: inherit;
64 | }
65 |
66 | .help-text {
67 | margin: 10px 30px 0 0;
68 | font-size: 14px;
69 | }
70 |
71 | .by-joining {
72 | text-align: center;
73 | max-width: 250px;
74 | margin: 10px auto 0 auto;
75 | opacity: 0.9;
76 | font-size: 12px;
77 | }
78 |
79 | .already-joined {
80 | margin-top: 25px;
81 | }
82 |
83 | .text-larger {
84 | font-size: 16px;
85 | font-weight: 500;
86 | }
87 |
88 | .btn {
89 | width: 90%;
90 | margin: 20px auto !important;
91 | font-size: 16px;
92 | line-height: 1.7;
93 | margin-bottom: 0;
94 | color: @blue;
95 | border-radius: 30px;
96 | border: 1px solid @shadowBlue;
97 | box-shadow: none;
98 | &[disabled] {
99 | background: darken(@blue, 5%);
100 | color: @white;
101 | opacity: 0.5;
102 | box-shadow: none;
103 | }
104 | }
105 |
106 | input[type="text"],
107 | input[type="password"],
108 | input[type="email"] {
109 | background: transparent;
110 | border: none;
111 | font-size: 20px;
112 | transition: all 0.2s ease-in-out;
113 | &::placeholder {
114 | color: fade(@white, 50%);
115 | font-size: 14px;
116 | padding-top: 4px;
117 | opacity: 1;
118 | }
119 | }
120 |
121 | .error {
122 | margin: 10px auto;
123 | width: 85%;
124 | background: @yellow;
125 | color: darken(@yellow, 40%);
126 | padding: 10px;
127 | border-radius: 3px;
128 | text-align: center;
129 | position: relative;
130 | &::after {
131 | bottom: 100%;
132 | left: 50%;
133 | border: solid transparent;
134 | content: " ";
135 | height: 0;
136 | width: 0;
137 | position: absolute;
138 | pointer-events: none;
139 | border-color: none;
140 | border-bottom-color: @yellow;
141 | border-width: 4px;
142 | margin-left: -4px;
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/pages/make/make.less:
--------------------------------------------------------------------------------
1 | #make {
2 | display: block;
3 | position: relative;
4 | min-width: 100%;
5 | min-height: 100%;
6 | padding: 20px 0;
7 |
8 | .btn-create {
9 | width: calc(100% - 10px);
10 | margin: 20px auto;
11 | text-decoration: none;
12 | }
13 |
14 | .profile-card {
15 | border-radius: 3px;
16 | box-shadow: 0 2px 0 0 @heatherGrey;
17 | width: calc(100% - 10px);
18 | background: @white;
19 | color: @slate;
20 | margin: 0 auto 20px auto;
21 | padding: 10px;
22 | text-align: center;
23 | p {
24 | margin: 0;
25 | display: flex;
26 | }
27 | .btn {
28 | box-shadow: inset 0 0 0 2px fade(@slate, 50%);
29 | background: fade(@slate, 4%);
30 | flex: 1;
31 | margin: 5px;
32 | padding: 10px;
33 | }
34 | }
35 | .action {
36 | button {
37 | margin: 0;
38 | }
39 | }
40 | .text {
41 | padding: 0;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/pages/page/flattening.js:
--------------------------------------------------------------------------------
1 | var types = require('../../components/basic-element/basic-element.jsx').types;
2 | module.exports = {
3 | flatten: function (element) {
4 | if (!types[element.type]) {
5 | return false;
6 | }
7 | return types[element.type].spec.flatten(element);
8 | },
9 |
10 | expand: function (element) {
11 | if (!types[element.type]) {
12 | return false;
13 | }
14 | return types[element.type].spec.expand(element);
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/src/pages/page/page-controls.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var classNames = require('classnames');
3 | var Link = require('../../components/link/link.jsx');
4 | var platform = require('../../lib/platform');
5 |
6 | var PageControls = React.createClass({
7 |
8 | mixins: [
9 | require("./flattening")
10 | ],
11 |
12 | secondaryButtonClass: function(name) {
13 | var names = {
14 | secondary: true,
15 | active: this.props.currentElementId > -1 && !this.props.showAddMenu
16 | };
17 | names[name] = true;
18 | return classNames(names);
19 | },
20 |
21 | render: function() {
22 | return (
23 |
27 |
32 |
35 |
36 |
41 |

42 |
43 |
44 | );
45 | }
46 | });
47 |
48 | module.exports = PageControls;
49 |
--------------------------------------------------------------------------------
/src/pages/project-settings/project-settings.less:
--------------------------------------------------------------------------------
1 | #projectSettings {
2 | padding: 30px;
3 | display: flex;
4 | flex-direction: column;
5 | min-height: 100%;
6 |
7 | > div:first-child {
8 | flex: 1;
9 | }
10 |
11 | .cc {
12 | font-size: 0.9em;
13 | color: @slate;
14 |
15 | img {
16 | opacity: 0.5;
17 | height: 18px;
18 | margin-right: 5px;
19 | }
20 |
21 | .mark {
22 | display: flex;
23 | align-items: center;
24 | }
25 |
26 | p:nth-child(2) {
27 | margin-bottom: 0;
28 | }
29 |
30 | a {
31 | color: @slate;
32 | }
33 |
34 | .explanation {
35 | color: lighten(@slate, 20%);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/pages/project/dpad-logic.js:
--------------------------------------------------------------------------------
1 | function getTargetCoords(sourceCoords, direction) {
2 | var cx = sourceCoords.x,
3 | cy = sourceCoords.y;
4 |
5 | var panTargets = {
6 | left: { x: cx - 1, y: cy },
7 | right: { x: cx + 1, y: cy },
8 | up: { x: cx, y: cy - 1 },
9 | down: { x: cx, y: cy + 1 }
10 | };
11 |
12 | var target = panTargets[direction];
13 |
14 | return target;
15 | }
16 |
17 | function verifyCoordsExist(pages, target) {
18 | if (pages.some(p => p.coords.x === target.x && p.coords.y === target.y)) {
19 | return true;
20 | } else {
21 | return false;
22 | }
23 | }
24 |
25 | module.exports = {
26 | handleDirectionClick: function (event) {
27 | var target = getTargetCoords(this.state.zoomedPageCoords, event);
28 |
29 | if (verifyCoordsExist(this.state.pages, target)) {
30 | this.zoomToPage(target);
31 | }
32 | },
33 | componentDidMount: function () {
34 | if (window) {
35 | window.addEventListener('resize', (event) => {
36 | this.setDPadStyle();
37 | });
38 | }
39 | },
40 | componentDidUpdate: function () {
41 | this.setDPadStyle();
42 | },
43 | setDPadStyle: function () {
44 | // Check what directions have pages that exist and update the dpad UI accordingly
45 | if (this.state.isPageZoomed && this.state.pages.length) {
46 | this.refs.dpad.bulkSetVisibility({
47 | showUp: verifyCoordsExist(this.state.pages, getTargetCoords(this.state.zoomedPageCoords, 'up')),
48 | showDown: verifyCoordsExist(this.state.pages, getTargetCoords(this.state.zoomedPageCoords, 'down')),
49 | showLeft: verifyCoordsExist(this.state.pages, getTargetCoords(this.state.zoomedPageCoords, 'left')),
50 | showRight: verifyCoordsExist(this.state.pages, getTargetCoords(this.state.zoomedPageCoords, 'right'))
51 | });
52 | }
53 |
54 | if (this.state.params.mode === 'play' && this.state.isPageZoomed) {
55 | this.refs.dpad.positionAroundContainer(this.getPageBoundingRect());
56 | }
57 | }
58 | };
59 |
--------------------------------------------------------------------------------
/src/pages/project/form-pages.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var PageBlock = require("./pageblock.jsx");
3 |
4 | module.exports = {
5 | formPages: function() {
6 | return this.state.pages.map((page, index) => {
7 | var props = {
8 | page,
9 | selected: page.id === this.state.selectedEl,
10 | source: page.id === this.state.sourcePageID,
11 | target: page.id === this.state.selectedEl && this.state.params.mode === 'link',
12 | blurred: this.state.isPageZoomed && (page.coords.x !== this.state.zoomedPageCoords.x || page.coords.y !== this.state.zoomedPageCoords.y),
13 | transform: this.cartesian.getTransform(page.coords),
14 | interactive: this.state.isPageZoomed,
15 | onClick: this.onPageClick.bind(this, page)
16 | };
17 | return ;
18 | });
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/src/pages/project/loader.js:
--------------------------------------------------------------------------------
1 | var api = require('../../lib/api');
2 | var platform = require('../../lib/platform');
3 | var reportError = require('../../lib/errors');
4 | var types = require('../../components/basic-element/basic-element.jsx').types;
5 |
6 | function findLandingPage(pages) {
7 | var result;
8 | // ... first, try to select 0, 0
9 | pages.forEach((page) => {
10 | if (page.coords.x === 0 && page.coords.y === 0) {
11 | result = page;
12 | }
13 | });
14 | // ... and if it was deleted, select the first page in the array
15 | return result || pages[0];
16 | }
17 |
18 | module.exports = {
19 | uri: function () {
20 | return `/users/${this.state.params.user}/projects/${this.state.params.project}/pages`;
21 | },
22 |
23 | load: function () {
24 | this.setState({loading: true});
25 | api({uri: this.uri()}, (err, data) => {
26 |
27 | this.setState({
28 | loading: false,
29 | sourcePageID: false
30 | });
31 |
32 | if (err) {
33 | reportError('Error loading project', err);
34 | } else if (!data || !data.pages) {
35 | reportError('No project found...');
36 | } else {
37 | var state = {
38 | isFirstLoad: false
39 | };
40 | var pages = this.formatPages(data.pages);
41 |
42 | // Set cartesian coordinates
43 | this.cartesian.allCoords = pages.map(el => el.coords);
44 |
45 | state.pages = pages;
46 |
47 | var landingPage = findLandingPage(pages);
48 | var {x, y} = this.cartesian.getFocusTransform(landingPage.coords, this.state.matrix[0]);
49 |
50 | if (this.state.params.mode === 'play' && typeof this.state.isFirstLoad) {
51 | this.zoomToPage(landingPage.coords);
52 | } else if (this.state.params.mode === 'edit' && !this.state.selectedEl) {
53 | state.selectedEl = landingPage.id;
54 | state.matrix = [this.state.matrix[0], 0, 0, this.state.matrix[0], x, y];
55 | } else if (this.state.isFirstLoad) {
56 | state.matrix = [this.state.matrix[0], 0, 0, this.state.matrix[0], x, y];
57 | }
58 |
59 | this.setState(state);
60 |
61 | // Highlight the source page if you're in link destination mode
62 | var java = platform.getAPI();
63 | if (java) {
64 | var payloads = java.getPayloads("link-element");
65 | if (payloads) {
66 | // We do not clear the payload, since it is also
67 | // required later on in setdestination.js
68 | try {
69 | var pageId;
70 | var list = JSON.parse(payloads);
71 | if (list.length > 0) {
72 | var entry = list[0];
73 | if (entry.metadata) {
74 | var metadata = entry.metadata;
75 | pageId = metadata.pageID;
76 | this.highlightPage(pageId, 'source');
77 | } else {
78 | console.error("no entry metadata available...", entry);
79 | }
80 | var element = types.link.spec.expand(entry.data);
81 | pageId = element.attributes.targetPageId;
82 | if (pageId) {
83 | this.highlightPage(pageId, 'selected');
84 | }
85 | } else {
86 | console.error("link-element payloads was an empty array:" + payloads);
87 | }
88 | } catch (e) {
89 | console.error("malformed JSON found while loading in link-element data...");
90 | }
91 | }
92 | }
93 | else if (this.state.params.mode === 'link') {
94 | this.highlightPage(this.state.routeData.pageID, 'source');
95 | }
96 | }
97 | });
98 | }
99 | };
100 |
--------------------------------------------------------------------------------
/src/pages/project/pageblock.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var classNames = require('classnames');
3 | var ElementGroup = require('../../components/element-group/element-group.jsx');
4 |
5 | /**
6 | * This is the component used in the Project view that draws pages as
7 | * a "simple page representation" rather than the detailed representation
8 | * you get when you navigate to the Page view.
9 | */
10 | var PageBlock = React.createClass({
11 |
12 | render: function() {
13 | var classes = classNames('page-container', {
14 | selected: this.props.selected,
15 | unselected: this.props.unselected,
16 | source: this.props.source,
17 | target: this.props.target,
18 | blurred: this.props.blurred
19 | });
20 |
21 | var style = {
22 | backgroundColor: this.props.page.styles.backgroundColor,
23 | transform: this.props.transform,
24 | WebkitTransform: this.props.transform
25 | };
26 |
27 | // The "shim" and "indicator" divs don't actually house any content,
28 | // suggesting that either they are intended to be empty, or they will
29 | // be hooked into later, in which case we have failed to uphold React
30 | // practices and reverted to traditional HTML work, which would be bad.
31 | return ();
38 | }
39 | });
40 |
41 | module.exports = PageBlock;
42 |
--------------------------------------------------------------------------------
/src/pages/project/remix.js:
--------------------------------------------------------------------------------
1 | var dispatcher = require('../../lib/dispatcher');
2 | var api = require('../../lib/api');
3 |
4 | module.exports = {
5 | componentDidMount: function() {
6 | // Handle remix calls from Android wrapper
7 | window.createRemix = () => {
8 | var uri = `/users/${this.state.params.user}/projects/${this.state.params.project}/remixes`;
9 |
10 | // Duplicate project via API call
11 | api({
12 | method: 'post',
13 | uri: uri
14 | }, (err, data) => {
15 | if (err) {
16 | window.Platform.trackEvent('Remix', 'Remix Error', err);
17 | return console.error('Error remixing project', err);
18 | }
19 |
20 | var projectID = data.project.id;
21 | var projectTitle = data.project.title;
22 |
23 | // Get author's username
24 | api({
25 | method: 'GET',
26 | uri: `/users/${this.state.params.user}/projects/${this.state.params.project}`
27 | }, (err, moreData) => {
28 | if (err) {
29 | window.Platform.trackEvent('Remix', 'Remix Error', err);
30 | return console.error('Error remixing project', err);
31 | }
32 |
33 | window.Platform.trackEvent('Remix', 'Remix Success', projectID);
34 |
35 | if (window.Platform) {
36 | window.Platform.setView(
37 | `/users/${this.state.user.id}/projects/${projectID}`,
38 | JSON.stringify({
39 | isFreshRemix: true,
40 | title: projectTitle,
41 | originalAuthor: moreData.project.author.username
42 | })
43 | );
44 | }
45 | });
46 | });
47 | };
48 |
49 | if (this.android && this.state.routeData.isFreshRemix) {
50 | // Notify user that THIS IS A REEEEEEMIXXXXX
51 | dispatcher.fire('modal-confirm:show', {
52 | config: {
53 | header: 'Project Remix',
54 | body: `This is your copy of ${this.state.routeData.title}. You can add or change anything. The original will stay the same. Have fun!`,
55 | attribution: this.state.routeData.originalAuthor,
56 | icon: 'tinker.png',
57 | buttons: [{
58 | text: this.getIntlMessage('got_it')
59 | }]
60 | }
61 | });
62 |
63 | // Prepend "Remix of..." to project name
64 |
65 | var remixTitle = this.state.routeData.title;
66 |
67 | if (!remixTitle.match(/^Remix of/)) {
68 | remixTitle = 'Remix of ' + remixTitle;
69 | }
70 |
71 | api({
72 | method: 'PATCH',
73 | uri: `/users/${this.state.user.id}/projects/${this.state.params.project}`,
74 | json: {
75 | title: remixTitle
76 | }
77 | }, function (err, body) {
78 | if (err) {
79 | window.Platform.trackEvent('Remix', 'Remix Rename Error', this.state.params.project);
80 | console.error('Could not update project settings.');
81 | }
82 | });
83 | }
84 | }
85 | };
86 |
--------------------------------------------------------------------------------
/src/pages/project/renderhelpers.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var assign = require('react/lib/Object.assign');
3 | var {SecondaryButton} = require('../../components/action-menu/action-menu.jsx');
4 |
5 | module.exports = {
6 | getContainerStyle: function() {
7 | return {
8 | width: this.cartesian.width + 'px',
9 | height: this.cartesian.height + 'px'
10 | };
11 | },
12 |
13 | getBoundingStyle: function() {
14 | var transformString = `matrix(${this.state.matrix.join(', ')})`;
15 | return assign({
16 | transform: transformString,
17 | WebkitTransform: transformString,
18 | opacity: this.state.pages.length ? 1 : 0
19 | }, this.cartesian.getBoundingSize() );
20 | },
21 |
22 | getPageURL: function(params, selectedEl) {
23 | return `/users/${params.user}/projects/${params.project}/pages/${selectedEl}`;
24 | },
25 |
26 | generateAddContainers: function(isPlayOnly) {
27 | if (isPlayOnly) {
28 | return false;
29 | }
30 |
31 | return this.cartesian.edges.map(coords => {
32 | var transformString = this.cartesian.getTransform(coords);
33 | return (
37 |

38 |
);
39 | });
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/src/pages/project/setdestination.js:
--------------------------------------------------------------------------------
1 | var types = require('../../components/basic-element/basic-element.jsx').types;
2 | var api = require('../../lib/api');
3 | var platform = require('../../lib/platform');
4 | var reportError = require('../../lib/errors');
5 | var dispatcher = require('../../lib/dispatcher');
6 |
7 | module.exports = {
8 | componentDidMount: function () {
9 | // Handle button actions
10 | dispatcher.on('linkClicked', (event) => {
11 | if (event.props.targetWebURL) {
12 | if (window.Platform) {
13 | window.Platform.openExternalUrl(event.props.targetWebURL);
14 | } else {
15 | document.location.href = event.props.targetWebURL;
16 | }
17 | } else {
18 | if (event.props.targetPageId && this.state.isPageZoomed) {
19 | this.zoomToPage( this.pageIdToCoords(event.props.targetPageId) );
20 | } else {
21 | this.highlightPage(event.props.targetPageId, 'selected');
22 | }
23 | }
24 | });
25 | },
26 |
27 | componentWillMount: function () {
28 | var java = platform.getAPI();
29 |
30 | if (java) {
31 | this.props.update({
32 | onBackPressed: () => {
33 | java.clearPayloads(`link-element`);
34 | window.Platform.goBack();
35 | }
36 | });
37 | }
38 | },
39 |
40 | setDestination: function () {
41 | var java = platform.getAPI();
42 |
43 | // If we have java caching available, there should be a link-element in cache
44 | if(java) {
45 | var payloads = java.getPayloads("link-element");
46 | java.clearPayloads("link-element");
47 |
48 | var list = JSON.parse(payloads);
49 | var element = types.link.spec.expand(list[0].data);
50 | element.attributes.targetPageId = this.state.selectedEl;
51 | element.attributes.targetProjectId = this.state.params.project;
52 | element.attributes.targetUserId = this.state.params.user;
53 | element.attributes.targetWebURL = ``; // Remove web url
54 |
55 | var serialized = JSON.stringify({
56 | data: element
57 | });
58 | java.queue("edit-element", serialized);
59 | platform.goBack();
60 | } else {
61 | console.warn(`This feature only works in Android.`);
62 | }
63 | }
64 | };
65 |
--------------------------------------------------------------------------------
/src/pages/project/transforms.js:
--------------------------------------------------------------------------------
1 | var Panzoom = require('panzoom');
2 |
3 | var MAX_ZOOM = 0.5;
4 | var MIN_ZOOM = 0.18;
5 |
6 | module.exports = {
7 | componentDidMount: function () {
8 | var pz = new Panzoom(this.refs.bounding, {
9 | minScale: MIN_ZOOM,
10 | maxScale: MAX_ZOOM,
11 | onEnd: (e) => {
12 | var matrix = pz.getMatrix();
13 | this.setState({matrix});
14 | }
15 | });
16 | this.panzoom = pz;
17 | if (this.state.isPageZoomed) {
18 | pz.disable();
19 | }
20 | },
21 | componentDidUpdate: function (prevProps, prevState) {
22 |
23 | // Disable panzoom if state goes from zoomedIn to not zoomedIn
24 | if (this.panzoom && this.state.isPageZoomed && !prevState.isPageZoomed) {
25 | this.panzoom.disable();
26 | }
27 |
28 | // Disable if state goes from zoomedIn to not zoomedIn
29 | if (this.panzoom && !this.state.isPageZoomed && prevState.isPageZoomed) {
30 | this.panzoom.enable();
31 | }
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/src/pages/style-guide/style-guide.less:
--------------------------------------------------------------------------------
1 | #style-guide {
2 | padding: 30px 0;
3 | background: linear-gradient(180deg, #141C29, #31325F);
4 |
5 | > .wrapper {
6 | padding: 20px 30px;
7 | margin: 0 auto;
8 | max-width: 960px;
9 | background: #F5F4FF;
10 | border-radius: 4px;
11 | }
12 |
13 | code {
14 | background: @slate;
15 | color: @white;
16 | padding: 0.2em 0.5em;
17 | margin: 0 10px;
18 | font-weight: 100;
19 | font-size: 0.8em;
20 | }
21 |
22 | .pattern {
23 | border: 1px solid @heatherGrey;
24 | border-radius: 2px;
25 | margin: 0 0 25px 0;
26 |
27 | .title {
28 | padding: 14px;
29 | margin: 0 0 10px 0;
30 | text-transform: uppercase;
31 | font-size: 14px;
32 | line-height: 15px;
33 | letter-spacing: 0.12rem;
34 | color: #2B3747;
35 | background: @heatherGrey;
36 | }
37 | .content {
38 | padding: 0 10px 10px 10px;
39 | }
40 | }
41 |
42 | .swatches {
43 | .color {
44 | color: @white;
45 | margin-bottom: 5px;
46 | font-family: monospace;
47 | font-size: 14px;
48 |
49 | span {
50 | display: inline-block;
51 | background: rgba(0,0,0,0.75);
52 | padding: 10px;
53 | border-right: 1px solid rgba(0,0,0,0.8);
54 | border-top-right-radius: 10px;
55 | }
56 |
57 | &.blue {
58 | background: @blue;
59 | }
60 | &.shadowBlue {
61 | background: @shadowBlue;
62 | }
63 | &.sapphire {
64 | background: @sapphire;
65 | }
66 | &.teal {
67 | background: @teal;
68 | }
69 | &.slate {
70 | background: @slate;
71 | }
72 | &.darkSlate {
73 | background: @darkSlate;
74 | }
75 | &.heatherGrey {
76 | background: @heatherGrey;
77 | }
78 | &.lightGrey {
79 | background: @lightGrey;
80 | }
81 | &.plum {
82 | background: @plum;
83 | }
84 | &.shadowPlum {
85 | background: @shadowPlum;
86 | }
87 | &.brick {
88 | background: @brick;
89 | }
90 | &.softGrey {
91 | background: @softGrey;
92 | }
93 | &.aqua {
94 | background: @aqua;
95 | }
96 | &.green {
97 | background: @green;
98 | }
99 | &.yellow {
100 | background: @yellow;
101 | }
102 | &.orange {
103 | background: @orange;
104 | }
105 | &.purple {
106 | background: @purple;
107 | }
108 | }
109 | }
110 |
111 | .alert.hidden {
112 | // Alerts are hidden by default, but we want to see one in the style guide...
113 | display: flex;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/pages/tag-list/tag-list.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var api = require('../../lib/api');
3 | var reportError = require('../../lib/errors');
4 | var i18n = require('../../lib/i18n');
5 | var render = require('../../lib/render.jsx');
6 | var platform = require('../../lib/platform');
7 |
8 | var ProjectList = require('../../components/project-list/project-list.jsx');
9 |
10 | var lang = i18n.isSupportedLanguage(i18n.currentLanguage) ? i18n.currentLanguage : i18n.defaultLang;
11 |
12 | var TagList = React.createClass({
13 | mixins: [
14 | require('react-intl').IntlMixin,
15 | require('../../lib/router')
16 | ],
17 | goBack: function () {
18 | platform.goBack();
19 | },
20 | render: function () {
21 | return (
22 |
30 | );
31 | }
32 | });
33 |
34 | render(TagList);
35 |
--------------------------------------------------------------------------------
/src/pages/tag-list/tag-list.less:
--------------------------------------------------------------------------------
1 | #tag-list {
2 | header {
3 | width: 90%;
4 | margin: auto;
5 |
6 | p {
7 | color: darken(@heatherGrey, 25%);
8 | margin-bottom: 0;
9 | font-size: 12px;
10 | }
11 | }
12 | .tag-name {
13 | color: @slate;
14 | font-size: 36px;
15 | margin: 20px 0;
16 | line-height: 1.5;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/pages/tinker/tinker.less:
--------------------------------------------------------------------------------
1 |
2 | #tinker {
3 | @_spectrum-pickerHeight: calc(~"100vh -" @editorPreviewHeight + @color-preview-height + @tabs_total-height + @editor-form-group_margin-bottom + @range_total-height + @element-label_margin-bottom + @editor-options-font-size*1.5); //line height of a label
4 | @_bg: #31344F;
5 | @_bgLight: #414468;
6 | @_greenText: #98FE90;
7 | @color-preview-height: 55px;
8 | @editorPreviewHeight: 140px;
9 | @editor-options-font-size: 15px;
10 |
11 | background-color: @_bg;
12 | color: @white;
13 | font-size: @editor-options-font-size;
14 |
15 | height: 100%;
16 | overflow: hidden;
17 |
18 | .editor-flex-container {
19 | height: 100%;
20 | overflow: hidden;
21 | }
22 |
23 | .editor-options {
24 | flex-grow: 1;
25 | overflow: scroll;
26 | padding: 0 20px 10px 20px;
27 | margin-top: 0;
28 | }
29 | .editor-scroll{
30 | background-image: url("./img/scrubber-white.svg");
31 | margin-right: -20px;
32 | }
33 |
34 | .editor-preview,
35 | .color-preview {
36 | flex-shrink: 0;
37 | }
38 |
39 | .editor-preview {
40 | height: @editorPreviewHeight;
41 | button.debug {
42 | position: absolute;
43 | color: #333;
44 | }
45 | }
46 |
47 | .tab-content{
48 | margin-top: @tabs_total-height;
49 | }
50 |
51 | .range {
52 | color: @_greenText;
53 | input[type=range] {
54 | background: #414468;
55 | }
56 | input[type=range]::-webkit-slider-thumb {
57 | box-shadow: 0px 1px 0 1px darken(@_bg, 4%);
58 | }
59 | }
60 |
61 | .color-preview {
62 | font-size: 14px;
63 | padding: 10px 20px;
64 | background: @_bgLight;
65 | display: flex;
66 | align-items: center;
67 | margin-top: 140px;
68 | height: @color-preview-height;
69 | .color-preview-right {
70 | flex-grow: 1;
71 | display: flex;
72 | justify-content: flex-end;
73 | }
74 | .color-preview-swatch {
75 | position: relative;
76 | height: 35px;
77 | width: 48px;
78 | border-radius: 2px;
79 | background: url(./img/gray_and_white_checkers.gif);
80 | > div {
81 | position: absolute;
82 | top: 0;
83 | left: 0;
84 | bottom: 0;
85 | right: 0;
86 | border-radius: 2px;
87 | }
88 | }
89 | }
90 | .spectrum-container{
91 | .saturation{
92 | height: @_spectrum-pickerHeight;
93 | }
94 | .hue{
95 | width: @_spectrum-pickerHeight;
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/pages/user-projects/user-projects.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var router = require('../../lib/router');
3 |
4 | var render = require('../../lib/render.jsx');
5 | var ProjectList = require ('../../components/project-list/project-list.jsx');
6 |
7 | var UserProjects = React.createClass({
8 | mixins: [router,require('react-intl').IntlMixin],
9 | render: function () {
10 | return (
11 |
12 | );
13 | }
14 | });
15 |
16 | render(UserProjects);
17 |
--------------------------------------------------------------------------------
/src/pages/user-projects/user-projects.less:
--------------------------------------------------------------------------------
1 | #userProjects {
2 | display: block;
3 | position: relative;
4 | width: 100%;
5 | height: 100%;
6 | padding: 20px 0;
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/web-link/web-link.less:
--------------------------------------------------------------------------------
1 | #webLink {
2 | padding: 20px;
3 |
4 | h4 {
5 | margin-bottom: 0;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/static/fonts/FiraSans-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/webmaker-core/69fd62ea9bdcc14fcb71575393ff95baa5e6f889/src/static/fonts/FiraSans-Medium.woff
--------------------------------------------------------------------------------
/src/static/fonts/FiraSans-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/webmaker-core/69fd62ea9bdcc14fcb71575393ff95baa5e6f889/src/static/fonts/FiraSans-Regular.woff
--------------------------------------------------------------------------------
/src/static/fonts/lamson-sharpie-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/webmaker-core/69fd62ea9bdcc14fcb71575393ff95baa5e6f889/src/static/fonts/lamson-sharpie-webfont.woff
--------------------------------------------------------------------------------
/src/static/img/B-white.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/B.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/I-white.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/I.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/U-white.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/U.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/webmaker-core/69fd62ea9bdcc14fcb71575393ff95baa5e6f889/src/static/img/add.png
--------------------------------------------------------------------------------
/src/static/img/align-center.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/align-left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/align-right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/avatar-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/back-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/webmaker-core/69fd62ea9bdcc14fcb71575393ff95baa5e6f889/src/static/img/back-arrow.png
--------------------------------------------------------------------------------
/src/static/img/brush.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/camera-gallery.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/camera.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/caret-down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/cc.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/static/img/change-image.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/curved-arrow-wide.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/static/img/curved-arrow.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/static/img/default.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/static/img/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/webmaker-core/69fd62ea9bdcc14fcb71575393ff95baa5e6f889/src/static/img/demo.png
--------------------------------------------------------------------------------
/src/static/img/external-url.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/img/flag.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/static/img/gray_and_white_checkers.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/webmaker-core/69fd62ea9bdcc14fcb71575393ff95baa5e6f889/src/static/img/gray_and_white_checkers.gif
--------------------------------------------------------------------------------
/src/static/img/link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/material-check-on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/webmaker-core/69fd62ea9bdcc14fcb71575393ff95baa5e6f889/src/static/img/material-check-on.png
--------------------------------------------------------------------------------
/src/static/img/material-check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/webmaker-core/69fd62ea9bdcc14fcb71575393ff95baa5e6f889/src/static/img/material-check.png
--------------------------------------------------------------------------------
/src/static/img/more-dots.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/static/img/nub-white.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/static/img/nub.svg:
--------------------------------------------------------------------------------
1 |
28 |
--------------------------------------------------------------------------------
/src/static/img/oval-droplet.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/static/img/oval-flag.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/static/img/oval-source.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/page-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/webmaker-core/69fd62ea9bdcc14fcb71575393ff95baa5e6f889/src/static/img/page-screenshot.png
--------------------------------------------------------------------------------
/src/static/img/palette-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/pencil-blue.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/pencil.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/plus.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/img/scrubber-white.svg:
--------------------------------------------------------------------------------
1 |
2 |
18 |
--------------------------------------------------------------------------------
/src/static/img/scrubber.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/static/img/settings.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/slider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/webmaker-core/69fd62ea9bdcc14fcb71575393ff95baa5e6f889/src/static/img/slider.png
--------------------------------------------------------------------------------
/src/static/img/take-photo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/text.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/tinker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/webmaker-core/69fd62ea9bdcc14fcb71575393ff95baa5e6f889/src/static/img/tinker.png
--------------------------------------------------------------------------------
/src/static/img/toucan.svg:
--------------------------------------------------------------------------------
1 |
27 |
--------------------------------------------------------------------------------
/src/static/img/trash.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/img/web-search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/img/webmaker-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
41 |
--------------------------------------------------------------------------------
/src/static/img/x.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/static/img/zoom-in.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/static/img/zoom-out-blue.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
--------------------------------------------------------------------------------
/src/static/img/zoom-out.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/tests/color-spectrum.test.js:
--------------------------------------------------------------------------------
1 | var should = require('should');
2 | var React = require('react');
3 |
4 | // Polyfill Intl for node -- some versions (>=10) don't support the Intl API
5 | var areIntlLocalesSupported = require('intl-locales-supported');
6 | var localesMyAppSupports = ['en-US'];
7 | if (global.Intl) {
8 | if (!areIntlLocalesSupported(localesMyAppSupports)) {
9 | require('intl');
10 | Intl.NumberFormat = IntlPolyfill.NumberFormat;
11 | Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
12 | }
13 | } else {
14 | global.Intl = require('intl');
15 | }
16 |
17 | var ColorSpectrum = require('../components/color-spectrum/color-spectrum.jsx');
18 |
19 | describe('ColorSpectrum', function() {
20 | describe("getColor()", function() {
21 | var getColor = ColorSpectrum.prototype.getColor;
22 |
23 | it('returns color if it is well-formed', function() {
24 | getColor('rgba(255, 0, 0, 0)', 'rgba(0, 0, 0, 0)').rgbaString()
25 | .should.eql('rgba(255, 0, 0, 0)');
26 | });
27 |
28 | it('returns default color if color is malformed', function() {
29 | getColor('aweg', 'rgba(0, 0, 0, 0)').rgbaString()
30 | .should.eql('rgba(0, 0, 0, 0)');
31 | });
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/tests/jsonUtils.test.js:
--------------------------------------------------------------------------------
1 | var {jsonToFormEncoded, parseJSON} = require('../lib/jsonUtils');
2 | var should = require('should');
3 |
4 | describe('jsonUtils', function () {
5 |
6 | describe('#jsonToFormEncoded', function () {
7 | [
8 | [
9 | {foo: 'bar'},
10 | 'foo=bar'
11 | ],
12 | [
13 | {foo: 'bar', bar: 'baz'},
14 | 'foo=bar&bar=baz'
15 | ],
16 | [
17 | {a: 'a@b.com', 'f_%2': 'b'},
18 | 'a=a%40b.com&f_%252=b'
19 | ]
20 | ].forEach(test => {
21 | it(`should convert ${JSON.stringify(test[0])}`, function () {
22 | should(jsonToFormEncoded(test[0])).equal(test[1]);
23 | });
24 | });
25 | });
26 |
27 | describe('#parseJson', function () {
28 | [
29 | [
30 | {foo: 'bar'},
31 | {foo: 'bar'}
32 | ],
33 | [
34 | '{"foo": "bar"}',
35 | {foo: 'bar'}
36 | ],
37 | [
38 | undefined,
39 | {}
40 | ],
41 | [
42 | null,
43 | {}
44 | ],
45 | [
46 | '',
47 | {}
48 | ],
49 | [
50 | 'fooinvalid',
51 | {}
52 | ]
53 | ].forEach(test => {
54 | it(`should convert ${typeof test[0] === 'object' ? JSON.stringify(test[0]) : test[0]}`, function () {
55 | should.deepEqual(parseJSON(test[0]), (test[1]));
56 | });
57 | });
58 | });
59 |
60 | });
61 |
--------------------------------------------------------------------------------
/src/variables.less:
--------------------------------------------------------------------------------
1 | // Action/+ menu
2 | @ZINDEX_ACTION_OVERLAY: 999;
3 |
4 | // Set link destination button
5 | @ZINDEX_META_BUTTON: @ZINDEX_ACTION_OVERLAY - 2;
6 |
7 | // Loading indicator
8 | @ZINDEX_LOADING: 10000;
9 |
10 | @ZINDEX_MAX: 2147483647; /* signed 32bit maxint */
11 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var getPages = require('./npm_tasks/get-pages');
2 | var path = require('path');
3 |
4 | // Prep all entry points
5 | var entry = {};
6 | getPages().forEach(function (page) {
7 | entry[page] = './src/pages/' + page + '/' + page + '.jsx';
8 | });
9 |
10 | module.exports = {
11 | entry: entry,
12 | devtool: 'source-map', //not good for ff
13 | output: {
14 | path: __dirname + '/dest/js',
15 | filename: '[name].bundle.js'
16 | },
17 | module: {
18 | loaders: [
19 | {
20 | test: /\.js$/,
21 | loaders: ['babel-loader'],
22 | include: path.resolve(__dirname, 'src')
23 | },
24 | {
25 | test: /\.jsx$/,
26 | loaders: ['babel-loader', 'jsx-loader'],
27 | include: path.resolve(__dirname, 'src')
28 | },
29 | {
30 | test: /\.json$/,
31 | loader: 'json-loader',
32 | include: [path.resolve(__dirname, 'src'), path.resolve(__dirname, 'node_modules')]
33 | }
34 | ]
35 | }
36 | };
37 |
--------------------------------------------------------------------------------