├── .babelrc
├── .github
├── CONTRIBUTING.md
└── ISSUE_TEMPLATE.md
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── docker-compose.yml
├── index.html
├── package.json
├── scripts
├── publish.sh
└── upload.js
├── server.js
├── src
├── js
│ ├── __mocks__
│ │ ├── .eslintrc
│ │ ├── d3-force.js
│ │ └── global.js
│ ├── ace
│ │ ├── completer
│ │ │ └── datasets.js
│ │ ├── mode
│ │ │ ├── javascript.js
│ │ │ └── pgsql.js
│ │ └── theme
│ │ │ └── qri.js
│ ├── actions
│ │ ├── app.js
│ │ ├── collections.js
│ │ ├── communities.js
│ │ ├── consensus.js
│ │ ├── content.js
│ │ ├── coverage.js
│ │ ├── files.js
│ │ ├── group.js
│ │ ├── layout.js
│ │ ├── locals.js
│ │ ├── metadata.js
│ │ ├── primers.js
│ │ ├── search.js
│ │ ├── selection.js
│ │ ├── session.js
│ │ ├── source.js
│ │ ├── tasks.js
│ │ ├── uncrawlables.js
│ │ ├── url.js
│ │ └── user.js
│ ├── analytics.js
│ ├── components
│ │ ├── CodeEditor.js
│ │ ├── Collection.js
│ │ ├── Consensus.js
│ │ ├── Footer.js
│ │ ├── ForceGraph.js
│ │ ├── ForceGraphArrowLink.js
│ │ ├── ForceGraphLink.js
│ │ ├── ForceGraphNode.js
│ │ ├── InteractiveForceGraph.js
│ │ ├── List.js
│ │ ├── LoadingButton.js
│ │ ├── MainMenu.js
│ │ ├── Metadata.js
│ │ ├── Navbar.js
│ │ ├── NodeSummary.js
│ │ ├── NotFound.js
│ │ ├── PageHeader.js
│ │ ├── ProgressBar.js
│ │ ├── PureRenderComponent.js
│ │ ├── RotatingText.js
│ │ ├── SessionRequired.js
│ │ ├── Spinner.js
│ │ ├── StatsBar.js
│ │ ├── TabBar.js
│ │ ├── TreeGraph.js
│ │ ├── ZoomableSVGGroup.js
│ │ ├── __tests__
│ │ │ └── .eslintrc
│ │ ├── activities
│ │ │ ├── Activities.js
│ │ │ ├── Analyzing.js
│ │ │ ├── Harvesting.js
│ │ │ ├── Monitoring.js
│ │ │ └── Storing.js
│ │ ├── form
│ │ │ ├── Clock.js
│ │ │ ├── CollectionForm.js
│ │ │ ├── DateInput.js
│ │ │ ├── EditCollectionItem.js
│ │ │ ├── KeyValueForm.js
│ │ │ ├── KeyValueInput.js
│ │ │ ├── LanguageInput.js
│ │ │ ├── MetadataForm.js
│ │ │ ├── MonthCalendar.js
│ │ │ ├── SelectUser.js
│ │ │ ├── TagInput.js
│ │ │ ├── TaskForm.js
│ │ │ ├── TimePicker.js
│ │ │ ├── UrlInput.js
│ │ │ ├── ValidDateInput.js
│ │ │ ├── ValidDateTimeInput.js
│ │ │ ├── ValidInput.js
│ │ │ ├── ValidLicenseInput.js
│ │ │ ├── ValidSelect.js
│ │ │ └── ValidTextarea.js
│ │ └── item
│ │ │ ├── CollectionItem.js
│ │ │ ├── CollectionItemItem.js
│ │ │ ├── ContentItem.js
│ │ │ ├── HistoryItem.js
│ │ │ ├── MetadataItem.js
│ │ │ ├── PrimerItem.js
│ │ │ ├── SearchResultItem.js
│ │ │ ├── SnapshotItem.js
│ │ │ ├── SourceItem.js
│ │ │ ├── TaskItem.js
│ │ │ ├── UncrawlableItem.js
│ │ │ ├── UrlItem.js
│ │ │ └── UserItem.js
│ ├── containers
│ │ ├── App.js
│ │ ├── ArchiveUrl.js
│ │ ├── Collection.js
│ │ ├── CollectionItems.js
│ │ ├── Collections.js
│ │ ├── Communities.js
│ │ ├── Content.js
│ │ ├── Coverage.js
│ │ ├── DevTools.js
│ │ ├── FileEditor.js
│ │ ├── Keys.js
│ │ ├── Login.js
│ │ ├── MetadataEditor.js
│ │ ├── Primer.js
│ │ ├── Primers.js
│ │ ├── PublicRecord.js
│ │ ├── Root.dev.js
│ │ ├── Root.js
│ │ ├── Root.prod.js
│ │ ├── Signup.js
│ │ ├── Source.js
│ │ ├── Stylesheet.js
│ │ ├── Tasks.js
│ │ ├── Uncrawlable.js
│ │ ├── Uncrawlables.js
│ │ ├── Url.js
│ │ ├── User.js
│ │ ├── UserSearch.js
│ │ └── UserSettings.js
│ ├── highlight
│ │ └── qri.js
│ ├── index.js
│ ├── middleware
│ │ ├── api.js
│ │ ├── coverage.js
│ │ ├── locals.js
│ │ ├── socket.js
│ │ └── users.js
│ ├── propTypes
│ │ ├── link.js
│ │ ├── node.js
│ │ └── simulation.js
│ ├── reducers
│ │ ├── app.js
│ │ ├── coverage.js
│ │ ├── index.js
│ │ ├── layout.js
│ │ ├── locals.js
│ │ ├── paginate.js
│ │ ├── pagination.js
│ │ ├── selection.js
│ │ ├── session.js
│ │ └── urlStates.js
│ ├── routes.dev.js
│ ├── routes.js
│ ├── schemas.js
│ ├── selectors
│ │ ├── archiveServices.js
│ │ ├── collections.js
│ │ ├── consensus.js
│ │ ├── content.js
│ │ ├── coverage.js
│ │ ├── file.js
│ │ ├── format.js
│ │ ├── keys.js
│ │ ├── metadata.js
│ │ ├── primers.js
│ │ ├── search.js
│ │ ├── session.js
│ │ ├── sources.js
│ │ ├── tasks.js
│ │ ├── uncrawlables.js
│ │ ├── url.js
│ │ ├── user.js
│ │ └── viewConfig.js
│ ├── store
│ │ ├── configureStore.dev.js
│ │ ├── configureStore.js
│ │ └── configureStore.prod.js
│ ├── utils
│ │ ├── __mocks__
│ │ │ ├── .eslintrc
│ │ │ └── raf.js
│ │ ├── __tests__
│ │ │ ├── .eslintrc
│ │ │ ├── d3-force.test.js
│ │ │ ├── raf.test.js
│ │ │ └── sets-equal.test.js
│ │ ├── d3-force.js
│ │ ├── raf.js
│ │ ├── sets-equal.js
│ │ └── tree.js
│ └── validators
│ │ ├── index.js
│ │ ├── invite.js
│ │ ├── metadata.js
│ │ └── user.js
├── scss
│ ├── _alert.scss
│ ├── _buttons.scss
│ ├── _components.scss
│ ├── _containers.scss
│ ├── _forms.scss
│ ├── _grid.scss
│ ├── _hacks.scss
│ ├── _mixins.scss
│ ├── _modal.scss
│ ├── _normalize.scss
│ ├── _print.scss
│ ├── _reboot.scss
│ ├── _site.scss
│ ├── _tables.scss
│ ├── _type.scss
│ ├── _utilities.scss
│ ├── _variables.scss
│ ├── components
│ │ ├── _CodeEditor.scss
│ │ ├── _DateInput.scss
│ │ ├── _Footer.scss
│ │ ├── _Items.scss
│ │ ├── _MainMenu.scss
│ │ ├── _MonthCalendar.scss
│ │ ├── _Navbar.scss
│ │ ├── _ProgressBar.scss
│ │ ├── _Signup.scss
│ │ ├── _Spinner.scss
│ │ ├── _TabBar.scss
│ │ ├── _TabPanel.scss
│ │ ├── _forceGraph.scss
│ │ ├── _login.scss
│ │ └── _view.scss
│ ├── containers
│ │ ├── _App.scss
│ │ ├── _Collection.scss
│ │ ├── _Home.scss
│ │ └── _User.scss
│ ├── mixins
│ │ ├── _alert.scss
│ │ ├── _background-variant.scss
│ │ ├── _border-radius.scss
│ │ ├── _breakpoints.scss
│ │ ├── _buttons.scss
│ │ ├── _cards.scss
│ │ ├── _clearfix.scss
│ │ ├── _forms.scss
│ │ ├── _gradients.scss
│ │ ├── _grid-framework.scss
│ │ ├── _grid.scss
│ │ ├── _hover.scss
│ │ ├── _image.scss
│ │ ├── _list-group.scss
│ │ ├── _lists.scss
│ │ ├── _nav-divider.scss
│ │ ├── _navbar-align.scss
│ │ ├── _pagination.scss
│ │ ├── _progress.scss
│ │ ├── _pulls.scss
│ │ ├── _reset-filter.scss
│ │ ├── _reset-text.scss
│ │ ├── _resize.scss
│ │ ├── _screen-reader.scss
│ │ ├── _size.scss
│ │ ├── _tab-focus.scss
│ │ ├── _table-row.scss
│ │ ├── _tag.scss
│ │ ├── _text-emphasis.scss
│ │ ├── _text-hide.scss
│ │ └── _text-truncate.scss
│ ├── style.scss
│ └── utilities
│ │ ├── _background.scss
│ │ ├── _clearfix.scss
│ │ ├── _display.scss
│ │ ├── _flex.scss
│ │ ├── _pulls.scss
│ │ ├── _screenreaders.scss
│ │ ├── _spacing.scss
│ │ ├── _text.scss
│ │ └── _visibility.scss
└── server.js
├── webpack.config.dev.js
├── webpack.config.prod.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "stage-0"],
3 | "plugins": ["syntax-dynamic-import"],
4 | "env": {
5 | "development": {
6 | "presets": ["react-hmre"],
7 | "plugins" : ["syntax-dynamic-import"]
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | We love improvements to our tools! Take a moment to check out our organization-wide [Contributing Guidelines](https://github.com/datatogether/datatogether/blob/master/CONTRIBUTING.md) and [Code of Conduct](https://github.com/datatogether/datatogether/blob/master/CONDUCT.md).
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Hey there and thank you for submitting an issue!
2 |
3 | We are trying to keep these issues for feature requests and bug reports. Please
4 | do the checklist before filing an issue:
5 |
6 | - [ ] Is this a **bug report** (if so, is it something you can **debug and fix**? Send a pull request!)
7 | - [ ] feature request
8 | - [ ] support request => Please do not submit support request here, ask your question on [Slack](https://archivers-slack.herokuapp.com/).
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .sublime-project
3 | .sublime-workspace
4 | node_modules
5 | npm-debug.log
6 | dist/
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 | ADD . /home/context
3 | WORKDIR /home/context
4 | RUN npm install
5 | RUN npm rebuild node-sass
6 |
7 | ENTRYPOINT npm run develop
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Data Together
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "context_webapp",
3 | "private": true,
4 | "version": "0.0.1",
5 | "description": "react & redux single page webapp for datatogether.org",
6 | "main": "server.js",
7 | "devDependencies": {
8 | "aws-sdk": "^2.4.0",
9 | "babel-core": "6.23.0",
10 | "babel-loader": "^6.2.4",
11 | "babel-plugin-syntax-dynamic-import": "^6.18.0",
12 | "babel-polyfill": "^6.9.1",
13 | "babel-preset-es2015": "^6.9.0",
14 | "babel-preset-react": "^6.5.0",
15 | "babel-preset-react-app": "^2.1.0",
16 | "babel-preset-react-hmre": "^1.1.1",
17 | "babel-preset-stage-0": "^6.22.0",
18 | "brace": "^0.8.0",
19 | "concurrently": "^2.1.0",
20 | "css-loader": "^0.23.1",
21 | "d3": "^3.5.17",
22 | "d3-force": "^1.0.4",
23 | "d3-hierarchy": "^1.1.5",
24 | "enzyme": "^2.7.1",
25 | "express": "^4.13.4",
26 | "file-saver": "^1.3.2",
27 | "gulp": "^3.9.1",
28 | "gulp-sass": "^3.1.0",
29 | "highlight.js": "^9.7.0",
30 | "humps": "^1.1.0",
31 | "isomorphic-fetch": "^2.2.1",
32 | "jest": "^18.1.0",
33 | "json-loader": "^0.5.4",
34 | "lodash": "^4.13.1",
35 | "node-sass": "4.0.0",
36 | "normalizr": "^2.1.0",
37 | "react": "15.4.2",
38 | "react-addons-shallow-compare": "15.4.2",
39 | "react-addons-test-utils": "15.4.2",
40 | "react-dev-utils": "^0.5.0",
41 | "react-dom": "15.4.2",
42 | "react-markdown": "^2.4.4",
43 | "react-redux": "^4.4.5",
44 | "react-router": "3.0.2",
45 | "react-router-redux": "4.0.7",
46 | "react-syntax-highlighter": "^2.10.0",
47 | "redux": "^3.5.2",
48 | "redux-devtools": "^3.3.1",
49 | "redux-devtools-dock-monitor": "^1.1.1",
50 | "redux-devtools-log-monitor": "^1.0.11",
51 | "redux-logger": "^2.6.1",
52 | "redux-thunk": "^2.1.0",
53 | "sass": "^0.5.0",
54 | "sass-loader": "6.0.0",
55 | "sinon": "^1.17.7",
56 | "style-loader": "^0.13.1",
57 | "webpack": "2.2.1",
58 | "webpack-dev-middleware": "^1.6.1",
59 | "webpack-hot-middleware": "^2.10.0"
60 | },
61 | "scripts": {
62 | "develop": "NODE_ENV=development node server.js",
63 | "test": "./node_modules/.bin/jest",
64 | "build": "NODE_ENV=production ./node_modules/.bin/webpack --progress --config webpack.config.prod",
65 | "build:staging": "NODE_ENV=development ./node_modules/.bin/webpack --progress --config webpack.config.staging",
66 | "publish": "./scripts/publish.sh"
67 | },
68 | "repository": {
69 | "type": "git",
70 | "url": "github.com/datatogether/context"
71 | },
72 | "author": "Brendan O'Brien",
73 | "license": "AGPL 3.0",
74 | "dependencies": {
75 | "brace": "^0.8.0",
76 | "d3-hierarchy": "^1.1.4"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/scripts/publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | PWD=$(pwd)
4 | SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
5 | cd "$SCRIPT_DIR/../"
6 |
7 | # npm test -- -R min || { exit 1; }
8 | npm run build || { exit 1; }
9 | node scripts/upload || { exit 1; }
10 |
11 | cd "${DIR}"
--------------------------------------------------------------------------------
/scripts/upload.js:
--------------------------------------------------------------------------------
1 | var AWS = require('aws-sdk')
2 | , path = require('path')
3 | , fs = require('fs')
4 | , s3 = new AWS.S3();
5 |
6 | fs.readdir(path.join(__dirname, '..', 'dist'), function (err, files){
7 | if (err) throw err;
8 | var count = files.length;
9 |
10 | function done() {
11 | if (--count === 0) process.exit();
12 | }
13 |
14 | files.forEach(function(fileName) {
15 | fs.readFile(path.join(__dirname,'..','dist', fileName), function (err, data) {
16 | if (err) { throw err; }
17 |
18 | s3.putObject({
19 | Bucket: 'static.qri.io',
20 | ContentType : 'application/json',
21 | Key: 'js/archivers/co/' + fileName,
22 | ACL: 'public-read',
23 | Body: data
24 | }, function(err, resp){
25 | if (err) { throw err; }
26 | console.log(resp.ETag);
27 | done();
28 | });
29 | });
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var webpackDevMiddleware = require('webpack-dev-middleware');
3 | var webpackHotMiddleware = require('webpack-hot-middleware');
4 | var config = require('./webpack.config.dev');
5 |
6 | var app = new require('express')();
7 | var port = process.env.PORT || 4000;
8 |
9 | var compiler
10 | try {
11 | compiler = webpack(config);
12 | } catch (err) {
13 | console.log(err.message);
14 | process.exit();
15 | }
16 |
17 | app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
18 | app.use(webpackHotMiddleware(compiler, { path : '/__qri_io' }));
19 |
20 | app.use(function(req, res, next) {
21 | res.setHeader("Access-Control-Allow-Origin", "*");
22 | return next();
23 | });
24 |
25 | app.get("*", function(req, res) {
26 | res.sendFile(__dirname + '/index.html');
27 | });
28 |
29 | app.listen(port, function(error) {
30 | if (error) {
31 | console.error(error);
32 | } else {
33 | console.info("Webapp HMR server listening on port %s", port);
34 | }
35 | });
36 |
--------------------------------------------------------------------------------
/src/js/__mocks__/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "import/no-extraneous-dependencies": 0,
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/js/__mocks__/global.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | export const window = {
22 | requestAnimationFrame: jest.fn(),
23 | cancelAnimationFrame: jest.fn(),
24 | setRafSupport(newValue) {
25 | if (newValue) {
26 | this.requestAnimationFrame = jest.fn();
27 | this.cancelAnimationFrame = jest.fn();
28 | } else {
29 | delete this.requestAnimationFrame;
30 | delete this.cancelAnimationFrame;
31 | }
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/src/js/ace/completer/datasets.js:
--------------------------------------------------------------------------------
1 | // set window.datasets so that the completer can always map against it
2 | window.datasets || (window.datasets = [])
3 |
4 | export const datasetCompleter = {
5 | getCompletions: function(editor, session, pos, prefix, callback) {
6 | if (prefix.length === 0) { callback(null, []); return }
7 | // console.log(editor, session, pos, prefix);
8 | // wordList like [{"word":"flow","freq":24,"score":300,"flags":"bc","syllables":"1"}]
9 | // callback(null, wordList.map(function(ea) {
10 | // return {name: ea.word, value: ea.word, score: ea.score, meta: "rhyme"}
11 | // }));
12 | if (window.debug) {
13 | console.log(session, pos, prefix);
14 | }
15 |
16 | callback(null, window.datasets.map(function(ds) {
17 | return { name : ds.slug, value : ds.slug, score : 1, meta : "dataset" };
18 | }));
19 |
20 | // callback(null, []);
21 | }
22 | }
--------------------------------------------------------------------------------
/src/js/actions/app.js:
--------------------------------------------------------------------------------
1 | export const APP_TOGGLE_MENU = 'APP_TOGGLE_MENU';
2 | export function toggleMenu() {
3 | return {
4 | type: APP_TOGGLE_MENU,
5 | };
6 | }
7 |
8 | export const APP_HIDE_MENU = 'APP_HIDE_MENU';
9 | export function hideMenu() {
10 | return {
11 | type: APP_HIDE_MENU,
12 | };
13 | }
14 |
15 | export const APP_SHOW_MODAL = 'APP_SHOW_MODAL';
16 | export function showModal(name = "", element, data) {
17 | return {
18 | type: APP_SHOW_MODAL,
19 | modal: {
20 | name,
21 | element,
22 | data,
23 | },
24 | };
25 | }
26 |
27 | export const APP_HIDE_MODAL = 'APP_HIDE_MODAL';
28 | export function hideModal() {
29 | return {
30 | type: APP_HIDE_MODAL,
31 | };
32 | }
33 |
34 |
35 | export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE';
36 | export function setErrorMessage(message) {
37 | return {
38 | type: SET_ERROR_MESSAGE,
39 | message,
40 | };
41 | }
42 |
43 | export const RESET_ERROR_MESSAGE = 'RESET_ERROR_MESSAGE';
44 | // Resets the currently visible error message.
45 | export function resetErrorMessage() {
46 | return {
47 | type: RESET_ERROR_MESSAGE,
48 | };
49 | }
50 |
51 | export const SET_MESSAGE = 'SET_MESSAGE';
52 | export function setMessage(message) {
53 | return {
54 | type: SET_MESSAGE,
55 | message,
56 | };
57 | }
58 |
59 | export const RESET_MESSAGE = 'RESET_MESSAGE';
60 | export function resetMessage() {
61 | return {
62 | type: RESET_MESSAGE,
63 | };
64 | }
65 |
66 | // Remove a remote model from state.entities
67 | export const REMOVE_MODEL = "REMOVE_MODEL";
68 | export function removeModel(schema, id) {
69 | return {
70 | type: REMOVE_MODEL,
71 | schema,
72 | id,
73 | };
74 | }
75 |
--------------------------------------------------------------------------------
/src/js/actions/communities.js:
--------------------------------------------------------------------------------
1 | import { USERS_API } from '../middleware/users';
2 | import Schemas from '../schemas';
3 |
4 | import analytics from '../analytics';
5 | import { selectMetadata } from '../selectors/metadata';
6 |
7 | export const COMMUNITY_USERS_REQUEST = "COMMUNITY_USERS_REQUEST";
8 | export const COMMUNITY_USERS_SUCCESS = "COMMUNITY_USERS_SUCCESS";
9 | export const COMMUNITY_USERS_FAILURE = "COMMUNITY_USERS_FAILURE";
10 |
11 | export function fetchCommunityUsers(id, page = 1, pageSize = 25) {
12 | return ({
13 | [USERS_API]: {
14 | types: [COMMUNITY_USERS_REQUEST, COMMUNITY_USERS_SUCCESS, COMMUNITY_USERS_FAILURE],
15 | schema: Schemas.USER_ARRAY,
16 | endpoint: `/communities/users`,
17 | data: { id, page, pageSize },
18 | },
19 | id, page, pageSize,
20 | });
21 | }
22 |
23 | export function loadCommunityUsers(id, page = 1, pageSize = 25) {
24 | return (dispatch) => {
25 | // TODO - add pagination & pagination check
26 | return dispatch(fetchCommunityUsers(id, page, pageSize));
27 | };
28 | }
--------------------------------------------------------------------------------
/src/js/actions/consensus.js:
--------------------------------------------------------------------------------
1 | import { CALL_API } from '../middleware/api';
2 | import Schemas from '../schemas';
3 |
4 | // import { selectConsensus } from '../selectors/consensus';
5 |
6 | export const CONSENSUS_REQUEST = 'CONSENSUS_REQUEST';
7 | export const CONSENSUS_SUCCESS = 'CONSENSUS_SUCCESS';
8 | export const CONSENSUS_FAILURE = 'CONSENSUS_FAILURE';
9 |
10 | export function fetchConsensus(subject) {
11 | return {
12 | [CALL_API]: {
13 | types: [CONSENSUS_REQUEST, CONSENSUS_SUCCESS, CONSENSUS_FAILURE],
14 | endpoint: `/consensus?subject=${subject}`,
15 | schema: Schemas.CONSENSUS,
16 | data: { subject },
17 | },
18 | };
19 | }
20 |
21 | export function loadConsensus(subject) {
22 | return fetchConsensus(subject);
23 | // return (dispatch) => {
24 | // return dispatch(fetchConsensus(subject));
25 | // };
26 | }
27 |
--------------------------------------------------------------------------------
/src/js/actions/coverage.js:
--------------------------------------------------------------------------------
1 | import { COVERAGE_API } from '../middleware/coverage';
2 | import { selectNode } from '../selectors/coverage';
3 |
4 | export const COVERAGE_NODE_TOGGLE = 'COVERAGE_NODE_TOGGLE';
5 | export function toggleNode(id) {
6 | return {
7 | type: COVERAGE_NODE_TOGGLE,
8 | id,
9 | }
10 | }
11 |
12 | export const COVERAGE_NODE_REQUEST = 'COVERAGE_NODE_REQUEST';
13 | export const COVERAGE_NODE_SUCCESS = 'COVERAGE_NODE_SUCCESS';
14 | export const COVERAGE_NODE_FAILURE = 'COVERAGE_NODE_FAILURE';
15 |
16 | export function fetchNode(id) {
17 | return {
18 | [COVERAGE_API] : {
19 | types : [COVERAGE_NODE_REQUEST,COVERAGE_NODE_SUCCESS,COVERAGE_NODE_FAILURE],
20 | endpoint : `/tree/${id}`,
21 | data: { id },
22 | }
23 | }
24 | }
25 |
26 | export function loadNode(id) {
27 | return ((dispatch, getState) => {
28 | const node = selectNode(getState(), id)
29 | if (node && node.children && node.children.length == node.numChildren) {
30 | return null;
31 | } else if (node && node._children) {
32 | return dispatch(toggleNode(id));
33 | }
34 |
35 | return dispatch(fetchNode(id));
36 | });
37 | }
--------------------------------------------------------------------------------
/src/js/actions/group.js:
--------------------------------------------------------------------------------
1 | import { push } from 'react-router-redux';
2 | import { USERS_API } from '../middleware/users';
3 |
4 | import analytics from '../analytics';
5 | import Schemas from '../schemas';
6 | import { setMessage, resetMessage } from './app';
7 | import { updateLocalModel, editModel } from './locals';
8 | import { selectSessionUser } from '../selectors/session';
9 |
10 | export const GROUPS_FETCH_REQUEST = "GROUPS_FETCH_REQUEST";
11 | export const GROUPS_FETCH_SUCCESS = "GROUPS_FETCH_SUCCESS";
12 | export const GROUPS_FETCH_FAILURE = "GROUPS_FETCH_FAILURE";
13 |
14 | export function fetchGroups(page = 1, pageSize = 25) {
15 | return {
16 | [USERS_API]: {
17 | types: [GROUPS_FETCH_REQUEST, GROUPS_FETCH_SUCCESS, GROUPS_FETCH_FAILURE],
18 | schema: Schemas.GROUP,
19 | endpoint: '/groups',
20 | data: { page, pageSize },
21 | },
22 | };
23 | }
24 |
25 | export function loadGroups(page = 1, pageSize = 25) {
26 | return (dispatch) => {
27 | // TODO - add pagination & pagination check
28 | return dispatch(fetchGroups(page, pageSize));
29 | };
30 | }
31 |
32 | export const GROUP_FETCH_REQUEST = "GROUP_FETCH_REQUEST";
33 | export const GROUP_FETCH_SUCCESS = "GROUP_FETCH_SUCCESS";
34 | export const GROUP_FETCH_FAILURE = "GROUP_FETCH_FAILURE";
35 |
36 | export function fetchGroup(id = "") {
37 | return {
38 | [USERS_API]: {
39 | types: [GROUP_FETCH_REQUEST, GROUP_FETCH_SUCCESS, GROUP_FETCH_FAILURE],
40 | schema: Schemas.GROUP,
41 | endpoint: `/groups/${id}`,
42 | data: { id },
43 | },
44 | };
45 | }
46 |
47 | export function loadGroup(id = "") {
48 | return (dispatch) => {
49 | // TODO - check for local url copy via getState
50 | return dispatch(fetchGroup(id));
51 | };
52 | }
53 |
54 | export const GROUP_SAVE_REQUEST = "GROUP_SAVE_REQUEST";
55 | export const GROUP_SAVE_SUCCESS = "GROUP_SAVE_SUCCESS";
56 | export const GROUP_SAVE_FAILURE = "GROUP_SAVE_FAILURE";
57 |
58 | export function saveGroup(group) {
59 | return {
60 | [USERS_API]: {
61 | types : [GROUP_SAVE_REQUEST, GROUP_SAVE_SUCCESS, GROUP_SAVE_FAILURE],
62 | schema: Schemas.GROUP,
63 | method: group.id ? "PUT" : "POST",
64 | endpoint: `/groups`,
65 | data: group,
66 | },
67 | };
68 | }
69 |
70 | export const GROUP_DELETE_REQUEST = "GROUP_DELETE_REQUEST";
71 | export const GROUP_DELETE_SUCCESS = "GROUP_DELETE_SUCCESS";
72 | export const GROUP_DELETE_FAILURE = "GROUP_DELETE_FAILURE";
73 |
74 | export function deleteGroup(group) {
75 | return {
76 | [USERS_API]: {
77 | types : [GROUP_SAVE_REQUEST, GROUP_SAVE_SUCCESS, GROUP_SAVE_FAILURE],
78 | schema: Schemas.GROUP,
79 | method: group.id ? "PUT" : "POST",
80 | endpoint: `/groups`,
81 | data: group,
82 | },
83 | };
84 | }
--------------------------------------------------------------------------------
/src/js/actions/layout.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export const LAYOUT_RESIZE = 'LAYOUT_RESIZE';
4 | export function layoutResize(width, height) {
5 | return {
6 | type: LAYOUT_RESIZE,
7 | stage: { width, height },
8 | };
9 | }
10 |
11 | export const LAYOUT_SHOW_SIDEBAR = "LAYOUT_SHOW_SIDEBAR";
12 | export function showSidebar() {
13 | return {
14 | type: LAYOUT_SHOW_SIDEBAR,
15 | };
16 | }
17 |
18 | export const LAYOUT_HIDE_SIDEBAR = "LAYOUT_HIDE_SIDEBAR";
19 | export function hideSidebar() {
20 | return {
21 | type: LAYOUT_HIDE_SIDEBAR,
22 | };
23 | }
24 |
25 | export const LAYOUT_SET = "LAYOUT_SET";
26 | export function setLayout({ navbar = false, sidebar = false, commandbar = true }) {
27 | return {
28 | type: LAYOUT_SET,
29 | navbar: { navbar },
30 | sidebar: { sidebar },
31 | commandbar: { commandbar },
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/src/js/actions/locals.js:
--------------------------------------------------------------------------------
1 | import { LOCAL_ACTION, NEW_MODEL, UPDATE_MODEL, EDIT_MODEL, CLEAR_MODEL } from '../middleware/locals';
2 |
3 |
4 | export function newLocalModel(schema, type, attributes = {}) {
5 | return {
6 | [LOCAL_ACTION]: {
7 | method: NEW_MODEL,
8 | type,
9 | schema,
10 | attributes,
11 | },
12 | };
13 | }
14 |
15 | export function updateLocalModel(schema, type, attributes) {
16 | return {
17 | [LOCAL_ACTION]: {
18 | method: UPDATE_MODEL,
19 | type,
20 | schema,
21 | attributes,
22 | },
23 | };
24 | }
25 |
26 | export function editModel(schema, type, attributes) {
27 | return {
28 | [LOCAL_ACTION]: {
29 | method: EDIT_MODEL,
30 | type,
31 | schema,
32 | attributes,
33 | },
34 | };
35 | }
36 |
37 | export function removeLocalModel(schema, type, attributes) {
38 | return {
39 | [LOCAL_ACTION]: {
40 | method: CLEAR_MODEL,
41 | type,
42 | schema,
43 | attributes,
44 | },
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/src/js/actions/primers.js:
--------------------------------------------------------------------------------
1 | import { CALL_API } from '../middleware/api';
2 | import Schemas from '../schemas';
3 |
4 | export const PRIMERS_FETCH_REQUEST = "PRIMERS_FETCH_REQUEST";
5 | export const PRIMERS_FETCH_SUCCESS = "PRIMERS_FETCH_SUCCESS";
6 | export const PRIMERS_FETCH_FAILURE = "PRIMERS_FETCH_FAILURE";
7 |
8 | export function fetchPrimers(page = 1, pageSize = 25, baseOnly = false) {
9 | return {
10 | [CALL_API]: {
11 | types: [PRIMERS_FETCH_REQUEST, PRIMERS_FETCH_SUCCESS, PRIMERS_FETCH_FAILURE],
12 | schema: Schemas.PRIMER,
13 | endpoint: '/primers',
14 | data: { page, pageSize, baseOnly },
15 | },
16 | };
17 | }
18 |
19 | export function loadPrimers(page = 1, pageSize = 25, baseOnly = false) {
20 | return (dispatch) => {
21 | // TODO - add pagination & pagination check
22 | return dispatch(fetchPrimers(page, pageSize, baseOnly));
23 | };
24 | }
25 |
26 |
27 | export const PRIMER_FETCH_REQUEST = "PRIMER_FETCH_REQUEST";
28 | export const PRIMER_FETCH_SUCCESS = "PRIMER_FETCH_SUCCESS";
29 | export const PRIMER_FETCH_FAILURE = "PRIMER_FETCH_FAILURE";
30 |
31 | export function fetchPrimer(id = "") {
32 | return {
33 | [CALL_API]: {
34 | types: [PRIMER_FETCH_REQUEST, PRIMER_FETCH_SUCCESS, PRIMER_FETCH_FAILURE],
35 | schema: Schemas.PRIMER_ARRAY,
36 | endpoint: `/primers/${id}`,
37 | data: { id },
38 | },
39 | };
40 | }
41 |
42 | export function loadPrimer(id = "") {
43 | return (dispatch) => {
44 | // TODO - check for local url copy via getState
45 | return dispatch(fetchPrimer(id));
46 | };
47 | }
48 |
49 | export const PRIMER_SUBPRIMERS_REQUEST = "PRIMER_SUBPRIMERS_REQUEST";
50 | export const PRIMER_SUBPRIMERS_SUCCESS = "PRIMER_SUBPRIMERS_SUCCESS";
51 | export const PRIMER_SUBPRIMERS_FAILURE = "PRIMER_SUBPRIMERS_FAILURE";
52 |
53 | export function fetchSources(id = "") {
54 | return {
55 | [CALL_API]: {
56 | types: [PRIMER_SUBPRIMERS_REQUEST, PRIMER_SUBPRIMERS_SUCCESS, PRIMER_SUBPRIMERS_FAILURE],
57 | schema: Schemas.SUBPRIMER_ARRAY,
58 | endpoint: `/primers/${id}/subprimers`,
59 | data: { id },
60 | },
61 | };
62 | }
63 |
64 | export function loadSources(id = "") {
65 | return (dispatch) => {
66 | // TODO - check for local url copy via getState
67 | return dispatch(fetchSources(id));
68 | };
69 | }
70 |
--------------------------------------------------------------------------------
/src/js/actions/search.js:
--------------------------------------------------------------------------------
1 | import { CALL_API } from '../middleware/api';
2 | import Schemas from '../schemas';
3 |
4 | export const SEARCH_SET = "SEARCH_SET";
5 | export function setSearch(query = "") {
6 | return {
7 | type: SEARCH_SET,
8 | query,
9 | };
10 | }
11 |
12 | export const SEARCH_CLEAR = "SEARCH_CLEAR";
13 | export function clearSearch() {
14 | return { type: SEARCH_CLEAR };
15 | }
16 |
17 | export const SEARCH_REQUEST = "SEARCH_REQUEST";
18 | export const SEARCH_SUCCESS = "SEARCH_SUCCESS";
19 | export const SEARCH_FAILURE = "SEARCH_FAILURE";
20 |
21 | export function search(query = "") {
22 | if (!query || query.length < 3) {
23 | return setSearch(query);
24 | }
25 |
26 | return (dispatch) => {
27 | dispatch(setSearch(query));
28 |
29 | return dispatch({
30 | [CALL_API]: {
31 | types: [SEARCH_REQUEST, SEARCH_SUCCESS, SEARCH_FAILURE],
32 | schema: Schemas.SEARCH_RESULT_ARRAY,
33 | endpoint: "/search",
34 | data: {
35 | query,
36 | page: 1,
37 | },
38 | },
39 | });
40 | };
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/src/js/actions/selection.js:
--------------------------------------------------------------------------------
1 | /*
2 | Selections are "highlights", or placing focus on an object or set of objects
3 | */
4 |
5 | // a list of constants for all types of things that can be selected
6 | export const selectionTypes = {
7 | NODE: "NODE",
8 | };
9 |
10 | export const SELECT = "SELECT";
11 |
12 | export function select(type, value) {
13 | return {
14 | type: SELECT,
15 | selection: {
16 | type,
17 | value,
18 | },
19 | }
20 | }
21 |
22 | export const DESELECT = "DESELECT";
23 |
24 | export function deselect() {
25 | return {
26 | type: DESELECT,
27 | }
28 | }
--------------------------------------------------------------------------------
/src/js/analytics.js:
--------------------------------------------------------------------------------
1 | /* global window, __BUILD__ */
2 |
3 |
4 | // mock analytics package when not in production
5 | if (!__BUILD__.PRODUCTION || __BUILD__.SEGMENT_KEY == "") {
6 | const noop = () => {};
7 | window.analytics = {
8 | track: noop,
9 | identify: noop,
10 | page: noop,
11 | group: noop,
12 | alias: noop,
13 | ready: noop,
14 | };
15 | }
16 |
17 | const analytics = window.analytics;
18 | const appProps = JSON.parse(__BUILD__.SEGMENT_APP_PROPERTIES);
19 |
20 | // analytics shim to inject global values
21 | // further reading: https://segment.com/docs/sources/website/analytics.js
22 | export default {
23 | track(event, properties, options, callback) {
24 | return analytics.track(event, Object.assign(appProps, properties), options, callback);
25 | },
26 | identify(userId, traits, options, callback) {
27 | return analytics.identify(userId, traits, options, callback);
28 | },
29 | page(category, name, properties, options, callback) {
30 | return analytics.page(category, name, Object.assign(appProps, properties), options, callback);
31 | },
32 | group(groupId, traits, options, callback) {
33 | return analytics.group(groupId, Object.assign(appProps, traits), options, callback);
34 | },
35 | alias(userId, previousId, options, callback) {
36 | return analytics.alias(userId, previousId, options, callback);
37 | },
38 | ready(callback) {
39 | return analytics.ready(callback);
40 | },
41 | };
42 |
--------------------------------------------------------------------------------
/src/js/components/Collection.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import List from './List';
5 |
6 | const Collection = ({ data, sessionKeyId, onEdit, onDelete, onArchive }) => {
7 | const collection = data;
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
Delete
16 |
Edit
17 | {sessionKeyId &&
Archive }
18 |
Collection
19 |
{collection.title}
20 |
{collection.description}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | hash
32 | url
33 | description
34 |
35 |
36 |
37 | {collection.contents.map((data, i) => {
38 | const hash = data[0];
39 | const url = data[1];
40 | const description = data[2];
41 |
42 | return (
43 |
44 | {/*{ hash && {hash} } */}
45 | { hash && {hash}
}
46 | {url}
47 | {description}
48 |
49 | );
50 | })}
51 |
52 |
53 |
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | Collection.propTypes = {
61 | data: PropTypes.object.isRequired,
62 | sessionKeyId: PropTypes.string,
63 | };
64 |
65 | export default Collection;
66 |
--------------------------------------------------------------------------------
/src/js/components/Consensus.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | export default class Consensus extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | this.state = {};
7 |
8 | [
9 | "handleKeyClick",
10 | ].forEach((m) => { this[m] = this[m].bind(this); });
11 | }
12 |
13 |
14 | handleKeyClick(key) {
15 | this.setState({
16 | [key]: !this.state[key],
17 | });
18 | }
19 |
20 | renderAllKeys(key) {
21 | if (!key) { return undefined; }
22 | return (
23 |
24 | {key.map((val, i) => {
25 | return
{val || blank }
;
26 | })}
27 |
28 | );
29 | }
30 |
31 | render() {
32 | const { data } = this.props;
33 |
34 | return (
35 |
36 | {Object.keys(data).map((key) => {
37 | return (
38 |
39 |
{key} {data[key].length > 1 ? data[key].length : "" }
40 | { this.state[key] ? this.renderAllKeys(data[key]) :
{data[key][0] ? data[key][0] : blank }
}
41 |
42 | );
43 | })}
44 |
45 | );
46 | }
47 | }
48 |
49 | Consensus.propTypes = {
50 | data: PropTypes.object.isRequired,
51 | };
52 |
53 | Consensus.defaultProps = {
54 | onClickKey: () => {},
55 | };
56 |
57 |
--------------------------------------------------------------------------------
/src/js/components/Footer.js:
--------------------------------------------------------------------------------
1 | /* globals __BUILD__ */
2 | import React, { PropTypes } from 'react';
3 | import { Link } from 'react-router';
4 |
5 | const Footer = ({ style }) => {
6 | return (
7 |
16 | );
17 | };
18 |
19 | Footer.propTypes = {
20 | };
21 |
22 | Footer.defaultProps = {
23 | };
24 |
25 | export default Footer;
26 |
--------------------------------------------------------------------------------
/src/js/components/ForceGraphLink.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import React from 'react';
22 |
23 | import PureRenderComponent from './PureRenderComponent';
24 | import linkPropTypes from '../propTypes/link';
25 |
26 | export default class ForceGraphLink extends PureRenderComponent {
27 | static get propTypes() {
28 | return {
29 | link: linkPropTypes.isRequired,
30 | };
31 | }
32 |
33 | static get defaultProps() {
34 | return {
35 | className: '',
36 | opacity: 0.6,
37 | stroke: '#999',
38 | };
39 | }
40 |
41 | render() {
42 | const { link, strokeWidth, className, ...spreadable } = this.props;
43 |
44 | return (
45 |
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/js/components/ForceGraphNode.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import React, { PropTypes } from 'react';
22 |
23 | import PureRenderComponent from './PureRenderComponent';
24 | import nodePropTypes from '../propTypes/node';
25 |
26 | export default class ForceGraphNode extends PureRenderComponent {
27 | static get propTypes() {
28 | return {
29 | node: nodePropTypes.isRequired,
30 | cx: PropTypes.number,
31 | cy: PropTypes.number,
32 | // these props only have an impact on the parent.
33 | labelStyle: PropTypes.object,
34 | labelClass: PropTypes.string,
35 | showLabel: PropTypes.bool,
36 | };
37 | }
38 |
39 | static get defaultProps() {
40 | return {
41 | className: '',
42 | fill: '#333',
43 | opacity: 1,
44 | stroke: '#FFF',
45 | strokeWidth: 1.5,
46 | };
47 | }
48 |
49 | render() {
50 | const {
51 | node, className, r,
52 | /* eslint-disable no-unused-vars */
53 | labelStyle, labelClass, showLabel,
54 | /* eslint-enable no-unused-vars */
55 | ...spreadable
56 | } = this.props;
57 |
58 | const { radius = 5 } = node;
59 |
60 | // return (
61 | //
62 | //
66 | // { node.id || node.label }
67 | //
68 | // );
69 |
70 | return ( );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/js/components/List.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | const List = (props) => {
4 | // This strange props destructuring is so props.component
5 | // can be referenced as a jsx component below
6 | const { data, onSelectItem, className } = props;
7 | const selectFunc = (fn, d, i) => {
8 | return () => {
9 | if (fn) {
10 | fn(i, d);
11 | }
12 | };
13 | };
14 |
15 | return (
16 |
17 | {data.map((item, i) =>
)}
18 |
19 | );
20 | };
21 |
22 | List.propTypes = {
23 | data: PropTypes.array,
24 | // eslint-disable-next-line react/no-unused-prop-types
25 | component: PropTypes.func.isRequired,
26 | onSelectItem: PropTypes.func,
27 | };
28 |
29 | List.defaultProps = {
30 | data: [],
31 | className: "list",
32 | };
33 |
34 | export default List;
35 |
--------------------------------------------------------------------------------
/src/js/components/LoadingButton.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | const LoadingButton = () => {
4 | return (
5 |
6 | );
7 | };
8 |
9 | LoadingButton.propTypes = {
10 | loading: PropTypes.bool,
11 | };
12 |
13 | LoadingButton.defaultProps = {
14 | };
15 |
16 | export default LoadingButton;
17 |
--------------------------------------------------------------------------------
/src/js/components/MainMenu.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import { Link } from 'react-router'
3 |
4 | export default class MainMenu extends React.Component {
5 | onClick (e) {
6 | // need to prevent any clicks on mainmenu from bubbling up
7 | // & hiding the menu
8 | e.stopPropagation()
9 | }
10 |
11 | userLinks (user) {
12 | if (!user) {
13 | return Login
14 | }
15 |
16 | return (
17 |
18 |
{user.username}
19 |
Docs
20 |
21 | )
22 | }
23 |
24 | render () {
25 | const { user, show } = this.props
26 | return (
27 |
28 |
Home
29 |
Add a Dataset
30 |
Communities
31 |
Collections
32 | {
33 | !user &&
34 | (
35 | Login
36 | Signup
37 |
)
38 | }
39 |
40 | )
41 | }
42 | }
43 |
44 | MainMenu.propTypes = {
45 | user: PropTypes.oneOfType([
46 | PropTypes.object,
47 | PropTypes.null]),
48 | show: PropTypes.bool
49 | }
50 |
51 | MainMenu.defaultProps = {
52 | }
53 |
--------------------------------------------------------------------------------
/src/js/components/Metadata.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | // order of special keys. most important key is **LAST**
4 | const keys = ["title", "description", "theme", "keyword"];
5 |
6 | // sortPriorityKeys returns a function to be used with Array.prototype.sort()
7 | // it accepts an array of items to prioritize over natural order comparison.
8 | // aka: let's you pull stuff out of an alphabetical list to put at the top.
9 | function priorityCompare(list) {
10 | list = list.reverse();
11 | return (a,b) => {
12 | const ai = list.findIndex((k) => k == a);
13 | const bi = list.findIndex((k) => k == b);
14 |
15 | // if neither is a priority string, regular comparison
16 | if (ai == -1 && bi == -1) {
17 | const nameA = a.toLowerCase(); // ignore upper and lowercase
18 | const nameB = b.toLowerCase(); // ignore upper and lowercase
19 | if (nameA < nameB) {
20 | return -1;
21 | }
22 | if (nameA > nameB) {
23 | return 1;
24 | }
25 | } else if (ai > bi) {
26 | return -1;
27 | } else if (ai < bi) {
28 | return 1;
29 | }
30 |
31 | // equal
32 | return 0;
33 | }
34 | }
35 |
36 | // Metadata is a static metadata viewer
37 | const Metadata = ({ metadata }) => {
38 | return (
39 |
40 | {Object.keys(metadata).sort(priorityCompare(keys)).map((key) => {
41 | return (
42 |
43 |
{key}
44 |
{metadata[key]}
45 |
46 | );
47 | })}
48 |
49 | );
50 | };
51 |
52 | Metadata.propTypes = {
53 | metadata: PropTypes.object,
54 | };
55 |
56 | Metadata.defaultProps = {
57 | };
58 |
59 | export default Metadata;
60 |
61 |
--------------------------------------------------------------------------------
/src/js/components/Navbar.js:
--------------------------------------------------------------------------------
1 | /* globals __BUILD__ */
2 | import React, { PropTypes } from 'react';
3 | import { Link } from 'react-router';
4 |
5 | const Navbar = ({ user, style, onToggleMenu }) => {
6 | return (
7 |
8 |
9 |
10 |
11 | [ data, together ]
12 |
13 |
14 | {user &&
{user.username}}
15 |
Menu
16 |
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | Navbar.propTypes = {
24 | user: PropTypes.oneOfType([
25 | React.PropTypes.object,
26 | React.PropTypes.null]),
27 | onToggleMenu: PropTypes.func.isRequired,
28 | };
29 |
30 | Navbar.defaultProps = {
31 | };
32 |
33 | export default Navbar;
34 |
--------------------------------------------------------------------------------
/src/js/components/NodeSummary.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import ProgressBar from './ProgressBar';
5 |
6 | // TODO - remove this badness
7 | import { archiveService } from '../selectors/archiveServices';
8 | import { completionColor } from '../selectors/coverage';
9 |
10 | export default class NodeDetails extends React.Component {
11 | render() {
12 | const { node, path } = this.props;
13 | if (!node) {
14 | return null;
15 | }
16 |
17 | return (
18 |
19 |
20 |
{node.name}
21 |
{node.numLeavesArchived || 0} / {node.numLeaves} | {Math.floor((node.numLeavesArchived / node.numLeaves || 0.001)*100)}% complete
22 |
23 | {node.coverage &&
Coverage: }
24 |
25 | {node.coverage && node.coverage.map((c, i) => {
26 | let s = archiveService(c.serviceId);
27 | if (!s) { return null; }
28 | return (
29 |
30 |
{i}. {s.name}
31 | {c.archiveUrl &&
link }
32 |
33 | );
34 | })}
35 |
36 |
37 | );
38 | }
39 | }
40 |
41 | NodeDetails.propTypes = {
42 | node: PropTypes.object,
43 | }
44 |
45 | NodeDetails.defaultProps = {
46 |
47 | }
--------------------------------------------------------------------------------
/src/js/components/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotFound = () => {
4 | return (
5 |
6 |
7 |
Not Found.
8 |
9 |
10 | );
11 | };
12 |
13 | export default NotFound;
14 |
--------------------------------------------------------------------------------
/src/js/components/PageHeader.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 |
4 | const PageHeader = ({ label, title, description }) => {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 | {label &&
{label} }
12 | {title &&
{title} }
13 | {description &&
{description}
}
14 |
15 |
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | PageHeader.propTypes = {
23 | label: PropTypes.string,
24 | title: PropTypes.string,
25 | description: PropTypes.string,
26 | };
27 |
28 | PageHeader.defaultProps = {
29 | };
30 |
31 | export default PageHeader;
32 |
--------------------------------------------------------------------------------
/src/js/components/ProgressBar.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | const ProgressBar = ({ total, progress, size, backgroundColor }) => {
4 | const width = ((progress / (total || 0.001)) * 100);
5 | return (
6 |
9 | );
10 | };
11 |
12 | ProgressBar.propTypes = {
13 | size: PropTypes.string,
14 | total: PropTypes.number.isRequired,
15 | progress: PropTypes.number.isRequired,
16 | backgroundColor: PropTypes.string,
17 | };
18 |
19 | ProgressBar.defaultProps = {
20 | total: 100,
21 | progress: 0,
22 | size: "standard",
23 | backgroundColor: "#8888888"
24 | };
25 |
26 |
27 | export default ProgressBar;
28 |
--------------------------------------------------------------------------------
/src/js/components/PureRenderComponent.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import { Component } from 'react';
22 | import shallowCompare from 'react-addons-shallow-compare';
23 |
24 | export default class PureRenderComponent extends Component {
25 | shouldComponentUpdate(nextProps, nextState) {
26 | return shallowCompare(this, nextProps, nextState);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/js/components/RotatingText.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 |
4 | export default class RotatingText extends React.Component {
5 | constructor(props) {
6 | super(props);
7 |
8 | this.state = {
9 | index: 0,
10 | fadeState: "display",
11 | };
12 |
13 | [
14 | "handleStartFade",
15 | "handleStopFade",
16 | ].forEach((m) => this[m] = this[m].bind(this));
17 | }
18 |
19 | componentWillMount() {
20 | this.timer = setInterval(this.handleStartFade, this.props.displayTime + this.props.transitionTime);
21 | }
22 |
23 | componentWillUnmount() {
24 | clearInterval(this.timer);
25 | }
26 |
27 | componentWillReceiveProps(nextProps) {
28 | if (nextProps.displayTime != this.props.displayTime) {
29 | this.timer = setInterval(this.handleStartFade, this.props.displayTime + this.props.transitionTime);
30 | }
31 | }
32 |
33 | handleStartFade() {
34 | const { index } = this.state;
35 | this.setState({ fadeState: "fade-out" });
36 |
37 | setTimeout(() => {
38 | this.setState({
39 | index: (index + 1 == this.props.text.length) ? 0 : index + 1,
40 | fadeState: "fade-in",
41 | });
42 | setTimeout(this.handleStopFade, this.props.transitionTime / 2);
43 | }, this.props.transitionTime / 2);
44 | }
45 |
46 | handleStopFade() {
47 | this.setState({ fadeState: "display" });
48 | }
49 |
50 | style(props, fadeState) {
51 | return {
52 | color: props.color,
53 | minWidth: props.minWidth,
54 | transition : `all ${this.props.transitionTime / 1000}s`,
55 | opacity: (fadeState == "fade-out") ? 0 : 1,
56 | }
57 | }
58 |
59 | render() {
60 | const { text } = this.props;
61 | const { index, fadeState } = this.state;
62 |
63 | return (
64 |
65 | {text[index]}
66 |
67 | );
68 | }
69 | }
70 |
71 | RotatingText.propTypes = {
72 | text: PropTypes.array.isRequired,
73 | minWidth: PropTypes.number,
74 | displayTime: PropTypes.number,
75 | transitionTime: PropTypes.number,
76 | color: PropTypes.string,
77 | };
78 |
79 | RotatingText.defaultProps = {
80 | minWidth: 200,
81 | displayTime: 1000,
82 | transitionTime: 500,
83 | color: "#FFFFFF",
84 | }
85 |
--------------------------------------------------------------------------------
/src/js/components/SessionRequired.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const SessionRequired = () => {
4 | return (
5 |
6 |
7 |
8 |
You must be logged-in to do this
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default SessionRequired;
16 |
--------------------------------------------------------------------------------
/src/js/components/Spinner.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Spinner = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default Spinner;
16 |
--------------------------------------------------------------------------------
/src/js/components/StatsBar.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | const StatsBar = ({ stats }) => {
4 | return (
5 |
6 | {Object.keys(stats).map((key, i) => {
7 | return (
8 |
9 | {key}
10 | {stats[key]}
11 |
12 | );
13 | })}
14 |
15 | );
16 | };
17 |
18 | StatsBar.propTypes = {
19 | stats: PropTypes.object.isRequired,
20 | };
21 |
22 | export default StatsBar;
23 |
--------------------------------------------------------------------------------
/src/js/components/TabBar.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | const TabBar = ({ tabs, value, onChange, color }) => {
4 | const handleClick = (tab) => {
5 | return () => {
6 | onChange(tab);
7 | };
8 | };
9 |
10 | return (
11 |
12 |
13 |
14 | {tabs.map((tab, i) => {
15 | return (
16 |
{tab}
21 | );
22 | })}
23 |
24 |
25 |
26 | );
27 | };
28 |
29 | TabBar.propTypes = {
30 | color: PropTypes.string,
31 | tabs: PropTypes.array.isRequired,
32 | value: PropTypes.string.isRequired,
33 | onChange: PropTypes.func.isRequired,
34 | };
35 |
36 | TabBar.defaultProps = {
37 | color: "",
38 | };
39 |
40 | export default TabBar;
41 |
--------------------------------------------------------------------------------
/src/js/components/__tests__/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "import/no-extraneous-dependencies": 0,
4 | "no-underscore-dangle": 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/js/components/activities/Activities.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import PageHeader from '../PageHeader';
5 |
6 | const Activities = ({ label, title, description }) => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
Harvesting
14 |
15 | Monitoring
16 |
17 | Storing
18 |
19 | Analyzing
20 |
21 |
22 |
23 |
24 |
25 | );
26 | };
27 |
28 | Activities.propTypes = {
29 | };
30 |
31 | Activities.defaultProps = {
32 | };
33 |
34 | export default Activities;
35 |
--------------------------------------------------------------------------------
/src/js/components/activities/Analyzing.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import PageHeader from '../PageHeader';
5 |
6 | const Analyzing = ({ label, title, description }) => {
7 | return (
8 |
13 | );
14 | };
15 |
16 | Analyzing.propTypes = {
17 | };
18 |
19 | Analyzing.defaultProps = {
20 | };
21 |
22 | export default Analyzing;
23 |
--------------------------------------------------------------------------------
/src/js/components/activities/Harvesting.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import PageHeader from '../PageHeader';
5 |
6 | const Harvesting = ({ label, title, description }) => {
7 | return (
8 |
13 | );
14 | };
15 |
16 | Harvesting.propTypes = {
17 | };
18 |
19 | Harvesting.defaultProps = {
20 | };
21 |
22 | export default Harvesting;
23 |
--------------------------------------------------------------------------------
/src/js/components/activities/Monitoring.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import PageHeader from '../PageHeader';
5 |
6 | const Monitoring = ({ label, title, description }) => {
7 | return (
8 |
13 | );
14 | };
15 |
16 | Monitoring.propTypes = {
17 | };
18 |
19 | Monitoring.defaultProps = {
20 | };
21 |
22 | export default Monitoring;
23 |
--------------------------------------------------------------------------------
/src/js/components/activities/Storing.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import PageHeader from '../PageHeader';
5 |
6 | const Storing = ({ label, title, description }) => {
7 | return (
8 |
13 | );
14 | };
15 |
16 | Storing.propTypes = {
17 | };
18 |
19 | Storing.defaultProps = {
20 | };
21 |
22 | export default Storing;
23 |
--------------------------------------------------------------------------------
/src/js/components/form/CollectionForm.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | import List from '../List';
4 | import ValidInput from './ValidInput';
5 | import SelectUser from './SelectUser';
6 | import ValidTextarea from './ValidTextarea';
7 |
8 | const CollectionForm = ({ name, data, users, onChange, onCancel, onSubmit }) => {
9 | const collection = data;
10 | const handleChange = (name, value) => {
11 | onChange(name, Object.assign({}, data, { [name] : value }));
12 | }
13 | const handleSubmit = (e) => {
14 | e.preventDefault();
15 | onSubmit(collection, e);
16 | };
17 |
18 | const handleItemChange = (index, item) => {
19 | let contents = collection.contents.concat()
20 | contents.splice(index,1,item);
21 |
22 | onChange(name, Object.assign({}, collection, { contents }));
23 | }
24 |
25 | const handleItemDelete = (index) => {
26 | // onChange(name, data);
27 | }
28 |
29 | const handleAddItem = (e) => {
30 | e.preventDefault();
31 | let contents = collection.contents || [];
32 | onChange(name,Object.assign({}, collection, {
33 | contents: contents.concat([
34 | { url : "", description: "", index: -1 }
35 | ]),
36 | }));
37 | }
38 |
39 | return (
40 |
41 |
42 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | Cancel
55 |
56 |
57 |
58 |
59 |
60 | );
61 | };
62 |
63 | CollectionForm.propTypes = {
64 | data: PropTypes.object.isRequired,
65 | name: PropTypes.string,
66 | onChange: PropTypes.func.isRequired,
67 | onCancel: PropTypes.func.isRequired,
68 | onSubmit: PropTypes.func.isRequired,
69 | };
70 |
71 | CollectionForm.defaultProps = {
72 | name : "collection",
73 | }
74 |
75 | export default CollectionForm;
76 |
--------------------------------------------------------------------------------
/src/js/components/form/EditCollectionItem.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import ValidInput from './ValidInput';
5 | import ValidTextarea from './ValidTextarea';
6 |
7 | const EditCollectionItem = ({data, index, onChange, onDelete}) => {
8 | const handleChange = (name, value) => {
9 | onChange(index, Object.assign({}, data, { [name] : value }));
10 | }
11 |
12 | const handleDelete = (e) => {
13 | onDelete(index, data);
14 | }
15 |
16 | return (
17 |
18 | delete
19 |
20 | {data.id ? {item.url} : }
21 |
22 |
23 | );
24 | }
25 |
26 | EditCollectionItem.propTypes = {
27 |
28 | }
29 |
30 | export default EditCollectionItem;
--------------------------------------------------------------------------------
/src/js/components/form/KeyValueForm.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | import KeyValueInput from './KeyValueInput';
4 |
5 | export default class KeyValueForm extends React.Component {
6 | constructor(props) {
7 | super(props);
8 |
9 | [
10 | "handleAddField",
11 | "handleRenameField",
12 | "handleRemoveField",
13 | "handleSubmit",
14 | ].forEach((m) => { this[m] = this[m].bind(this); });
15 | }
16 |
17 | handleAddField() {
18 | const change = { meta: Object.assign({}, this.props.data.meta, { new_field: "" }) };
19 | // this.props.updateMetadata({ meta: Object.assign({}, this.props.data, change) });
20 | }
21 |
22 | handleRenameField(prevName, newName) {
23 | const change = { meta: Object.assign({}, this.props.data.meta, { [newName]: this.props.data.meta[prevName] }) };
24 | delete change.meta[prevName];
25 | // this.props.updateMetadata(Object.assign({}, this.props.data, change));
26 | }
27 |
28 | handleRemoveField(field) {
29 | const change = { meta: Object.assign({}, this.props.data.meta) };
30 | delete change.meta[field];
31 | // this.props.updateMetadata(Object.assign({}, this.props.data, change));
32 | }
33 |
34 | handleSubmit(e) {
35 | e.preventDefault();
36 | onSubmit(data, e);
37 | }
38 |
39 | render() {
40 | const { data, onChangeValue, onCancel, onSubmit, onChangeKey, onAddField, onRemove } = this.props;
41 |
42 |
43 | return (
44 |
45 |
46 | {Object.keys(data).map((key, i) => {
47 | return (
48 | );
56 | })}
57 | Add Field
58 |
59 |
60 | );
61 | }
62 | }
63 |
64 | KeyValueForm.propTypes = {
65 | data: PropTypes.object.isRequired,
66 | validation: PropTypes.object,
67 |
68 | onChange: PropTypes.func.isRequired,
69 | onCancel: PropTypes.func.isRequired,
70 | onSubmit: PropTypes.func.isRequired,
71 | };
72 |
--------------------------------------------------------------------------------
/src/js/components/form/KeyValueInput.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | import ValidInput from './ValidInput';
4 | import ValidTextarea from './ValidTextarea';
5 |
6 | const KeyValueInput = ({ name, value, onChangeValue, onChangeKey, onRemove }) => {
7 | const rename = (prevName, _, newName) => {
8 | onChangeKey(prevName, newName);
9 | };
10 |
11 | return (
12 |
17 | );
18 | };
19 |
20 | export default KeyValueInput;
21 |
--------------------------------------------------------------------------------
/src/js/components/form/LanguageInput.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | const LanguageInput = (props) => {
4 | const { label, name, showError, error, value, onChange, helpText, showHelpText } = props;
5 | return (
6 |
7 | {label &&
{label} }
8 |
{ onChange(name, e.target.value, e); }}>
9 | -unknown-
10 | arabic
11 | bengali
12 | chinese
13 | english
14 | french
15 | german
16 | hindi
17 | japanese
18 | javanese
19 | korean
20 | lahanda
21 | portuguese
22 | russian
23 | spanish
24 |
25 | {(error != "" && showError) ?
{error}
: undefined}
26 | {(helpText && showHelpText) &&
{helpText} }
27 |
28 | );
29 | };
30 |
31 | LanguageInput.propTypes = {
32 | // required name for the field
33 | name: PropTypes.string.isRequired,
34 | // if provided it'll create a label element to accompany the field
35 | label: PropTypes.string,
36 | // value to display in the field
37 | value: PropTypes.string.isRequired,
38 | // an error message to displacy
39 | error: PropTypes.string,
40 | // weather or not to actually display any passed-in errors
41 | showError: PropTypes.bool,
42 | // change handler func. will be called with (name, value, event)
43 | onChange: PropTypes.func.isRequired,
44 | // short message to help the user
45 | helpText: PropTypes.string,
46 | // weather to show help text or not
47 | showHelpText: PropTypes.bool,
48 | };
49 |
50 | LanguageInput.defaultProps = {
51 | name: undefined,
52 | error: undefined,
53 | showError: true,
54 | placeholder: "",
55 | helpText: "",
56 | showHelpText: false,
57 | };
58 |
59 | export default LanguageInput;
60 |
--------------------------------------------------------------------------------
/src/js/components/form/SelectUser.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | // SelectUser is for picking a user key from a set of keys
4 | const SelectUser = ({ users, value, name, label, onChange }) => {
5 | const handleChange = (e) => {
6 | onChange(name, e.target.value);
7 | }
8 |
9 | if (!users) {
10 | return null;
11 | }
12 |
13 | return (
14 |
15 | {label && {label} }
16 | {label && }
17 |
18 | {users.map((u, i) => {
19 | return {u.username}
20 | })}
21 |
22 |
23 | );
24 | }
25 |
26 | export default SelectUser;
--------------------------------------------------------------------------------
/src/js/components/form/TagInput.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | // TODO - work in progress
4 | const TagInput = ({ label, name, showError, error, value, placeholder, onChange, helpText, showHelpText }) => {
5 | return (
6 |
7 | {label ?
{label} : undefined }
8 |
{ onChange(name, e.target.value, e); }}
16 | />
17 | {(error != "" && showError) ?
{error}
: undefined}
18 | {(helpText && showHelpText) &&
{helpText} }
19 |
20 | );
21 | };
22 |
23 | TagInput.propTypes = {
24 | // required name for the field
25 | name: PropTypes.string.isRequired,
26 | // if provided it'll create a label element to accompany the field
27 | label: PropTypes.string,
28 | // value to display in the field
29 | value: PropTypes.string.isRequired,
30 | // placeholder text for an empty field. default: ""
31 | placeholder: PropTypes.string,
32 | // an error message to displacy
33 | error: PropTypes.string,
34 | // weather or not to actually display any passed-in errors
35 | showError: PropTypes.bool,
36 | // change handler func. will be called with (name, value, event)
37 | onChange: PropTypes.func.isRequired,
38 | // short message to help the user
39 | helpText: PropTypes.string,
40 | // weather to show help text or not
41 | showHelpText: PropTypes.bool,
42 | };
43 |
44 | TagInput.defaultProps = {
45 | name: undefined,
46 | error: undefined,
47 | showError: true,
48 | placeholder: "",
49 | };
50 |
51 | export default TagInput;
52 |
--------------------------------------------------------------------------------
/src/js/components/form/TaskForm.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import ValidInput from './ValidInput';
5 | import ValidSelect from './ValidSelect';
6 |
7 | export default class TaskForm extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | task: {
13 | title: "Add to IPFS",
14 | taskType: "ipfs.add",
15 | params: {
16 | url: "https://i.redd.it/59su4dfwq08z.jpg",
17 | },
18 | },
19 | validation : {},
20 | showErrors : false,
21 | };
22 |
23 | [
24 | "handleChange",
25 | "handleSubmit",
26 | ].forEach((m) => { this[m] = this[m].bind(this); });
27 | }
28 |
29 | handleChange(name, value) {
30 | let params = Object.assign({}, this.state.task.params, { [name] : value });
31 | let task = Object.assign({}, this.state.task, { params });
32 | const change = Object.assign({}, this.state, { task });
33 | this.setState(change);
34 | }
35 |
36 | handleSubmit() {
37 | console.log(this.state.task);
38 | this.props.onSubmit(this.state.task);
39 | }
40 |
41 | render() {
42 | const { task, validation, showErrors } = this.state;
43 |
44 | return (
45 |
46 |
Archive Url
47 |
48 | Submit Task
49 |
50 | );
51 | }
52 | };
53 |
54 | TaskForm.propTypes = {
55 | // data: React.PropTypes.object.isRequired,
56 | // onSelect: React.PropTypes.func.isRequired,
57 | onSubmit: React.PropTypes.func.isRequired,
58 | };
59 |
60 | TaskForm.defaultProps = {
61 | };
--------------------------------------------------------------------------------
/src/js/components/form/TimePicker.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import Clock from './Clock';
3 | /*
4 | * TimePicker Pairs the Clock Component with an
5 | * input field.
6 | *
7 | */
8 |
9 | class TimePicker extends Component {
10 | static propTypes = {
11 | // @todo
12 | }
13 | static hours = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]
14 | static minutes = ["00", "15", "30", "45"]
15 | static phase = ["am", "pm"]
16 | state = {
17 | focused: false,
18 | }
19 |
20 | // return a string representation of the time
21 | _stringValue = () => {
22 | let val = this.props.value;
23 | // if no initial value return blank string
24 | if (!val) {
25 | return "";
26 | }
27 |
28 | if (typeof val === "number") {
29 | val = new Date(val);
30 | }
31 |
32 | let h = val.getHours(),
33 | ph = (h <= 12) ? 0 : 1,
34 | m = (15 * Math.round(val.getMinutes() / 15)) / 15;
35 |
36 | // if we're in the aft, minus 12 to de-militarize time
37 | if (ph === 1) {
38 | h = h - 12;
39 | }
40 |
41 | return this.hours[(h === 0) ? h : h - 1] + ":" + this.minutes[m] + " " + this.phase[ph]
42 | }
43 | _focus = () => {
44 | this.setState({ focused : true });
45 | }
46 | _blur = () => {
47 | this.setState({ focused : false });
48 | }
49 | _fakeFn = () => { }
50 | // Clicks on the clock div should maintain focus on the element
51 | _clockMouseDown = (e) => {
52 | e.preventDefault();
53 | // Cancel Blur event triggered by clicking the clock
54 | $(React.findDOMNode(this.refs["field"])).focus();
55 | return false;
56 | }
57 | _clockChange = (value) => {
58 | if (typeof this.props.onChange === "function") {
59 | this.props.onChange({
60 | name : this.props.name,
61 | value : value
62 | });
63 | }
64 | }
65 |
66 | // Render
67 | render() {
68 | var time
69 | , display = this._stringValue();
70 |
71 | // if (this.refs["field"]) {
72 | // if ($(this.refs["field"]).is(":focus")) {
73 | if (this.state.focused) {
74 | time =
75 | }
76 |
77 | return (
78 |
79 | {this.props.label}
80 |
81 | {time}
82 |
83 | )
84 | }
85 | }
86 |
87 | export default TimePicker;
--------------------------------------------------------------------------------
/src/js/components/form/UrlInput.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | const UrlInput = (props) => {
4 | const { label, name, type, showError, error, value, placeholder, onChange, helpText, showHelpText } = props;
5 | return (
6 |
7 | {label ?
{label} : undefined }
8 |
{ onChange(name, e.target.value, e); }}
16 | />
17 | {(error != "" && showError) ?
{error}
: undefined}
18 | {(helpText && showHelpText) &&
{helpText} }
19 |
20 | );
21 | };
22 |
23 | UrlInput.propTypes = {
24 | // required name for the field
25 | name: PropTypes.string.isRequired,
26 | // if provided it'll create a label element to accompany the field
27 | label: PropTypes.string,
28 | // the type of input, default "text"
29 | type: PropTypes.string.isRequired,
30 | // value to display in the field
31 | value: PropTypes.string.isRequired,
32 | // placeholder text for an empty field. default: ""
33 | placeholder: PropTypes.string,
34 | // an error message to displacy
35 | error: PropTypes.string,
36 | // weather or not to actually display any passed-in errors
37 | showError: PropTypes.bool,
38 | // change handler func. will be called with (name, value, event)
39 | onChange: PropTypes.func.isRequired,
40 | // short message to help the user
41 | helpText: PropTypes.string,
42 | // weather to show help text or not
43 | showHelpText: PropTypes.bool,
44 | };
45 |
46 | UrlInput.defaultProps = {
47 | type: "text",
48 | showError: true,
49 | placeholder: "",
50 | helpText: "",
51 | showHelpText: false,
52 | };
53 |
54 | export default UrlInput;
55 |
--------------------------------------------------------------------------------
/src/js/components/form/ValidDateInput.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import DateInput from './DateInput';
3 |
4 | const ValidDateInput = (props) => {
5 | const { name, label, valid, value, placeholder, disabled, onChange, helpText, showHelpText, className, showValidation } = props;
6 | let validClass = "", message;
7 |
8 | if (showValidation) {
9 | message = (valid) ? "" : message;
10 | validClass = (valid) ? "valid " : "invalid ";
11 | }
12 |
13 | return (
14 |
15 | {label && {label} }
16 |
17 | {message}
18 | {(helpText && showHelpText) && {helpText} }
19 |
20 | );
21 | };
22 |
23 | ValidDateInput.propTypes = {
24 | // gotta name yo fields
25 | name: PropTypes.string.isRequired,
26 | // field value
27 | value: PropTypes.object,
28 | // onChange in the form (value, name)
29 | onChange: PropTypes.func,
30 | // placeholder text
31 | placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
32 | // leave undefined to display no valid
33 | valid: PropTypes.bool,
34 | // enable / disable the field
35 | disabled: PropTypes.bool,
36 | // className will set on the containing div
37 | className: PropTypes.string,
38 | // explicit control over weather or not to display validation
39 | showValidation: PropTypes.bool,
40 | // short message to help the user
41 | helpText: PropTypes.string,
42 | // weather to show help text or not
43 | showHelpText: PropTypes.bool,
44 | };
45 |
46 | ValidDateInput.defaultProps = {
47 | name: "",
48 | value: new Date(),
49 | placeholder: "",
50 | className: " validTextArea field",
51 | showValidationIcon: false,
52 | helpText: "",
53 | showHelpText: false,
54 | };
55 |
56 | export default ValidDateInput;
57 |
--------------------------------------------------------------------------------
/src/js/components/form/ValidDateTimeInput.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import DateInput from './DateInput';
3 |
4 | const ValidDateTimeInput = (props) => {
5 | const { name, value, label, placeholder, disabled, onChange, error, showError, helpText, showHelpText, className } = props;
6 | const errorClass = (error && showError) ? "has-error" : "";
7 | return (
8 |
9 | {label && {label} }
10 |
17 | {(helpText && showHelpText) && {helpText} }
18 |
19 | );
20 | };
21 |
22 | ValidDateTimeInput.propTypes = {
23 | // gotta name yo fields
24 | name: PropTypes.string.isRequired,
25 | // field value
26 | value: PropTypes.object,
27 | // onChange in the form (value, name)
28 | onChange: PropTypes.func.isRequired,
29 | // placeholder text
30 | placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
31 | // enable / disable the field
32 | disabled: PropTypes.bool,
33 | // error text, if any
34 | error: PropTypes.string,
35 | // explicit control over weather or not to display validation
36 | showError: PropTypes.bool,
37 | };
38 |
39 | ValidDateTimeInput.defaultProps = {
40 | name: "",
41 | value: new Date(),
42 | placeholder: "",
43 | helpText: "",
44 | showHelpText: false,
45 | };
46 |
47 | export default ValidDateTimeInput;
48 |
--------------------------------------------------------------------------------
/src/js/components/form/ValidInput.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | const ValidInput = (props) => {
4 | const { label, name, type, showError, error, value, placeholder, onChange, helpText, showHelpText } = props;
5 | return (
6 |
7 | {label &&
{label} }
8 |
{ onChange(name, e.target.value, e); }}
16 | />
17 | {(error != "" && showError) ?
{error}
: undefined}
18 | {(helpText && showHelpText) &&
{helpText} }
19 |
20 | );
21 | };
22 |
23 | ValidInput.propTypes = {
24 | // required name for the field
25 | name: PropTypes.string.isRequired,
26 | // if provided it'll create a label element to accompany the field
27 | label: PropTypes.string,
28 | // the type of input, default "text"
29 | type: PropTypes.string.isRequired,
30 | // value to display in the field
31 | value: PropTypes.string.isRequired,
32 | // placeholder text for an empty field. default: ""
33 | placeholder: PropTypes.string,
34 | // an error message to displacy
35 | error: PropTypes.string,
36 | // weather or not to actually display any passed-in errors
37 | showError: PropTypes.bool,
38 | // change handler func. will be called with (name, value, event)
39 | onChange: PropTypes.func.isRequired,
40 | // short message to help the user
41 | helpText: PropTypes.string,
42 | // weather to show help text or not
43 | showHelpText: PropTypes.bool,
44 | };
45 |
46 | ValidInput.defaultProps = {
47 | name: undefined,
48 | type: "text",
49 | error: undefined,
50 | showError: true,
51 | placeholder: "",
52 | helpText: "",
53 | showHelpText: false,
54 | };
55 |
56 | export default ValidInput;
57 |
--------------------------------------------------------------------------------
/src/js/components/form/ValidSelect.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | const ValidSelect = (props) => {
4 | const { label, name, className, options, showError, error, value, onChange, helpText, showHelpText } = props;
5 | const errorClass = (error && showError) ? "has-error" : "";
6 | return (
7 |
8 | {label &&
{label} }
9 |
{ onChange(name, e.target.value, e); }}>
10 |
11 | {options.map((opt, i) => {opt} )}
12 |
13 | {(error != "" && showError) ?
{error}
: undefined}
14 | {(helpText && showHelpText) &&
{helpText} }
15 |
16 | );
17 | };
18 |
19 | ValidSelect.propTypes = {
20 | // required name for the field
21 | name: PropTypes.string.isRequired,
22 | // if provided it'll create a label element to accompany the field
23 | label: PropTypes.string,
24 | // value to display in the field
25 | value: PropTypes.string.isRequired,
26 | // array of option strings
27 | options: PropTypes.array.isRequired,
28 | // an error message to displacy
29 | error: PropTypes.string,
30 | // weather or not to actually display any passed-in errors
31 | showError: PropTypes.bool,
32 | // change handler func. will be called with (name, value, event)
33 | onChange: PropTypes.func.isRequired,
34 | // short message to help the user
35 | helpText: PropTypes.string,
36 | // weather to show help text or not
37 | showHelpText: PropTypes.bool,
38 | };
39 |
40 | ValidSelect.defaultProps = {
41 | name: undefined,
42 | error: undefined,
43 | showError: true,
44 | placeholder: "",
45 | helpText: "",
46 | showHelpText: false,
47 | };
48 |
49 | export default ValidSelect;
50 |
--------------------------------------------------------------------------------
/src/js/components/form/ValidTextarea.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | const ValidTextarea = (props) => {
4 | const { label, name, type, showError, error, value, placeholder, onChange, helpText, showHelpText } = props;
5 | return (
6 |
7 | {label &&
{label} }
8 |
20 | );
21 | };
22 |
23 | ValidTextarea.propTypes = {
24 | // required name for the field
25 | name: PropTypes.string.isRequired,
26 | // if provided it'll create a label element to accompany the field
27 | label: PropTypes.string,
28 | // the type of input, default "text"
29 | type: PropTypes.string.isRequired,
30 | // value to display in the field
31 | value: PropTypes.string.isRequired,
32 | // placeholder text for an empty field. default: ""
33 | placeholder: PropTypes.string,
34 | // an error message to display
35 | error: PropTypes.string,
36 | // weather or not to actually display any passed-in errors
37 | showError: PropTypes.bool,
38 | // change handler func. will be called with (name, value, event)
39 | onChange: PropTypes.func.isRequired,
40 | // short message to help the user
41 | helpText: PropTypes.string,
42 | // weather to show help text or not
43 | showHelpText: PropTypes.bool,
44 | };
45 |
46 | ValidTextarea.defaultProps = {
47 | name: undefined,
48 | type: "text",
49 | error: undefined,
50 | showError: true,
51 | placeholder: "",
52 | helpText: "",
53 | showHelpText: false,
54 | };
55 |
56 | export default ValidTextarea;
57 |
--------------------------------------------------------------------------------
/src/js/components/item/CollectionItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | const CollectionItem = ({ data }) => {
5 | const collection = data;
6 | return (
7 |
8 |
{collection.title}
9 | {collection.contents &&
{collection.contents.length} {(collection.contents.length == 1) ? "url" : "urls"}
}
10 |
11 | );
12 | };
13 |
14 | CollectionItem.propTypes = {
15 | data: React.PropTypes.object.isRequired,
16 | // onSelect: React.PropTypes.func.isRequired,
17 | };
18 |
19 | CollectionItem.defaultProps = {
20 | };
21 |
22 | export default CollectionItem;
23 |
--------------------------------------------------------------------------------
/src/js/components/item/CollectionItemItem.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import EditCollectionItem from '../form/EditCollectionItem';
5 |
6 | const CollectionItemItem = ({data, index, editable, editing, checked, onToggleSelect, onChange, onDelete}) => {
7 | const item = data;
8 |
9 | if (editing) {
10 | return
11 | }
12 |
13 | const handleToggleSelect = () =>{
14 | onToggleSelect(index, data);
15 | }
16 |
17 | return (
18 |
19 | {editable && }
20 | { item.hash && {item.hash}
}
21 | {item.url}
22 | {item.description}
23 |
24 | );
25 | }
26 |
27 | CollectionItemItem.propTypes = {
28 |
29 | }
30 |
31 | export default CollectionItemItem;
32 |
--------------------------------------------------------------------------------
/src/js/components/item/ContentItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import { fileSizeString, fileTypeString } from '../../selectors/format';
5 |
6 | const ContentItem = ({ data }) => {
7 | const content = data;
8 | return (
9 |
10 |
{content.fileName || "unnamed content"}
11 |
{fileSizeString(content.contentLength)} | {fileTypeString(content.contentSniff)}
12 |
13 | );
14 | };
15 |
16 | ContentItem.propTypes = {
17 | data: React.PropTypes.object.isRequired,
18 | // onSelect: React.PropTypes.func.isRequired,
19 | };
20 |
21 | ContentItem.defaultProps = {
22 | };
23 |
24 | export default ContentItem;
25 |
--------------------------------------------------------------------------------
/src/js/components/item/HistoryItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | const HistoryItem = ({ data }) => {
5 | const uri = encodeURIComponent(data.url);
6 | return (
7 |
8 | {data.url}
9 | {data.hash ? {data.hash} : undefined }
10 |
11 | );
12 | };
13 |
14 | HistoryItem.propTypes = {
15 | data: React.PropTypes.object.isRequired,
16 | // onSelect: React.PropTypes.func.isRequired,
17 | };
18 |
19 | HistoryItem.defaultProps = {
20 | };
21 |
22 | export default HistoryItem;
23 |
--------------------------------------------------------------------------------
/src/js/components/item/MetadataItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router'
3 |
4 | const MetadataItem = ({ data }) => {
5 | const metadata = data
6 | return (
7 |
8 | {metadata.hash}
9 |
10 | )
11 | }
12 |
13 | MetadataItem.propTypes = {
14 | data: React.PropTypes.object.isRequired
15 | // onSelect: React.PropTypes.func.isRequired,
16 | }
17 |
18 | MetadataItem.defaultProps = {
19 | }
20 |
21 | export default MetadataItem
22 |
--------------------------------------------------------------------------------
/src/js/components/item/PrimerItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import ProgressBar from '../ProgressBar';
5 |
6 |
7 |
8 | const PrimerItem = ({ data }) => {
9 | const primer = data;
10 |
11 | return (
12 |
13 |
14 |
15 |
16 | {primer.shortTitle}
17 |
18 |
19 |
20 |
{primer.title}
21 | {primer.stats &&
22 |
23 |
{primer.stats.urlCount} urls
24 |
{primer.stats.contentMetadataCount} / {primer.stats.contentUrlCount} completed
25 |
26 | }
27 |
28 |
29 |
30 | );
31 | };
32 |
33 | PrimerItem.propTypes = {
34 | data: React.PropTypes.object.isRequired,
35 | // onSelect: React.PropTypes.func.isRequired,
36 | };
37 |
38 | PrimerItem.defaultProps = {
39 | };
40 |
41 | export default PrimerItem;
42 |
--------------------------------------------------------------------------------
/src/js/components/item/SearchResultItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import UrlItem from './UrlItem';
4 |
5 | const SearchResultItem = ({ data }) => {
6 | return ;
7 | };
8 |
9 | SearchResultItem.propTypes = {
10 | data: React.PropTypes.object.isRequired,
11 | // onSelect: React.PropTypes.func.isRequired,
12 | };
13 |
14 | SearchResultItem.defaultProps = {
15 | };
16 |
17 | export default SearchResultItem;
18 |
--------------------------------------------------------------------------------
/src/js/components/item/SnapshotItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | const SnapshotItem = ({ data }) => {
5 | return (
6 |
7 |
{data.hash ? {data.hash} : undefined }
8 |
9 | );
10 | };
11 |
12 | SnapshotItem.propTypes = {
13 | data: React.PropTypes.object.isRequired,
14 | // onSelect: React.PropTypes.func.isRequired,
15 | };
16 |
17 | SnapshotItem.defaultProps = {
18 | };
19 |
20 | export default SnapshotItem;
21 |
--------------------------------------------------------------------------------
/src/js/components/item/SourceItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import ProgressBar from '../ProgressBar';
5 |
6 | const SourceItem = ({ data }) => {
7 | const source = data;
8 |
9 | return (
10 |
11 |
12 |
{source.title}
13 |
14 |
15 |
{source.stats.contentMetadataCount}/{source.stats.contentUrlCount}
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | SourceItem.propTypes = {
23 | data: React.PropTypes.object.isRequired,
24 | // onSelect: React.PropTypes.func.isRequired,
25 | };
26 |
27 | SourceItem.defaultProps = {
28 | };
29 |
30 | export default SourceItem;
31 |
--------------------------------------------------------------------------------
/src/js/components/item/TaskItem.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 |
4 | import ProgressBar from '../ProgressBar';
5 |
6 | const TaskItem = ({ data, onSelect }) => {
7 | const task = data;
8 |
9 | return (
10 |
11 |
12 |
{task.title}
13 | {(task.progress && !task.succeeded && !task.error) &&
14 |
15 |
16 |
{task.progress.status}
17 |
}
18 | {(task.progress && task.succeeded && !task.error) &&
done!
}
19 | {task.error &&
{task.error}
}
20 |
21 |
22 | );
23 | };
24 |
25 | TaskItem.propTypes = {
26 | data: PropTypes.object.isRequired,
27 | onSelect: PropTypes.func.isRequired,
28 | };
29 |
30 | TaskItem.defaultProps = {
31 | };
32 |
33 | export default TaskItem;
34 |
--------------------------------------------------------------------------------
/src/js/components/item/UncrawlableItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | const UncrawlableItem = ({ data }) => {
5 | const uncrawlable = data;
6 | return (
7 |
8 | {uncrawlable.url}
9 |
10 | );
11 | };
12 |
13 | UncrawlableItem.propTypes = {
14 | data: React.PropTypes.object.isRequired,
15 | // onSelect: React.PropTypes.func.isRequired,
16 | };
17 |
18 | UncrawlableItem.defaultProps = {
19 | };
20 |
21 | export default UncrawlableItem;
22 |
--------------------------------------------------------------------------------
/src/js/components/item/UrlItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router'
3 |
4 | import { concatUrlString, containsContent } from '../../selectors/url'
5 |
6 | const UrlItem = ({ data }) => {
7 | const url = data
8 | const uri = encodeURIComponent(url.url)
9 |
10 | const urlState = (state) => {
11 | if (state) {
12 | return {url.state.loading ? 'loading' : ''} {url.state.success ? 'success!' : ''} {url.state.error ? url.state.error : '' }
13 | }
14 | return undefined
15 | }
16 |
17 | return (
18 |
19 |
20 | { url.title ?
{url.title} : undefined }
21 | {concatUrlString(url.url)}
22 |
23 |
{containsContent(url) && {url.fileName || 'unnamed content'}}
24 | {urlState(url.state)}
25 |
26 | )
27 | }
28 |
29 | UrlItem.propTypes = {
30 | data: React.PropTypes.object.isRequired
31 | // onSelect: React.PropTypes.func.isRequired,
32 | }
33 |
34 | UrlItem.defaultProps = {
35 | }
36 |
37 | export default UrlItem
38 |
--------------------------------------------------------------------------------
/src/js/components/item/UserItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router';
3 |
4 | const UserItem = ({ data, onSelect }) => {
5 | const imgUrl = data.thumbUrl || data.profileUrl;
6 |
7 | return (
8 |
9 | { imgUrl &&
10 |
11 |
12 |
13 | }
14 |
15 | {data.username}
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | UserItem.propTypes = {
23 | data: React.PropTypes.object.isRequired,
24 | onSelect: React.PropTypes.func.isRequired,
25 | };
26 |
27 | UserItem.defaultProps = {
28 | };
29 |
30 | export default UserItem;
31 |
--------------------------------------------------------------------------------
/src/js/containers/Collections.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import { Link } from 'react-router'
3 | import { connect } from 'react-redux'
4 |
5 | import analytics from '../analytics'
6 | import { selectSessionUser } from '../selectors/session'
7 | import { selectCollections } from '../selectors/collections'
8 | import { loadCollections } from '../actions/collections'
9 |
10 | import List from '../components/List'
11 | import CollectionItem from '../components/item/CollectionItem'
12 | import Spinner from '../components/Spinner'
13 |
14 | class Collections extends React.Component {
15 | constructor (props) {
16 | super(props)
17 | this.state = {
18 | loading: true
19 | };
20 |
21 | [].forEach((m) => { this[m] = this[m].bind(this) })
22 | }
23 |
24 | componentWillMount () {
25 | analytics.page('collections')
26 | this.props.loadCollections()
27 | }
28 |
29 | componentWillReceiveProps (nextProps) {
30 | if (nextProps.collections && this.state.loading) {
31 | this.setState({ loading: false })
32 | }
33 | }
34 |
35 | render () {
36 | const { loading } = this.state
37 | const { collections, user } = this.props
38 |
39 | if (loading) {
40 | return
41 | }
42 |
43 | return (
44 |
45 |
46 |
47 |
48 |
49 |
50 | { user && (
New Collection)}
51 |
Collections
52 |
Curated lists of archived content
53 |
54 |
55 |
56 |
57 |
65 |
66 | )
67 | }
68 | }
69 |
70 | Collections.propTypes = {
71 | // user: PropTypes.object,
72 | collections: PropTypes.array,
73 | loadCollections: PropTypes.func.isRequired
74 | }
75 |
76 | function mapStateToProps (state) {
77 | return {
78 | user: selectSessionUser(state),
79 | collections: selectCollections(state)
80 | }
81 | }
82 |
83 | export default connect(mapStateToProps, {
84 | loadCollections
85 | })(Collections)
86 |
--------------------------------------------------------------------------------
/src/js/containers/Communities.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | import analytics from '../analytics';
5 | import { selectCommunities } from '../selectors/user';
6 | import { loadUsers } from '../actions/user';
7 |
8 | import List from '../components/List';
9 | import UserItem from '../components/item/UserItem';
10 | import Spinner from '../components/Spinner';
11 |
12 | class Communities extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | loading: true,
17 | };
18 |
19 | [].forEach((m) => { this[m] = this[m].bind(this); });
20 | }
21 |
22 | componentWillMount() {
23 | analytics.page('communities');
24 | this.props.loadUsers("community", 1, 25);
25 | }
26 |
27 | componentWillReceiveProps(nextProps) {
28 | if (nextProps.communities && this.state.loading) {
29 | this.setState({ loading: false });
30 | }
31 | }
32 |
33 | render() {
34 | const { loading } = this.state;
35 | const { communities } = this.props;
36 |
37 | if (loading) {
38 | return ;
39 | }
40 |
41 | return (
42 |
43 |
44 |
45 |
46 |
47 |
48 | Communities:
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
62 |
63 | );
64 | }
65 | }
66 |
67 | Communities.propTypes = {
68 | // user: PropTypes.object,
69 | communities: PropTypes.array,
70 | loadUsers: PropTypes.func.isRequired,
71 | };
72 |
73 | function mapStateToProps(state) {
74 | return {
75 | communities: selectCommunities(state),
76 | };
77 | }
78 |
79 | export default connect(mapStateToProps, {
80 | loadUsers,
81 | })(Communities);
82 |
--------------------------------------------------------------------------------
/src/js/containers/DevTools.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // Exported from redux-devtools
4 | import { createDevTools } from 'redux-devtools';
5 |
6 | // Monitors are separate packages, and you can make a custom one
7 | import LogMonitor from 'redux-devtools-log-monitor';
8 | import DockMonitor from 'redux-devtools-dock-monitor';
9 |
10 | // createDevTools takes a monitor and produces a DevTools component
11 | const DevTools = createDevTools(
12 | // Monitors are individually adjustable with props.
13 | // Consult their repositories to learn about those props.
14 | // Here, we put LogMonitor inside a DockMonitor.
15 | // Note: DockMonitor is visible by default.
16 |
21 |
22 | ,
23 | );
24 |
25 | export default DevTools;
26 |
--------------------------------------------------------------------------------
/src/js/containers/Login.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { connect } from 'react-redux';
3 | import { Link, browserHistory } from 'react-router';
4 |
5 | import analytics from '../analytics';
6 | import { loginUser } from '../actions/session';
7 | import { selectSessionUser } from '../selectors/session';
8 | import ValidInput from '../components/form/ValidInput';
9 |
10 | // TODO - add validation logic
11 | class Login extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | username: "",
16 | password: "",
17 | validation: {
18 | username: "",
19 | password: "",
20 | },
21 | };
22 |
23 | ['handleChange', 'handleLoginSubmit'].forEach((m) => { this[m] = this[m].bind(this); });
24 | }
25 |
26 | componentWillMount() {
27 | analytics.page('login');
28 | if (this.props.user != null) {
29 | browserHistory.push('/');
30 | }
31 | }
32 |
33 | componentWillReceiveProps(nextProps) {
34 | if (nextProps.user != null) {
35 | browserHistory.push('/');
36 | }
37 | }
38 |
39 | handleLoginSubmit(e) {
40 | e.preventDefault();
41 | this.props.loginUser(this.state.username, this.state.password);
42 | }
43 |
44 | handleChange(name, value) {
45 | this.setState({ [name]: value });
46 | }
47 |
48 | render() {
49 | const { username, password, validation } = this.state;
50 | return (
51 |
64 | );
65 | }
66 | }
67 |
68 | Login.propTypes = {
69 | loginUser: PropTypes.func.isRequired,
70 | };
71 |
72 | Login.defaultProps = {
73 | };
74 |
75 | function mapStateToProps(state) {
76 | return {
77 | user: selectSessionUser(state),
78 | };
79 | }
80 |
81 | export default connect(mapStateToProps, {
82 | loginUser,
83 | })(Login);
84 |
--------------------------------------------------------------------------------
/src/js/containers/Primers.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | import analytics from '../analytics';
5 | import { selectPrimers } from '../selectors/primers';
6 | import { loadPrimers } from '../actions/primers';
7 |
8 | import List from '../components/List';
9 | import PrimerItem from '../components/item/PrimerItem';
10 | import Spinner from '../components/Spinner';
11 |
12 | class Primers extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | loading: true,
17 | };
18 |
19 | [].forEach((m) => { this[m] = this[m].bind(this); });
20 | }
21 |
22 | componentWillMount() {
23 | analytics.page('primers');
24 | this.props.loadPrimers(1, 25, true);
25 | }
26 |
27 | componentWillReceiveProps(nextProps) {
28 | if (nextProps.primers && this.state.loading) {
29 | this.setState({ loading: false });
30 | }
31 | }
32 |
33 | render() {
34 | const { loading } = this.state;
35 | const { primers } = this.props;
36 |
37 | if (loading) {
38 | return ;
39 | }
40 |
41 | return (
42 |
43 |
44 |
45 |
46 |
47 | Primers:
48 |
49 |
50 |
In order to make our archiving efforts as thorough and systematic as possible,
51 | we use Agency Archiving Primers to identify key programs,
52 | datasets, and documents that are vulnerable to change and loss. Primers are composed of sources ,
53 | which each specify a url as a starting point for archiving.
54 |
55 |
56 |
57 |
58 |
59 |
60 |
66 |
67 | );
68 | }
69 | }
70 |
71 | Primers.propTypes = {
72 | // user: PropTypes.object,
73 | primers: PropTypes.array,
74 | loadPrimers: PropTypes.func.isRequired,
75 | };
76 |
77 | function mapStateToProps(state) {
78 | return {
79 | primers: selectPrimers(state),
80 | };
81 | }
82 |
83 | export default connect(mapStateToProps, {
84 | loadPrimers,
85 | })(Primers);
86 |
--------------------------------------------------------------------------------
/src/js/containers/Root.dev.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Provider } from 'react-redux';
3 | import { Router } from 'react-router';
4 | import routes from '../routes.dev';
5 | import DevTools from './DevTools';
6 |
7 | const Root = ({ store, history }) => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | Root.propTypes = {
19 | store: PropTypes.object.isRequired,
20 | history: PropTypes.object.isRequired,
21 | };
22 |
23 | export default Root;
24 |
--------------------------------------------------------------------------------
/src/js/containers/Root.js:
--------------------------------------------------------------------------------
1 | /* global __BUILD__ */
2 | /* eslint-disable global-require */
3 | if (__BUILD__.PRODUCTION) {
4 | module.exports = require('./Root.prod');
5 | } else if (__BUILD__.STAGING) {
6 | module.exports = require('./Root.prod');
7 | } else if (__BUILD__.DEVELOP) {
8 | module.exports = require('./Root.dev');
9 | }
10 | /* eslint-enable global-require */
11 |
--------------------------------------------------------------------------------
/src/js/containers/Root.prod.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { PropTypes } from 'react';
3 | import { Provider } from 'react-redux';
4 | import { Router } from 'react-router';
5 | import routes from '../routes';
6 |
7 | const Root = ({ store, history }) => {
8 | return (
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | Root.propTypes = {
16 | store: PropTypes.object.isRequired,
17 | history: PropTypes.object.isRequired,
18 | };
19 |
20 | export default Root;
21 |
--------------------------------------------------------------------------------
/src/js/containers/Tasks.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | import analytics from '../analytics';
5 | import { selectTasks } from '../selectors/tasks';
6 | import { loadTasks, enqueueTask } from '../actions/tasks';
7 |
8 | import List from '../components/List';
9 | import TaskForm from '../components/form/TaskForm';
10 | import TaskItem from '../components/item/TaskItem';
11 | import Spinner from '../components/Spinner';
12 |
13 | class Tasks extends React.Component {
14 | constructor(props) {
15 | super(props);
16 | this.state = {
17 | loading: true,
18 | };
19 |
20 | [].forEach((m) => { this[m] = this[m].bind(this); });
21 | }
22 |
23 | componentWillMount() {
24 | analytics.page('tasks');
25 | this.props.loadTasks(1, 25, true);
26 | }
27 |
28 | componentWillReceiveProps(nextProps) {
29 | if (nextProps.tasks && this.state.loading) {
30 | this.setState({ loading: false });
31 | }
32 | }
33 |
34 | render() {
35 | const { loading } = this.state;
36 | const { tasks } = this.props;
37 |
38 | if (loading) {
39 | return ;
40 | }
41 |
42 | return (
43 |
44 |
45 |
46 |
47 |
48 | Tasks:
49 |
50 |
51 |
Hey look a queue of tasks
52 |
53 |
54 |
55 |
56 |
57 |
58 |
64 |
65 |
66 |
67 |
68 | );
69 | }
70 | }
71 |
72 | Tasks.propTypes = {
73 | // user: PropTypes.object,
74 | tasks: PropTypes.array,
75 | loadTasks: PropTypes.func.isRequired,
76 | enqueueTask: PropTypes.func.isRequired,
77 | };
78 |
79 | function mapStateToProps(state) {
80 | return {
81 | tasks: selectTasks(state),
82 | };
83 | }
84 |
85 | export default connect(mapStateToProps, {
86 | loadTasks,
87 | enqueueTask,
88 | })(Tasks);
89 |
--------------------------------------------------------------------------------
/src/js/containers/Uncrawlables.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import { Link } from 'react-router';
3 | import { connect } from 'react-redux';
4 |
5 | import analytics from '../analytics';
6 | import { selectSessionUser } from '../selectors/session';
7 | import { selectUncrawlables } from '../selectors/uncrawlables';
8 | import { loadUncrawlables } from '../actions/uncrawlables';
9 |
10 | import List from '../components/List';
11 | import UncrawlableItem from '../components/item/UncrawlableItem';
12 | import Spinner from '../components/Spinner';
13 |
14 | class Uncrawlables extends React.Component {
15 | constructor(props) {
16 | super(props);
17 | this.state = {
18 | loading: true,
19 | };
20 |
21 | [].forEach((m) => { this[m] = this[m].bind(this); });
22 | }
23 |
24 | componentWillMount() {
25 | analytics.page('uncrawlables');
26 | // this.props.loadUncrawlables();
27 | }
28 |
29 | componentWillReceiveProps(nextProps) {
30 | if (nextProps.uncrawlables && this.state.loading) {
31 | this.setState({ loading: false });
32 | }
33 | }
34 |
35 | render() {
36 | const { loading } = this.state;
37 | const { uncrawlables, user } = this.props;
38 |
39 | if (loading) {
40 | return ;
41 | }
42 |
43 | return (
44 |
45 |
46 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | );
62 | }
63 | }
64 |
65 | Uncrawlables.propTypes = {
66 | // user: PropTypes.object,
67 | uncrawlables: PropTypes.array,
68 | loadUncrawlables: PropTypes.func.isRequired,
69 | };
70 |
71 | function mapStateToProps(state) {
72 | return {
73 | user: selectSessionUser(state),
74 | uncrawlables: selectUncrawlables(state),
75 | };
76 | }
77 |
78 | export default connect(mapStateToProps, {
79 | loadUncrawlables,
80 | })(Uncrawlables);
81 |
--------------------------------------------------------------------------------
/src/js/index.js:
--------------------------------------------------------------------------------
1 | /* globals window */
2 | import 'babel-polyfill';
3 | import React from 'react';
4 | import { render } from 'react-dom';
5 | import { browserHistory } from 'react-router';
6 | import { syncHistoryWithStore } from 'react-router-redux';
7 | import Root from './containers/Root';
8 | import configureStore from './store/configureStore';
9 |
10 | require('../scss/style.scss');
11 |
12 | const store = configureStore(window.data);
13 | const history = syncHistoryWithStore(browserHistory, store);
14 |
15 | render(
16 | ,
17 | document.getElementById('root'),
18 | );
19 |
20 | // Analytics Snippit
21 | // TODO - modularize Analytics
22 | // if (__BUILD__.PRODUCTION) {
23 | // !function(){
24 | // var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t next => action => {}
11 | export default () => next => (action) => {
12 | const modelAction = action[LOCAL_ACTION];
13 | if (typeof modelAction === 'undefined') {
14 | return next(action);
15 | }
16 |
17 | const { method, type, schema, attributes } = modelAction;
18 |
19 | if (!method) {
20 | console.warn("model action is missing method. type: %s. schema: %s, attributes:", type, schema.getKey(), attributes);
21 | }
22 | if (!type) {
23 | // TODO - warnings
24 | console.warn("model action is type. type: %s. schema: %s, attributes:", type, schema.getKey(), attributes);
25 | }
26 | if (!schema) {
27 | // TODO - warnings
28 | }
29 | if (!attributes) {
30 | // TODO - warnings
31 | }
32 |
33 | function actionWith(data) {
34 | const finalAction = Object.assign({}, action, { type }, data);
35 | delete finalAction[LOCAL_ACTION];
36 | return finalAction;
37 | }
38 |
39 | if (method == NEW_MODEL) {
40 | const model = schema.new(attributes);
41 | return next(actionWith({ locals: normalize(model, schema) }));
42 | }
43 |
44 | switch (method) {
45 | case UPDATE_MODEL:
46 | return next(actionWith({ locals: normalize(attributes, schema) }));
47 | case EDIT_MODEL:
48 | return next(actionWith({ locals: normalize(attributes, schema) }));
49 | case CLEAR_MODEL:
50 | // TODO
51 | // return next(actionWith({ locals: { schema.getKey() : { schema.getId(attributes) : undefined }}}))
52 | return next(actionWith({ locals: { remove: normalize(attributes, schema) } }));
53 | default:
54 | console.warn("unknown model action method: %s", method);
55 | }
56 |
57 | return undefined;
58 | };
59 |
--------------------------------------------------------------------------------
/src/js/propTypes/link.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import { PropTypes } from 'react';
22 |
23 | export default PropTypes.shape({
24 | source: PropTypes.string.isRequired,
25 | target: PropTypes.string.isRequired,
26 | value: PropTypes.number,
27 | });
28 |
--------------------------------------------------------------------------------
/src/js/propTypes/node.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import { PropTypes } from 'react';
22 |
23 | export default PropTypes.shape({
24 | id: PropTypes.string.isRequired,
25 | radius: PropTypes.number,
26 | });
27 |
--------------------------------------------------------------------------------
/src/js/propTypes/simulation.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import { PropTypes } from 'react';
22 |
23 | export const DEFAULT_SIMULATION_PROPS = {
24 | animate: false,
25 | width: 900,
26 | height: 600,
27 | strength: {},
28 | };
29 |
30 | export default PropTypes.shape({
31 | data: PropTypes.object,
32 | animate: PropTypes.bool,
33 | alpha: PropTypes.number,
34 | alphaDecay: PropTypes.number,
35 | alphaMin: PropTypes.number,
36 | alphaTarget: PropTypes.number,
37 | velocityDecay: PropTypes.number,
38 | radiusMargin: PropTypes.number,
39 | linkAttrs: PropTypes.array,
40 | nodeAttrs: PropTypes.array,
41 |
42 | // strengths
43 | strength: PropTypes.objectOf(
44 | PropTypes.oneOfType([PropTypes.func, PropTypes.number])
45 | ),
46 | });
47 |
--------------------------------------------------------------------------------
/src/js/reducers/app.js:
--------------------------------------------------------------------------------
1 | import { APP_TOGGLE_MENU, APP_HIDE_MENU, APP_SHOW_MODAL, APP_HIDE_MODAL } from '../actions/app';
2 | import { SEARCH_SET, SEARCH_CLEAR } from '../actions/search';
3 |
4 | const initialState = {
5 | showMenu: false,
6 | modal: undefined,
7 | query: "",
8 | };
9 |
10 | export default function appReducer(state = initialState, action) {
11 | switch (action.type) {
12 | case APP_TOGGLE_MENU:
13 | return Object.assign({}, state, { showMenu: !state.showMenu });
14 | case APP_HIDE_MENU:
15 | return Object.assign({}, state, { showMenu: false });
16 | case APP_SHOW_MODAL:
17 | return Object.assign({}, state, { modal: action.modal });
18 | case APP_HIDE_MODAL:
19 | return Object.assign({}, state, { modal: undefined });
20 | case SEARCH_SET:
21 | return Object.assign({}, state, { query: action.query });
22 | case SEARCH_CLEAR:
23 | return Object.assign({}, state, { query: "" });
24 | // whenever the route changes, close the menu
25 | case "@@router/LOCATION_CHANGE":
26 | return Object.assign({}, state, { showMenu: false });
27 | default:
28 | return state;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/js/reducers/coverage.js:
--------------------------------------------------------------------------------
1 | import {
2 | COVERAGE_NODE_TOGGLE,
3 | COVERAGE_NODE_SUCCESS,
4 | // COVERAGE_USER_FAILURE,
5 | // COVERAGE_LOGIN_SUCCESS,
6 | // COVERAGE_ADD_HISTORY_ENTRY,
7 | } from '../actions/coverage';
8 |
9 | // initial state is an empty tree
10 | const initialState = {
11 | tree : undefined,
12 | };
13 |
14 | export default function coverageReducer(state = initialState, action) {
15 | switch (action.type) {
16 | case COVERAGE_NODE_TOGGLE:
17 | return {
18 | tree: copyWalk(state.tree, (n) => {
19 | if (n && n.id != action.id) {
20 | return n;
21 | } else {
22 | if (n.children) {
23 | return Object.assign({}, n, { _children: n.children, children: undefined });
24 | } else if (n._children) {
25 | return Object.assign({}, n, { children: n._children, _children: undefined });
26 | } else {
27 | return n;
28 | }
29 | }
30 | })
31 | }
32 | case COVERAGE_NODE_SUCCESS:
33 | if (action.tree.id == "root") {
34 | return {
35 | tree: action.tree,
36 | }
37 | }
38 | return Object.assign({}, state, {
39 | tree: copyWalk(state.tree, (n) => (n && n.id == action.tree.id) ? action.tree : n)
40 | });
41 | default:
42 | return state;
43 | }
44 | }
45 |
46 | // walk a provide tree, calling fn on each node with a new copy
47 | function copyWalk(tree, fn) {
48 | let copy = fn(copyNode(tree))
49 |
50 | if (copy.children) {
51 | for (let i = 0; i < copy.children.length; i++) {
52 | copy.children[i] = copyWalk(copy.children[i], fn);
53 | }
54 | }
55 |
56 | return copy;
57 | }
58 |
59 | function copyNode(node) {
60 | let copy = Object.assign({}, node);
61 | if (copy.children) {
62 | var a = [];
63 | // TODO - make faster
64 | copy.children.forEach(n => {
65 | a.push(n);
66 | });
67 | copy.children = a;
68 | }
69 |
70 | return copy;
71 | }
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/js/reducers/layout.js:
--------------------------------------------------------------------------------
1 | import { LAYOUT_RESIZE, LAYOUT_SHOW_SIDEBAR, LAYOUT_HIDE_SIDEBAR } from '../actions/layout';
2 |
3 | const COLLAPSED_WIDTH = 0;
4 |
5 |
6 | // @return {string} string representation of windowidth size class
7 | function sizeClass(width) {
8 | if (width >= 1200) {
9 | return 'xl';
10 | } else if (width >= 992) {
11 | return 'lg';
12 | } else if (width >= 768) {
13 | return 'md';
14 | } else if (width >= 544) {
15 | return 'sm';
16 | }
17 |
18 | return 'xs';
19 | }
20 |
21 |
22 | function layout(state) {
23 | const { stage, navbar, sidebar, main } = state;
24 |
25 | let sidebarWidth = sidebar.collapsed ? COLLAPSED_WIDTH : stage.width * sidebar.pct_width;
26 | if (!sidebar.collapsed && sidebarWidth > sidebar.maxWidth) { sidebarWidth = sidebar.maxWidth; }
27 |
28 | return {
29 | size: sizeClass(state.stage.width),
30 | stage,
31 | navbar: {
32 | width: stage.width,
33 | height: navbar.height,
34 | left: 0,
35 | top: 0,
36 | collapsed: navbar.collapsed,
37 | },
38 | main: {
39 | width: stage.width - sidebarWidth,
40 | height: stage.height - navbar.height,
41 | top: navbar.height,
42 | left: 0,
43 | },
44 | sidebar: {
45 | maxWidth: sidebar.maxWidth,
46 | width: sidebarWidth,
47 | height: stage.height - navbar.height,
48 | left: stage.width - sidebarWidth,
49 | top: navbar.height,
50 | collapsed: sidebar.collapsed,
51 | pct_width: sidebar.pct_width,
52 | },
53 | };
54 | }
55 |
56 | const initialState = {
57 | size: 'xs',
58 | stage: { width: 100, height: 100 },
59 | navbar: { width: 100, height: 50, left: 0, bottom: 0, collapsed: false },
60 | main: { width: 100, height: 100, left: 0, top: 0 },
61 | sidebar: { maxWidth: 250, width: COLLAPSED_WIDTH, height: 100, left: 0, top: 0, collapsed: true, pct_width: 0.35 },
62 | };
63 |
64 | export default function layoutReducer(state = initialState, action) {
65 | switch (action.type) {
66 | case LAYOUT_RESIZE:
67 | return layout(Object.assign({}, state, action));
68 | case LAYOUT_SHOW_SIDEBAR:
69 | return layout(Object.assign({}, state, { sidebar: Object.assign({}, state.sidebar, { collapsed: false }) }));
70 | case LAYOUT_HIDE_SIDEBAR:
71 | return layout(Object.assign({}, state, { sidebar: Object.assign({}, state.sidebar, { collapsed: true }) }));
72 | default:
73 | return state;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/js/reducers/locals.js:
--------------------------------------------------------------------------------
1 | // import * as ActionTypes from '../actions/app';
2 |
3 | const initialState = {
4 | sshKeys: {},
5 | groups: {},
6 | files: {},
7 | metadata: {},
8 | collections: {},
9 | uncrawlables: {},
10 | tasks: {},
11 | users: {},
12 | };
13 |
14 | // updates an entity cache in response to any actuion with response.local.
15 | // see local middleware
16 | export default function locals(state = initialState, action) {
17 | if (action.locals) {
18 | if (action.locals.remove) {
19 | const newState = Object.assign({}, state, action.locals.entities);
20 | Object.keys(action.locals.remove.entities).forEach((model) => {
21 | Object.keys(action.locals.remove.entities[model]).forEach((id) => {
22 | delete newState[model][id];
23 | });
24 | });
25 | return newState;
26 | } else if (action.locals.entities) {
27 | return Object.assign({}, state, action.locals.entities);
28 | }
29 | }
30 | return state;
31 | }
32 |
--------------------------------------------------------------------------------
/src/js/reducers/selection.js:
--------------------------------------------------------------------------------
1 | import { SELECT, DESELECT } from '../actions/selection'
2 |
3 | const initialState = {
4 | }
5 |
6 | export default function selectionReducer(state=initialState, action) {
7 | switch (action.type) {
8 | case SELECT:
9 | return Object.assign(action.selection);
10 | case DESELECT:
11 | return Object.assign({});
12 | }
13 |
14 | return state;
15 | }
16 |
--------------------------------------------------------------------------------
/src/js/reducers/session.js:
--------------------------------------------------------------------------------
1 | import {
2 | SESSION_USER_SUCCESS,
3 | SESSION_USER_FAILURE,
4 | SESSION_LOGIN_SUCCESS,
5 | SESSION_ADD_HISTORY_ENTRY,
6 | SESSION_USER_COMMUNITIES_SUCCESS,
7 | } from '../actions/session';
8 |
9 | const initialState = {
10 | requestedSession: false,
11 | history: [],
12 | };
13 |
14 | export default function sessionReducer(state = initialState, action) {
15 | switch (action.type) {
16 | case SESSION_USER_SUCCESS:
17 | case SESSION_USER_FAILURE:
18 | case SESSION_LOGIN_SUCCESS:
19 | return Object.assign({}, state, { requestedSession: true });
20 | case SESSION_ADD_HISTORY_ENTRY:
21 | return Object.assign({}, state, { history: [action.value].concat(state.history) });
22 | default:
23 | return state;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/js/reducers/urlStates.js:
--------------------------------------------------------------------------------
1 | import { URL_SET_LOADING, URL_SET_SUCCESS, URL_SET_ERROR } from '../actions/url';
2 |
3 | const initialState = {};
4 |
5 | export default function urlStates(state = initialState, action) {
6 | switch (action.type) {
7 | case URL_SET_LOADING:
8 | return Object.assign({}, state, {
9 | [action.data.url]: Object.assign({}, state[action.data.url], { loading: action.data.loading }),
10 | });
11 | case URL_SET_SUCCESS:
12 | return Object.assign({}, state, {
13 | [action.data.url]: Object.assign({}, state[action.data.url], { success: action.data.success, loading: false }),
14 | });
15 | case URL_SET_ERROR:
16 | return Object.assign({}, state, {
17 | [action.data.url]: Object.assign({}, state[action.data.url], { error: action.data.error, loading: false }),
18 | });
19 | default:
20 | return state;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/js/selectors/archiveServices.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const archiveServices = [
4 | {
5 | id: "5d5583g5-38a8-26d3-be70-c324bf686a87",
6 | name: "Internet Archive",
7 | description: "the internet archive",
8 | homeUrl: "https://archive.org",
9 | },
10 | {
11 | id: "3f5b22g5-37b4-5dc3-be91-c324bf686a87",
12 | name: "EOT Nomination Tool",
13 | description: "",
14 | homeUrl: "https://github.com/edgi-govdata-archiving/eot-nomination-tool",
15 | },
16 | {
17 | id: "4c0122g5-38a8-40b3-be91-c324bf686a87",
18 | name: "archivers.space",
19 | description: "",
20 | homeUrl: "https://www.archivers.space",
21 | },
22 | {
23 | id: "8d7e22g5-38a8-40b3-be91-c324bf686a87",
24 | name: "archivers 2.0",
25 | description: "",
26 | homeUrl: "https://alpha.archivers.space",
27 | }
28 | ]
29 |
30 | export function archiveService(id) {
31 | return archiveServices.find((s) => s.id == id);
32 | }
--------------------------------------------------------------------------------
/src/js/selectors/collections.js:
--------------------------------------------------------------------------------
1 |
2 | export function selectLocalCollection (state, id) {
3 | return state.locals.collections[id]
4 | }
5 |
6 | export function selectCollection (state, id = '') {
7 | return state.entities.collections[id]
8 | }
9 |
10 | export function selectCollections (state) {
11 | const { collections } = state.entities
12 | return Object.keys(collections).map(k => collections[k])
13 | }
14 |
15 | export function selectRecentCollections (state, count = 3) {
16 | const { collections } = state.entities
17 | return Object.keys(collections).map(k => collections[k]).slice(0, count)
18 | }
19 |
20 | export function selectCollectionItems (state, id) {
21 | const { collectionItems } = state.entities
22 | const ci = state.pagination.collectionItems[id]
23 | if (ci) {
24 | return ci.ids.map(id => collectionItems[id])
25 | }
26 |
27 | return []
28 | }
29 |
30 | export function selectCollectionsByKey (state, key) {
31 | const { collections } = state.entities
32 | return Object.keys(collections).filter(id => collections[id].creator == key).map(id => collections[id])
33 | }
34 |
--------------------------------------------------------------------------------
/src/js/selectors/consensus.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export function selectConsensus(state, subject) {
4 | return state.entities.consensus[subject];
5 | }
6 |
7 | export function selectAllConsensus(state) {
8 | return Object.keys(state.entities.consensus).map(hash => state.entities.consensus[hash]);
9 | }
10 |
--------------------------------------------------------------------------------
/src/js/selectors/content.js:
--------------------------------------------------------------------------------
1 | import { fileSizeString, dateString, fileTypeString } from './format';
2 |
3 | export function selectContent(state, hash) {
4 | return state.entities.content[hash];
5 | }
6 |
7 | export function selectContentConsensus(state, hash) {
8 | return state.entities.consensus[hash];
9 | }
10 |
11 | export function selectContentUrls(state, hash) {
12 | const { urls } = state.entities;
13 |
14 | return Object.keys(urls)
15 | .filter(u => urls[u].hash == hash)
16 | .map(url => urls[url]);
17 | }
18 |
19 | export function selectContentMetadata(state, hash) {
20 | const { metadata } = state.entities;
21 | return Object.keys(metadata)
22 | .filter(key => metadata[key].subject == hash)
23 | .map(key => metadata[key]);
24 | }
25 |
26 | export function selectRecentContentUrls(state) {
27 | const pages = state.pagination.contentRecentUrls;
28 | if (!pages.created) {
29 | return [];
30 | }
31 | return pages.created.ids.map(urlId => state.entities.urls[urlId]);
32 | }
33 |
34 | export function contentStats(url = {}) {
35 | return {
36 | size: fileSizeString(url.contentLength),
37 | "content type": fileTypeString(url.contentSniff),
38 | "last get": url.lastGet ? dateString(url.lastGet) : "never",
39 | status: url.status,
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/src/js/selectors/coverage.js:
--------------------------------------------------------------------------------
1 | import { walk, filter } from '../utils/tree';
2 |
3 | const completionColors = ["#ee005c", "#e01f54", "#d43a4e", "#c85647", "#bc7040", "#b08a3a", "#a4a533", "#98c02c", "#8adf24"];
4 | export function completionColor(node) {
5 | if (!node.numLeaves) {
6 | return node.coverage && node.coverage.length ? completionColors[completionColors.length - 1] : completionColors[0];
7 | }
8 | const compl = ((node.numLeavesArchived || 0) / node.numLeaves);
9 | if (compl == 1) return completionColors[completionColors.length - 1];
10 | return completionColors[Math.floor(compl * completionColors.length)];
11 | }
12 |
13 | export function searchTree(tree, query) {
14 | return filter(tree, (n) => ~~n.name.indexOf(query) >= 0);
15 | }
16 |
17 | export function selectNode(state,id) {
18 | const { tree } = state.coverage;
19 | let node;
20 | walk(tree, (n) => {
21 | if (n && n.id == id) {
22 | node = n;
23 | }
24 | });
25 | return node;
26 | }
27 |
28 | export function flattenTree(tree) {
29 | let links = [], nodes = [];
30 |
31 | if (tree == undefined) {
32 | return {
33 | links,
34 | nodes
35 | };
36 | }
37 |
38 | walk(tree,(n) => {
39 | nodes.push({
40 | id : n.id,
41 | name: n.name,
42 | radius: 5,
43 | numLeaves: n.numLeaves,
44 | numLeavesArchived: n.numLeavesArchived,
45 | numChildren: n.numChildren,
46 | coverage: n.coverage,
47 | collapsed: n.collapsed,
48 | });
49 |
50 | if (n.children) {
51 | n.children.forEach((c) => {
52 | links.push({
53 | source: n.id,
54 | target: c.id,
55 | });
56 | })
57 | }
58 |
59 | return true;
60 | });
61 |
62 | return {
63 | links,
64 | nodes,
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/js/selectors/file.js:
--------------------------------------------------------------------------------
1 |
2 | export function selectFileByKey(state, key) {
3 | const { files } = state.entities;
4 | return Object.keys(file).filter(id => files[id].keyId == key).map(id => files[id]);
5 | }
6 |
7 | export function selectLocalFile(state, id) {
8 | return state.locals.files[id];
9 | }
10 |
11 | export function selectFile(state, id) {
12 | return state.entities.files[id];
13 | }
14 |
--------------------------------------------------------------------------------
/src/js/selectors/format.js:
--------------------------------------------------------------------------------
1 |
2 | export function fileSizeString(len = 0) {
3 | let rounded;
4 | if (len < 1000) {
5 | return `${len} bytes`;
6 | } else if (len < (1000 * 1000 * 1000)) {
7 | rounded = Math.round(len/1000);
8 | return `${rounded} kb`;
9 | } else if (len < (1000 * 1000 * 1000 * 1000)) {
10 | rounded = Math.round(len/1000/1000) / 100;
11 | return `${rounded} Mb`;
12 | } else {
13 | rounded = Math.round(len/1000/1000/1000) / 100;
14 | return `${rounded} Gb`;
15 | }
16 | }
17 |
18 | export function dateString(d) {
19 | d = new Date(d)
20 | return d.toISOString().slice(0,-14);
21 | }
22 |
23 | export function fileTypeString(mime) {
24 | switch (mime) {
25 | case "application/pdf":
26 | return "pdf";
27 | case "application/csv":
28 | return "csv";
29 | default:
30 | return mime;
31 | }
32 | }
--------------------------------------------------------------------------------
/src/js/selectors/keys.js:
--------------------------------------------------------------------------------
1 |
2 | // default key looks for a key named "default key", falling back to any
3 | // available key
4 | export function selectDefaultKey(state) {
5 | const { keys } = state.entities;
6 | return Object.keys(keys).map(id => keys[id]).reduce((key, a) => {
7 | if (key && key.name == "default key") {
8 | return key;
9 | }
10 | return a;
11 | }, {});
12 | }
13 |
14 | export function selectDefaultKeyId(state) {
15 | const key = selectDefaultKey(state);
16 | return key.sha256 || "";
17 | }
18 |
19 | export function selectKeys(state) {
20 | const { keys } = state.entities;
21 | return Object.keys(keys).map(id => keys[id]);
22 | }
23 |
--------------------------------------------------------------------------------
/src/js/selectors/metadata.js:
--------------------------------------------------------------------------------
1 |
2 | export function metadataId(userId, subjectHash) {
3 | return `${userId}.${subjectHash}`;
4 | }
5 |
6 | export function selectMetadataByKey(state, key) {
7 | const { metadata } = state.entities;
8 | return Object.keys(metadata).filter(id => metadata[id].keyId == key).map(id => metadata[id]);
9 | }
10 |
11 | export function selectLocalMetadata(state, userId, subjectHash) {
12 | if (!userId || !subjectHash) { return undefined; }
13 | return state.locals.metadata[metadataId(userId, subjectHash)];
14 | }
15 |
16 | export function selectMetadata(state, userId, subjectHash) {
17 | if (!userId || !subjectHash) { return undefined; }
18 | return state.entities.metadata[metadataId(userId, subjectHash)];
19 | }
20 |
--------------------------------------------------------------------------------
/src/js/selectors/primers.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export function selectPrimers(state) {
4 | const { primers } = state.entities;
5 | return Object.keys(primers).map(k => primers[k]);
6 | }
7 |
8 | export function selectPrimer(state, id = "") {
9 | if (!state.entities.primers[id]) { return undefined; }
10 | const p = Object.assign({}, state.entities.primers[id]);
11 | p.stats || (p.stats = {});
12 | if (p.subPrimers) {
13 | p.subPrimers = p.subPrimers.map(subId => state.entities.primers[subId]);
14 | }
15 | return p;
16 | }
17 |
--------------------------------------------------------------------------------
/src/js/selectors/search.js:
--------------------------------------------------------------------------------
1 |
2 | export function selectSearchQuery(state) {
3 | return state.app.query;
4 | }
5 |
6 | export function selectSearchResults(state) {
7 | const query = selectSearchQuery(state);
8 | if (!query) { return []; }
9 |
10 | const { searchResults } = state.entities;
11 | return Object.keys(searchResults).map(key => searchResults[key]).filter((res) => {
12 | return (res.url.indexOf(query) >= 0);
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/src/js/selectors/session.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export function selectSessionUser(state) {
4 | const { session } = state.entities;
5 | const users = Object.keys(session).map(k => session[k]);
6 | return (users.length == 1) ? users[0] : undefined;
7 | }
8 |
9 | export function selectLocalSessionUser(state) {
10 | const { session } = state.locals;
11 | if (!session) {
12 | return undefined;
13 | }
14 | const users = Object.keys(session).map(k => session[k]);
15 | return (users.length == 1) ? users[0] : undefined;
16 | }
17 |
18 | export function selectAvailableUsers(state) {
19 | const session = selectSessionUser(state);
20 | const { users } = state.entities;
21 | const { communities={ids:[]} } = state.pagination.sessionUserCommunities;
22 |
23 | if (!session) {
24 | return [];
25 | }
26 |
27 | // TODO - ew code smell
28 | return [session].concat(communities.ids.map(k => users[k]));
29 | }
--------------------------------------------------------------------------------
/src/js/selectors/sources.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export function selectSources(state) {
4 | const { sources } = state.entities;
5 | return Object.keys(sources).map(k => sources[k]);
6 | }
7 |
8 | export function selectSource(state, id = "") {
9 | return state.entities.sources[id];
10 | }
11 |
12 | export function selectSourceUndescribedUrls(state, id = "") {
13 | const pages = state.pagination.sourceUndescribedUrls;
14 | if (!pages[id]) {
15 | return [];
16 | }
17 |
18 | return pages[id].ids.map(urlId => state.entities.urls[urlId]);
19 | }
20 |
21 | export function selectRecentSources(state) {
22 | const pages = state.pagination.sources;
23 | if (!pages.created) {
24 | return [];
25 | }
26 | return pages.created.ids.map(id => state.entities.sources[id]);
27 | }
28 |
29 | export function selectSourceAttributedUrls(state, id = "") {
30 | const pages = state.pagination.sourceAttributedUrls;
31 | if (!pages[id]) {
32 | return [];
33 | }
34 | return pages[id].ids.map(urlId => state.entities.urls[urlId]);
35 | }
36 |
--------------------------------------------------------------------------------
/src/js/selectors/tasks.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export function selectTasks(state) {
4 | const { tasks } = state.entities;
5 | return Object.keys(tasks).map(k => tasks[k]);
6 | }
7 |
8 | export function selectTask(state, id = "") {
9 | if (!state.entities.tasks[id]) { return undefined; }
10 | const p = Object.assign({}, state.entities.tasks[id]);
11 | p.stats || (p.stats = {});
12 | if (p.subTasks) {
13 | p.subTasks = p.subTasks.map(subId => state.entities.tasks[subId]);
14 | }
15 | return p;
16 | }
17 |
18 | export function selectCollectionTasks(state, collectionId = "") {
19 | const { tasks } = state.entities;
20 | return Object.keys(tasks).filter(id => tasks[id].params.collectionId == collectionId).map(id => tasks[id]);
21 | }
22 |
23 | export function selectCollectionActiveTasks(state, collectionId = "") {
24 | const { tasks } = state.entities;
25 | return Object.keys(tasks).filter(id => (tasks[id].params.collectionId == collectionId && tasks[id].progress)).map(id => tasks[id]);
26 | }
27 |
28 | export function selectUrlActiveTasks(state) {
29 | const { tasks } = state.entities;
30 | return Object.keys(tasks).filter(id => (tasks[id].progress)).map(id => tasks[id]);
31 | }
--------------------------------------------------------------------------------
/src/js/selectors/uncrawlables.js:
--------------------------------------------------------------------------------
1 |
2 | export function selectLocalUncrawlable(state, id) {
3 | return state.locals.uncrawlables[id];
4 | }
5 |
6 | export function selectUncrawlable(state, id = "") {
7 | return state.entities.uncrawlables[id];
8 | }
9 |
10 | export function selectUncrawlables(state) {
11 | const { uncrawlables } = state.entities;
12 | return Object.keys(uncrawlables).map(k => uncrawlables[k]);
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/src/js/selectors/url.js:
--------------------------------------------------------------------------------
1 | import { fileSizeString, dateString } from './format';
2 |
3 | export function concatUrlString(u, max = 80) {
4 | return (u.length < max) ? u : `${u.slice(0, max)}...`;
5 | }
6 |
7 | export function containsContent(url = {}) {
8 | return (url.contentSniff && url.contentSniff != "text/html; charset=utf-8");
9 | }
10 |
11 | export function selectUrl(state, urlString = "") {
12 | const { urls } = state.entities;
13 | const id = Object.keys(urls).find(u => (u == urlString));
14 | if (!id) { return undefined; }
15 |
16 | let url = urls[id];
17 | url.state = state.urlStates[urlString] || {};
18 | return url;
19 | }
20 |
21 | export function selectUrlByHash(state, hash = "") {
22 | const { urls } = state.entities;
23 | const id = Object.keys(urls).find(u => (urls[u].hash == hash));
24 | if (!id) { return undefined; }
25 |
26 | let url = urls[id];
27 | url.state = state.urlStates[url.url] || {};
28 | return url;
29 | }
30 |
31 | export function urlStats(url = {}) {
32 | return {
33 | "last get": url.lastGet ? dateString(url.lastGet) : "never",
34 | status: url.status,
35 | size: fileSizeString(url.contentLength),
36 | "content type": url.contentSniff,
37 | };
38 | }
39 |
40 | export function selectOutboundLinks(state, urlString = "") {
41 | const { urls, links } = state.entities;
42 | return Object.keys(links)
43 | .map(key => links[key])
44 | .filter(link => link.src.url == urlString)
45 | .map((link) => {
46 | let url = urls[link.dst.url] || link.dst;
47 | url.state = state.urlStates[link.dst.url] || {};
48 | return url;
49 | });
50 | }
51 |
52 | export function selectInboundLinks(state, urlString = "") {
53 | const { urls, links } = state.entities;
54 | return Object.keys(links)
55 | .map(key => links[key])
56 | .filter(link => link.dst.url == urlString)
57 | .map((link) => {
58 | let url = urls[link.src.url] || link.src;
59 | url.state = state.urlStates[link.dst.url] || {};
60 | return url;
61 | });
62 | }
63 |
--------------------------------------------------------------------------------
/src/js/selectors/user.js:
--------------------------------------------------------------------------------
1 |
2 | export function selectCommunities(state) {
3 | const { users } = state.entities;
4 | return Object.keys(users).filter(id => users[id].type == "community").map(id => users[id]);
5 | }
6 |
7 | // search through the state tree for
8 | export function selectUserByUsername(state, username) {
9 | const { users } = state.entities;
10 | const userId = Object.keys(users).find(id => (users[id].username == username));
11 | return userId ? users[userId] : undefined;
12 | }
13 |
14 | export function selectUserById(state, id) {
15 | return state.entities.users[id];
16 | }
17 |
18 | export function selectCommunityUsers(state, id) {
19 | const { users } = state.entities;
20 | const { communityUsers } = state.pagination;
21 |
22 | if (communityUsers[id]) {
23 | return communityUsers[id].ids.map(id => users[id]);
24 | }
25 |
26 | return [];
27 | }
28 |
29 | export function selectLocalUser(state, id) {
30 | // const { users } = state.locals;
31 | // if (!users) {
32 | // return undefined;
33 | // }
34 | // const u = Object.keys(users).map(k => users[k]).find(u)
35 | // return (u.length == 1) ? u[0] : undefined;
36 | return state.locals.users[id];
37 | }
38 |
--------------------------------------------------------------------------------
/src/js/selectors/viewConfig.js:
--------------------------------------------------------------------------------
1 |
2 | export function dimensions(device) {
3 | let width = 300, height = 400;
4 | switch (device.size) {
5 | case 'xs':
6 | width = 300;
7 | break;
8 | case 'sm':
9 | width = 450;
10 | break;
11 | case 'md':
12 | width = 600;
13 | break;
14 | case 'lg':
15 | width = 900;
16 | height = 400;
17 | break;
18 | case 'xl':
19 | width = 1100;
20 | height = 500;
21 | break;
22 | default:
23 | width = 960;
24 | height = 1200;
25 | break;
26 | }
27 |
28 | return { width, height };
29 | }
30 |
31 | export function wrapperStyle() {
32 | return { background: "#272727", border: "none", borderRadius: 3 };
33 | }
34 |
35 | export function defaultColor(index) {
36 | return ["#49C797", "#E6DB74", "#B04BD8", "#E2542E", "#DFE2CE", "#49C3C7"][index];
37 | }
38 |
--------------------------------------------------------------------------------
/src/js/store/configureStore.dev.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import { routerMiddleware } from 'react-router-redux';
3 | import { browserHistory } from 'react-router';
4 | import thunk from 'redux-thunk';
5 | // import createLogger from 'redux-logger';
6 | import api from '../middleware/api';
7 | import users from '../middleware/users';
8 | import locals from '../middleware/locals';
9 | import rootReducer from '../reducers';
10 | import DevTools from '../containers/DevTools';
11 | import coverage from '../middleware/coverage';
12 |
13 | export default function configureStore(preloadedState) {
14 | const store = createStore(
15 | rootReducer,
16 | preloadedState,
17 | compose(
18 | applyMiddleware(thunk, api, locals, users, coverage, routerMiddleware(browserHistory)),
19 | DevTools.instrument(),
20 | ),
21 | );
22 |
23 | if (module.hot) {
24 | // Enable Webpack hot module replacement for reducers
25 | module.hot.accept('../reducers', () => {
26 | /* eslint-disable global-require */
27 | const nextRootReducer = require('../reducers').default;
28 | /* eslint-enable global-require */
29 | store.replaceReducer(nextRootReducer);
30 | });
31 | }
32 |
33 | return store;
34 | }
35 |
--------------------------------------------------------------------------------
/src/js/store/configureStore.js:
--------------------------------------------------------------------------------
1 | /* globals __BUILD__ */
2 | /* eslint-disable global-require */
3 | if (__BUILD__.PRODUCTION) {
4 | module.exports = require('./configureStore.prod');
5 | } else if (__BUILD__.STAGING) {
6 | module.exports = require('./configureStore.prod');
7 | } else if (__BUILD__.DEVELOP) {
8 | module.exports = require('./configureStore.dev');
9 | }
10 | /* eslint-enable global-require */
11 |
--------------------------------------------------------------------------------
/src/js/store/configureStore.prod.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import { browserHistory } from 'react-router';
3 | import { routerMiddleware } from 'react-router-redux';
4 | import thunk from 'redux-thunk';
5 |
6 | import api from '../middleware/api';
7 | import users from '../middleware/users';
8 | import coverage from '../middleware/coverage';
9 | import locals from '../middleware/locals';
10 | import rootReducer from '../reducers';
11 |
12 |
13 | export default function configureStore(preloadedState) {
14 | return createStore(
15 | rootReducer,
16 | preloadedState,
17 | applyMiddleware(thunk, api, locals, users, coverage, routerMiddleware(browserHistory)),
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/js/utils/__mocks__/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "import/no-extraneous-dependencies": 0,
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/js/utils/__mocks__/raf.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | const bindFactory = fn => ({ fn });
22 | const unbindFactory = obj => ({ ...obj, fn: noop => noop });
23 |
24 | export const requestAnimationFrame = jest.fn((...args) => bindFactory(...args));
25 | export const cancelAnimationFrame = jest.fn((...args) => unbindFactory(...args));
26 |
--------------------------------------------------------------------------------
/src/js/utils/__tests__/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "import/no-extraneous-dependencies": 0,
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/js/utils/__tests__/sets-equal.test.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import setsEqual from '../sets-equal';
22 |
23 | describe('setsEqual()', () => {
24 | it('should return true for sets in the same order', () => {
25 | expect(setsEqual(
26 | new Set(['a', 'b', 'c']),
27 | new Set(['a', 'b', 'c'])
28 | )).toEqual(true);
29 | });
30 |
31 | it('should return true for out-of-order sets', () => {
32 | expect(setsEqual(
33 | new Set(['a', 'b', 'c']),
34 | new Set(['c', 'a', 'b'])
35 | )).toEqual(true);
36 | });
37 |
38 | it('should return false for blatantly different', () => {
39 | expect(setsEqual(
40 | new Set(['a', 'b', 'c']),
41 | new Set(['e', 'f', 'c'])
42 | )).toEqual(false);
43 | });
44 |
45 | it('should return false for left-side inclusive non-matching sets', () => {
46 | expect(setsEqual(
47 | new Set(['c', 'a', 'b', 'd']),
48 | new Set(['a', 'b', 'c'])
49 | )).toEqual(false);
50 | });
51 |
52 | it('should return false for right-side inclusive non-matching sets', () => {
53 | expect(setsEqual(
54 | new Set(['a', 'b', 'c']),
55 | new Set(['c', 'a', 'b', 'd'])
56 | )).toEqual(false);
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/src/js/utils/raf.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import { window } from 'global';
22 |
23 | export const requestAnimationFrame = (fn, ...rest) => {
24 | if (window && {}.hasOwnProperty.call(window, 'cancelAnimationFrame')) {
25 | window.requestAnimationFrame(fn, ...rest);
26 | } else {
27 | fn(...rest);
28 | }
29 | };
30 |
31 | export const cancelAnimationFrame = (...args) => {
32 | if (window && {}.hasOwnProperty.call(window, 'cancelAnimationFrame')) {
33 | window.cancelAnimationFrame(...args);
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/src/js/utils/sets-equal.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | /**
22 | * check ES2015 Sets for equality.
23 | * http://stackoverflow.com/questions/31128855/comparing-ecma6-sets-for-equality
24 | * @param {Set} setA
25 | * @param {Set} setB
26 | * @returns {boolean} are the sets equal
27 | */
28 | export default function setsEqual(setA, setB) {
29 | if (setA.size !== setB.size) {
30 | return false;
31 | }
32 |
33 | let acc = true;
34 | setA.forEach((a) => {
35 | acc = acc && setB.has(a);
36 | });
37 |
38 | return acc;
39 | }
40 |
--------------------------------------------------------------------------------
/src/js/utils/tree.js:
--------------------------------------------------------------------------------
1 | // walk a provide tree, calling fn on each node
2 | // if fn returns a falsy value walk will halt
3 | // and return the current node
4 | export function walk(tree, fn, depth = 0) {
5 | if (!tree) { return; }
6 | fn(tree, depth)
7 | if (tree.children) {
8 | tree.children.forEach((c) => {
9 | walk(c,fn, depth+1)
10 | })
11 | }
12 | }
13 |
14 | export function widestDepthCount(tree) {
15 | let depths = [0];
16 | walk(tree, (node, depth) => {
17 | if (depths.length < depth + 1) {
18 | depths.push(0);
19 | }
20 | depths[depth]++;
21 | });
22 |
23 | return depths.reduce((w, a) => {
24 | return (a > w) ? a : w;
25 | }, 0);
26 | }
27 |
28 | export function maxDepthCount(tree) {
29 | let max = [0];
30 | walk(tree, (node, depth) => {
31 | if (max < depth) {
32 | max = depth;
33 | }
34 | });
35 | return max;
36 | }
37 |
38 | // copy a tree, keeping a branch if the node
39 | // or any children pass the filter test
40 | export function filter(tree, fn, depth = 0) {
41 | if (!tree) { return; }
42 |
43 | // copy the node
44 | let node = Object.assign({}, tree);
45 | let include = fn(node, depth);
46 |
47 | if (node.children) {
48 | node.children = node.children.reduce((children, node) => {
49 | node = filter(node, fn, depth + 1)
50 | if (node) {
51 | include || (include = true);
52 | children.push(node);
53 | }
54 | return children;
55 | }, []);
56 | if (node.children.length === 0) {
57 | node.children = undefined;
58 | }
59 | }
60 |
61 | return include ? node : undefined;
62 | }
63 |
64 | // walk a provide tree, calling fn on each node with a new copy
65 | function copyWalk(tree, fn) {
66 | let copy = fn(copyNode(tree))
67 |
68 | if (copy.children) {
69 | for (let i = 0; i < copy.children.length; i++) {
70 | copy.children[i] = copyWalk(copy.children[i], fn);
71 | }
72 | }
73 |
74 | return copy;
75 | }
76 |
77 | function copyNode(node) {
78 | let copy = Object.assign({}, node);
79 | if (copy.children) {
80 | var a = [];
81 | // TODO - make faster
82 | copy.children.forEach(n => {
83 | a.push(n);
84 | });
85 | copy.children = a;
86 | }
87 |
88 | return copy;
89 | }
--------------------------------------------------------------------------------
/src/js/validators/invite.js:
--------------------------------------------------------------------------------
1 | import Validate from './index';
2 |
3 | export default function validateInvite(invite = {}) {
4 | const errors = {
5 | // invitename : Validate(invite.invitename).required().handle().message(),
6 | // name : Validate(invite.name).required().message(),
7 | username: Validate(invite.username).required().handle().message(),
8 | password: Validate(invite.username).required().message(),
9 | email: Validate(invite.email).required().email().message(),
10 | // description : Validate(invite.description).message()
11 | };
12 |
13 | errors.isValid = Object.keys(errors).every(key => (errors[key] == undefined));
14 | return errors;
15 | }
16 |
--------------------------------------------------------------------------------
/src/js/validators/metadata.js:
--------------------------------------------------------------------------------
1 | import Validate from './index';
2 |
3 | export default function validateMetadata(metadata = {}) {
4 | const errors = {};
5 |
6 | Object.keys(metadata.meta).forEach((fieldName) => {
7 | errors[`${fieldName}_field`] = Validate(fieldName).required().message();
8 | errors[`${fieldName}_value`] = Validate(metadata.meta[fieldName]).required().message();
9 | });
10 |
11 | errors.isValid = Object.keys(errors).every(key => (errors[key] == undefined));
12 | return errors;
13 | }
14 |
--------------------------------------------------------------------------------
/src/js/validators/user.js:
--------------------------------------------------------------------------------
1 | import Validate from './index';
2 |
3 | export default function validateUser(user = {}) {
4 | const errors = {
5 | username: Validate(user.username).required().handle().message(),
6 | name: Validate(user.name).message(),
7 | email: Validate(user.email).required().email().message(),
8 | description: Validate(user.description).message(),
9 | };
10 |
11 | errors.isValid = Object.keys(errors).every(key => (errors[key] == undefined));
12 | return errors;
13 | }
14 |
--------------------------------------------------------------------------------
/src/scss/_alert.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Base styles
3 | //
4 |
5 | .alert {
6 | padding: $alert-padding;
7 | margin-bottom: $spacer-y;
8 | border: $alert-border-width solid transparent;
9 | @include border-radius($alert-border-radius);
10 | }
11 |
12 | // Headings for larger alerts
13 | .alert-heading {
14 | // Specified to prevent conflicts of changing $headings-color
15 | color: inherit;
16 | }
17 |
18 | // Provide class for links that match alerts
19 | .alert-link {
20 | font-weight: $alert-link-font-weight;
21 | }
22 |
23 |
24 | // Dismissible alerts
25 | //
26 | // Expand the right padding and account for the close button's positioning.
27 |
28 | .alert-dismissible {
29 | padding-right: ($alert-padding + 20px);
30 |
31 | // Adjust close link position
32 | .close {
33 | position: relative;
34 | top: -2px;
35 | right: -21px;
36 | color: inherit;
37 | }
38 | }
39 |
40 |
41 | // Alternate styles
42 | //
43 | // Generate contextual modifier classes for colorizing the alert.
44 |
45 | .alert-success {
46 | @include alert-variant($alert-success-bg, $alert-success-border, $alert-success-text);
47 | }
48 | .alert-info {
49 | @include alert-variant($alert-info-bg, $alert-info-border, $alert-info-text);
50 | }
51 | .alert-warning {
52 | @include alert-variant($alert-warning-bg, $alert-warning-border, $alert-warning-text);
53 | }
54 | .alert-danger {
55 | @include alert-variant($alert-danger-bg, $alert-danger-border, $alert-danger-text);
56 | }
57 |
--------------------------------------------------------------------------------
/src/scss/_components.scss:
--------------------------------------------------------------------------------
1 | @import "components/CodeEditor";
2 | @import "components/DateInput";
3 | @import "components/Footer";
4 | @import "components/ForceGraph";
5 | @import "components/Items";
6 | @import "components/Login";
7 | @import "components/MainMenu";
8 | @import "components/MonthCalendar";
9 | @import "components/Navbar";
10 | @import "components/ProgressBar";
11 | @import "components/Signup";
12 | @import "components/Spinner";
13 | @import "components/TabBar";
14 | @import "components/View";
15 |
16 |
17 | .item {
18 | margin-bottom: 8px;
19 | }
20 |
21 | .card {
22 | overflow: hidden;
23 | min-height: 260px;
24 | padding-top: 0;
25 | margin-bottom: 1.5em;
26 | margin-top: 1em;
27 | border-radius: 5px;
28 | box-shadow: 0 0 3px rgba(0,0,0,0.15);
29 | background: white;
30 |
31 | header {
32 | display: block;
33 | min-height: 150px;
34 | padding: 1.5em 1em 0 1em;
35 | margin-top: 0;
36 | color: white;
37 | position: relative;
38 | h3, h4 {
39 | color: white;
40 | position: absolute;
41 | bottom: 5px;
42 | }
43 | }
44 | .info {
45 | padding: 1em;
46 | }
47 | }
--------------------------------------------------------------------------------
/src/scss/_containers.scss:
--------------------------------------------------------------------------------
1 | @import "containers/App";
2 | @import "containers/Collection";
3 | @import "containers/Home";
4 | @import "containers/User";
5 |
6 | .page {
7 | margin-top: 3em;
8 | margin-bottom: 8em;
9 | header {
10 | padding-bottom: 2em;
11 | margin-bottom: 2em;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/scss/_grid.scss:
--------------------------------------------------------------------------------
1 | // Container widths
2 | //
3 | // Set the container width, and override it for fixed navbars in media queries.
4 |
5 | @if $enable-grid-classes {
6 | .container {
7 | @include make-container();
8 | @include make-container-max-widths();
9 | }
10 | }
11 |
12 | // Fluid container
13 | //
14 | // Utilizes the mixin meant for fixed width containers, but without any defined
15 | // width for fluid, full width layouts.
16 |
17 | @if $enable-grid-classes {
18 | .container-fluid {
19 | @include make-container();
20 | }
21 | }
22 |
23 | // Row
24 | //
25 | // Rows contain and clear the floats of your columns.
26 |
27 | @if $enable-grid-classes {
28 | .row {
29 | @include make-row();
30 | }
31 | }
32 |
33 | // Columns
34 | //
35 | // Common styles for small and large grid columns
36 |
37 | @if $enable-grid-classes {
38 | @include make-grid-columns();
39 | }
40 |
--------------------------------------------------------------------------------
/src/scss/_hacks.scss:
--------------------------------------------------------------------------------
1 | // Place Quick hacks that need to be folded properly
2 | // in to css here. Ideally this file is empty.
3 |
4 | .helpToggle {
5 | font-weight: 400;
6 | }
7 |
8 | .help.hint {
9 | display: block;
10 | font-size: 12px;
11 | line-height: 1.3em;
12 | margin-top: 5px;
13 | margin-bottom: 5px;
14 | }
15 |
16 | .control-label {
17 | font-weight: bold;
18 | }
19 |
20 | .primer.item {
21 | min-height: 380px;
22 | }
--------------------------------------------------------------------------------
/src/scss/_mixins.scss:
--------------------------------------------------------------------------------
1 | // Toggles
2 | //
3 | // Used in conjunction with global variables to enable certain theme features.
4 |
5 | @mixin box-shadow($shadow...) {
6 | @if $enable-shadows {
7 | box-shadow: $shadow;
8 | }
9 | }
10 |
11 | @mixin transition($transition...) {
12 | @if $enable-transitions {
13 | transition: $transition;
14 | }
15 | }
16 |
17 | // Utilities
18 | @import "mixins/breakpoints";
19 | @import "mixins/hover";
20 | @import "mixins/image";
21 | @import "mixins/tag";
22 | @import "mixins/reset-filter";
23 | @import "mixins/resize";
24 | @import "mixins/screen-reader";
25 | @import "mixins/size";
26 | @import "mixins/tab-focus";
27 | @import "mixins/reset-text";
28 | @import "mixins/text-emphasis";
29 | @import "mixins/text-hide";
30 | @import "mixins/text-truncate";
31 |
32 | // // Components
33 | @import "mixins/alert";
34 | @import "mixins/buttons";
35 | @import "mixins/cards";
36 | @import "mixins/pagination";
37 | @import "mixins/lists";
38 | @import "mixins/list-group";
39 | @import "mixins/nav-divider";
40 | @import "mixins/forms";
41 | @import "mixins/progress";
42 | @import "mixins/table-row";
43 |
44 | // // Skins
45 | @import "mixins/background-variant";
46 | @import "mixins/border-radius";
47 | @import "mixins/gradients";
48 |
49 | // // Layout
50 | @import "mixins/clearfix";
51 | // @import "mixins/navbar-align";
52 | @import "mixins/grid-framework";
53 | @import "mixins/grid";
54 | @import "mixins/pulls";
55 |
--------------------------------------------------------------------------------
/src/scss/_modal.scss:
--------------------------------------------------------------------------------
1 | #modal-wrap {
2 | background: rgba(0,0,0,0.45);
3 | width: 100%;
4 | height: 100%;
5 | position: fixed;
6 | top: 0;
7 | left: 0;
8 |
9 | .modal {
10 | z-index: 10000;
11 | box-shadow: 0 0 8px rgba(0,0,0,0.3);
12 | background: $slate;
13 | border-radius: 6px;
14 | padding: 15px;
15 | min-width: 280px;
16 | max-width: 600px;
17 | position: absolute;
18 | width: 35%;
19 | height: 400px;
20 | overflow-y: auto;
21 | margin: 4.5rem auto auto auto;
22 | top: 0;
23 | bottom: 0;
24 | left: 0;
25 | right: 0;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/scss/_utilities.scss:
--------------------------------------------------------------------------------
1 | @import "utilities/background";
2 | @import "utilities/clearfix";
3 | @import "utilities/display";
4 | @import "utilities/flex";
5 | @import "utilities/pulls";
6 | @import "utilities/screenreaders";
7 | @import "utilities/spacing";
8 | @import "utilities/text";
9 | @import "utilities/visibility";
10 |
--------------------------------------------------------------------------------
/src/scss/components/_CodeEditor.scss:
--------------------------------------------------------------------------------
1 | .codeEditor.wrap {
2 | position: relative;
3 | width: 100%;
4 | margin-top: 15px;
5 | margin-bottom: 15px;
6 | min-height: 180px;
7 |
8 | .editor {
9 | position: absolute;
10 | top: 0;
11 | right: 0;
12 | left: 0;
13 | min-height: 180px;
14 | background: #2A2E2E;
15 | border-radius: 3px;
16 | }
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/src/scss/components/_DateInput.scss:
--------------------------------------------------------------------------------
1 | .dateInput {
2 | position: relative;
3 | .calendar {
4 | position: absolute;
5 | top: 40px;
6 | left: 0;
7 | z-index: 2;
8 | padding: 4px 8px;
9 | box-shadow: 0 0 4px rgba(0,0,0,0.45);
10 | border-radius: 3px;
11 | }
12 | }
--------------------------------------------------------------------------------
/src/scss/components/_Footer.scss:
--------------------------------------------------------------------------------
1 | #footer {
2 | margin-top: 6em;
3 | padding: 2em 0;
4 | background: $cool-slate;
5 | min-height: 200px;
6 | box-shadow: inset 0 0 8px rgba(0,0,0,0.35);
7 | a {
8 | color: #3BB563;
9 | text-transform: uppercase;
10 | font-weight: bold;
11 | }
12 | }
--------------------------------------------------------------------------------
/src/scss/components/_Items.scss:
--------------------------------------------------------------------------------
1 | .user.item {
2 | min-height: 60px;
3 | margin-top: 15px;
4 | margin-bottom: 15px;
5 | .profile-photo {
6 | max-width: 60px;
7 | max-height: 60px;
8 | float: left;
9 | border-radius: 3px;
10 | margin-right: 8px;
11 | overflow: hidden;
12 | img {
13 | width: 100%;
14 | }
15 | }
16 | }
17 |
18 | .collection.item {
19 | margin-bottom: 1em;
20 | margin-top: 1em;
21 | }
--------------------------------------------------------------------------------
/src/scss/components/_MainMenu.scss:
--------------------------------------------------------------------------------
1 | #main_menu {
2 | width: 300px;
3 | background: $slate;
4 | box-shadow: -1 0 8px rgba(0,0,0,0.45);
5 | position: fixed;
6 | transition: right 0.15s;
7 | top: 50px;
8 | right: -100%;
9 | height: 100%;
10 | z-index: 2;
11 | a {
12 | border-bottom: 1px solid rgba(0,0,0,0.25);
13 | display: block;
14 | text-transform: uppercase;
15 | padding: 1em;
16 | letter-spacing: 1px;
17 | font-weight: 500;
18 | }
19 | a:hover {
20 | background-color: rgba(0,0,0,0.2);
21 | }
22 |
23 | a.section-title {
24 | text-transform: uppercase;
25 | font-weight: bold;
26 | letter-spacing: 2px;
27 | padding: 1em;
28 | background: rgba(0,0,0,0.2);
29 | }
30 |
31 | a.sub {
32 | padding: 0.75em 1em 0.75em 2em;
33 | }
34 | }
35 |
36 | #main_menu.hide {
37 | right: -100%;
38 | }
39 | #main_menu.show {
40 | right: 0;
41 | }
--------------------------------------------------------------------------------
/src/scss/components/_MonthCalendar.scss:
--------------------------------------------------------------------------------
1 | .calendar {
2 | width: 300px;
3 | background: white;
4 | .header {
5 | background: transparent;
6 | box-shadow: none;
7 | .backButton {
8 | float: left;
9 | }
10 | .month {
11 | text-align: center;
12 | }
13 | .year {
14 | text-align: center;
15 | }
16 | .nextButton {
17 | float: right;
18 | }
19 | }
20 | table.dates {
21 | width: 100%;
22 | td, th {
23 | text-align: center;
24 | min-width: 28px;
25 | min-height: 28px;
26 | color: rgb(180,180,180);
27 | }
28 | td.current {
29 | color: rgb(60,60,60);
30 | }
31 | td.selected {
32 | color: rgb(200,0,0);
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/src/scss/components/_Navbar.scss:
--------------------------------------------------------------------------------
1 | #navbar {
2 | z-index: 3;
3 | padding: 10px 0;
4 | background: $slate;
5 |
6 | #logotype {
7 | color: white;
8 | font-weight: 700;
9 | letter-spacing: 1px;
10 | font-size: 1.3em;
11 | padding-top: 0;
12 | text-decoration: none;
13 | }
14 |
15 | .alpha {
16 | text-align: center;
17 | letter-spacing: 1px;
18 | color: $slate;
19 | }
20 |
21 | .menu {
22 | text-align: right;
23 | padding-top: 3px;
24 | a {
25 | margin-right: 16px;
26 | letter-spacing: 2px;
27 | text-transform: uppercase;
28 | font-weight: 500;
29 | font-size: 14px;
30 | color: $green;
31 | }
32 | }
33 | }
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/scss/components/_ProgressBar.scss:
--------------------------------------------------------------------------------
1 | .progressbar {
2 | width: 100%;
3 | border-radius: 2px;
4 | background: rgba(0,0,0,0.25);
5 | position: relative;
6 | height: 10px;
7 | overflow: hidden;
8 |
9 | .progress {
10 | position: absolute;
11 | height: 100%;
12 | }
13 |
14 | &.standard {
15 | margin-top: 0.2em;
16 | margin-bottom: 0.8em;
17 | }
18 |
19 | &.micro {
20 | height: 5px;
21 | max-width: 200px;
22 | }
23 | }
--------------------------------------------------------------------------------
/src/scss/components/_Signup.scss:
--------------------------------------------------------------------------------
1 | #signup {
2 | .form {
3 | margin-top: 3em;
4 | padding: 1em 1em 1.5em 1em;
5 | .signup.btn {
6 | width: 100%;
7 | margin-bottom: 15px;
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/src/scss/components/_Spinner.scss:
--------------------------------------------------------------------------------
1 | .spinner {
2 | margin: 100px auto;
3 | width: 50px;
4 | height: 40px;
5 | text-align: center;
6 | font-size: 10px;
7 | }
8 |
9 | .spinner > div {
10 | background-color: #A1B2BC;
11 | height: 100%;
12 | width: 6px;
13 | display: inline-block;
14 |
15 | -webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
16 | animation: sk-stretchdelay 1.2s infinite ease-in-out;
17 | }
18 |
19 | .spinner .rect2 {
20 | -webkit-animation-delay: -1.1s;
21 | animation-delay: -1.1s;
22 | }
23 |
24 | .spinner .rect3 {
25 | -webkit-animation-delay: -1.0s;
26 | animation-delay: -1.0s;
27 | }
28 |
29 | .spinner .rect4 {
30 | -webkit-animation-delay: -0.9s;
31 | animation-delay: -0.9s;
32 | }
33 |
34 | .spinner .rect5 {
35 | -webkit-animation-delay: -0.8s;
36 | animation-delay: -0.8s;
37 | }
38 |
39 | @-webkit-keyframes sk-stretchdelay {
40 | 0%, 40%, 100% { -webkit-transform: scaleY(0.4) }
41 | 20% { -webkit-transform: scaleY(1.0) }
42 | }
43 |
44 | @keyframes sk-stretchdelay {
45 | 0%, 40%, 100% {
46 | transform: scaleY(0.4);
47 | -webkit-transform: scaleY(0.4);
48 | } 20% {
49 | transform: scaleY(1.0);
50 | -webkit-transform: scaleY(1.0);
51 | }
52 | }
--------------------------------------------------------------------------------
/src/scss/components/_TabBar.scss:
--------------------------------------------------------------------------------
1 | .tab.bar a {
2 | margin-right: 1.5em;
3 | text-transform: uppercase;
4 | opacity: 0.5;
5 | font-weight: 500;
6 | &.current {
7 | opacity: 1.0;
8 | }
9 | }
--------------------------------------------------------------------------------
/src/scss/components/_TabPanel.scss:
--------------------------------------------------------------------------------
1 | header {
2 | padding: 15px 0 15px 0;
3 |
4 | .tab {
5 | text-transform: uppercase;
6 | font-style: oblique;
7 | font-weight: 700;
8 | margin: 2px 20px 2px 0;
9 | opacity: 0.25;
10 | font-size: 18px;
11 | }
12 |
13 | .tab.current {
14 | opacity: 1.0;
15 | }
16 | }
--------------------------------------------------------------------------------
/src/scss/components/_forceGraph.scss:
--------------------------------------------------------------------------------
1 | .rv-force__interactive .rv-force__node {
2 | cursor: pointer;
3 | }
4 |
5 | .rv-force__label {
6 | pointer-events: none;
7 | text-transform: none;
8 | letter-spacing: 0;
9 | }
10 |
11 | .rv-force__label::selection {
12 | background: none;
13 | }
14 |
--------------------------------------------------------------------------------
/src/scss/components/_login.scss:
--------------------------------------------------------------------------------
1 | #login {
2 | .form {
3 | margin-top: 3em;
4 | // padding: 1em 1em 1.5em 1em;
5 | // border-radius: 5px;
6 | // box-shadow: 0 0 4px rgba(0,0,0,0.5);
7 | .login.btn {
8 | width: 100%;
9 | margin-bottom: 15px;
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/scss/components/_view.scss:
--------------------------------------------------------------------------------
1 | .chart.view {
2 | .recharts-text {
3 | fill : $slate;
4 | }
5 | .recharts-cartesian-axis-line {
6 | stroke: $slate;
7 | }
8 | .recharts-cartesian-axis-tick-line {
9 | stroke: $slate;
10 | }
11 | .recharts-bar-cursor {
12 | fill: $slate;
13 | }
14 | }
15 |
16 | .value.list.view {
17 | margin-top: 2.0rem;
18 | margin-bottom: 2.0rem;
19 | // border-top: 1px solid $slate;
20 | // border-bottom: 1px solid $slate;
21 | // background: $slate;
22 | border-radius: 3px;
23 | padding-top: 1.0rem;
24 |
25 | .value {
26 | text-align: left;
27 | color: $green;
28 | }
29 | }
--------------------------------------------------------------------------------
/src/scss/containers/_App.scss:
--------------------------------------------------------------------------------
1 | .alert {
2 | margin-top: 3em;
3 | z-index: 2;
4 | margin-bottom: 0;
5 |
6 | .message {
7 | margin-top: 0;
8 | margin-bottom: 0;
9 | }
10 | .dismiss {
11 | color: $background-text;
12 | font-weight: 500;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/scss/containers/_Collection.scss:
--------------------------------------------------------------------------------
1 | #collection {
2 | table.collection.items.table th {
3 | border-top: 0.5px solid transparent;
4 | }
5 | table.collection.items.table tbody {
6 | border-bottom: 0.5px solid rgba(0,0,0,0.1);
7 | }
8 | table.collection.items.table tbody td {
9 | // border-color: rgba(0,0,0,0.3);
10 | border-top: 0.5px solid rgba(0,0,0,0.1);
11 | // border-bottom: 0.5px solid rgba(0,0,0,0.3);
12 | }
13 | }
--------------------------------------------------------------------------------
/src/scss/containers/_Home.scss:
--------------------------------------------------------------------------------
1 | #home {
2 | margin: 2em 0 0 0;
3 | padding-bottom: 0;
4 | background: $slate;
5 |
6 | .masthead {
7 | // background: top center url("https://s3.us-east-2.amazonaws.com/static.archivers.space/clouds.png") repeat-x;
8 | background: top center url("https://s3.amazonaws.com/datatogether/svg/lines_bg.svg") repeat-x;
9 | padding-top: 50px;
10 | min-height: 650px;
11 | position: relative;
12 | .tagline {
13 | text-align: center;
14 | color: white;
15 | margin: 5em auto 0 auto;
16 | max-width: 60%;
17 | font-size: 3em;
18 | line-height: 1.45em;
19 | }
20 | .left-lines {
21 | position: absolute;
22 | left: 0;
23 | top: 12%;
24 | max-width: 200px;
25 | }
26 | .right-lines {
27 | position: absolute;
28 | right: 0;
29 | top: 40%;
30 | max-width: 300px;
31 | }
32 | // img {
33 | // width: 100%;
34 | // max-width: 1200px;
35 | // margin: 0 0 0 auto;
36 | // display: block;
37 | // }
38 | }
39 |
40 | .signup {
41 | margin-top: 10em;
42 | .text {
43 | margin-top: 35px;
44 | margin-bottom: 35px;
45 | color: white;
46 | }
47 | .image {
48 | text-align: center;
49 | img {
50 | margin: 0 auto;
51 | max-width: 300px;
52 | }
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/src/scss/containers/_User.scss:
--------------------------------------------------------------------------------
1 | #user {
2 | .profile_photo {
3 | max-width: 170px;
4 | border-radius: 3px;
5 | overflow: hidden;
6 | img {
7 | width: 100%;
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/src/scss/mixins/_alert.scss:
--------------------------------------------------------------------------------
1 | // Alerts
2 |
3 | @mixin alert-variant($background, $border, $body-color) {
4 | background-color: $background;
5 | border-color: $border;
6 | color: $body-color;
7 |
8 | hr {
9 | border-top-color: darken($border, 5%);
10 | }
11 | .alert-link {
12 | color: darken($body-color, 10%);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/scss/mixins/_background-variant.scss:
--------------------------------------------------------------------------------
1 | // Contextual backgrounds
2 |
3 | @mixin bg-variant($parent, $color) {
4 | #{$parent} {
5 | color: #fff !important;
6 | background-color: $color !important;
7 | }
8 | a#{$parent} {
9 | @include hover-focus {
10 | background-color: darken($color, 10%) !important;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/scss/mixins/_border-radius.scss:
--------------------------------------------------------------------------------
1 | // Single side border-radius
2 |
3 | @mixin border-radius($radius: $border-radius) {
4 | @if $enable-rounded {
5 | border-radius: $radius;
6 | }
7 | }
8 |
9 | @mixin border-top-radius($radius) {
10 | @if $enable-rounded {
11 | border-top-right-radius: $radius;
12 | border-top-left-radius: $radius;
13 | }
14 | }
15 |
16 | @mixin border-right-radius($radius) {
17 | @if $enable-rounded {
18 | border-bottom-right-radius: $radius;
19 | border-top-right-radius: $radius;
20 | }
21 | }
22 |
23 | @mixin border-bottom-radius($radius) {
24 | @if $enable-rounded {
25 | border-bottom-right-radius: $radius;
26 | border-bottom-left-radius: $radius;
27 | }
28 | }
29 |
30 | @mixin border-left-radius($radius) {
31 | @if $enable-rounded {
32 | border-bottom-left-radius: $radius;
33 | border-top-left-radius: $radius;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/scss/mixins/_cards.scss:
--------------------------------------------------------------------------------
1 | // Card variants
2 |
3 | @mixin card-variant($background, $border) {
4 | background-color: $background;
5 | border-color: $border;
6 |
7 | .card-header,
8 | .card-footer {
9 | background-color: transparent;
10 | }
11 | }
12 |
13 | @mixin card-outline-variant($color) {
14 | background-color: transparent;
15 | border-color: $color;
16 | }
17 |
18 | //
19 | // Inverse text within a card for use with dark backgrounds
20 | //
21 |
22 | @mixin card-inverse {
23 | .card-header,
24 | .card-footer {
25 | border-color: rgba(255,255,255,.2);
26 | }
27 | .card-header,
28 | .card-footer,
29 | .card-title,
30 | .card-blockquote {
31 | color: #fff;
32 | }
33 | .card-link,
34 | .card-text,
35 | .card-subtitle,
36 | .card-blockquote .blockquote-footer {
37 | color: rgba(255,255,255,.65);
38 | }
39 | .card-link {
40 | @include hover-focus {
41 | color: $card-link-hover-color;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/scss/mixins/_clearfix.scss:
--------------------------------------------------------------------------------
1 | @mixin clearfix() {
2 | &::after {
3 | content: "";
4 | display: table;
5 | clear: both;
6 | }
7 | }
--------------------------------------------------------------------------------
/src/scss/mixins/_gradients.scss:
--------------------------------------------------------------------------------
1 | // Gradients
2 |
3 | // Horizontal gradient, from left to right
4 | //
5 | // Creates two color stops, start and end, by specifying a color and position for each color stop.
6 | // Color stops are not available in IE9.
7 | @mixin gradient-x($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) {
8 | background-image: linear-gradient(to right, $start-color $start-percent, $end-color $end-percent);
9 | background-repeat: repeat-x;
10 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start-color)}', endColorstr='#{ie-hex-str($end-color)}', GradientType=1); // IE9
11 | }
12 |
13 | // Vertical gradient, from top to bottom
14 | //
15 | // Creates two color stops, start and end, by specifying a color and position for each color stop.
16 | // Color stops are not available in IE9.
17 | @mixin gradient-y($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) {
18 | background-image: linear-gradient(to bottom, $start-color $start-percent, $end-color $end-percent);
19 | background-repeat: repeat-x;
20 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start-color)}', endColorstr='#{ie-hex-str($end-color)}', GradientType=0); // IE9
21 | }
22 |
23 | @mixin gradient-directional($start-color: #555, $end-color: #333, $deg: 45deg) {
24 | background-repeat: repeat-x;
25 | background-image: linear-gradient($deg, $start-color, $end-color);
26 | }
27 | @mixin gradient-x-three-colors($start-color: #00b3ee, $mid-color: #7a43b6, $color-stop: 50%, $end-color: #c3325f) {
28 | background-image: linear-gradient(to right, $start-color, $mid-color $color-stop, $end-color);
29 | background-repeat: no-repeat;
30 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start-color)}', endColorstr='#{ie-hex-str($end-color)}', GradientType=1); // IE9 gets no color-stop at all for proper fallback
31 | }
32 | @mixin gradient-y-three-colors($start-color: #00b3ee, $mid-color: #7a43b6, $color-stop: 50%, $end-color: #c3325f) {
33 | background-image: linear-gradient($start-color, $mid-color $color-stop, $end-color);
34 | background-repeat: no-repeat;
35 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start-color)}', endColorstr='#{ie-hex-str($end-color)}', GradientType=0); // IE9 gets no color-stop at all for proper fallback
36 | }
37 | @mixin gradient-radial($inner-color: #555, $outer-color: #333) {
38 | background-image: radial-gradient(circle, $inner-color, $outer-color);
39 | background-repeat: no-repeat;
40 | }
41 | @mixin gradient-striped($color: rgba(255,255,255,.15), $angle: 45deg) {
42 | background-image: linear-gradient($angle, $color 25%, transparent 25%, transparent 50%, $color 50%, $color 75%, transparent 75%, transparent);
43 | }
--------------------------------------------------------------------------------
/src/scss/mixins/_grid-framework.scss:
--------------------------------------------------------------------------------
1 | // Framework grid generation
2 | //
3 | // Used only by Bootstrap to generate the correct number of grid classes given
4 | // any value of `$grid-columns`.
5 |
6 | @mixin make-grid-columns($columns: $grid-columns, $gutter: $grid-gutter-width, $breakpoints: $grid-breakpoints) {
7 | $breakpoint-counter: 0;
8 | @each $breakpoint in map-keys($breakpoints) {
9 | $breakpoint-counter: ($breakpoint-counter + 1);
10 | @include media-breakpoint-up($breakpoint, $breakpoints) {
11 | @if $enable-flex {
12 | .col-#{$breakpoint} {
13 | position: relative;
14 | flex-basis: 0;
15 | flex-grow: 1;
16 | max-width: 100%;
17 | min-height: 1px;
18 | padding-right: ($gutter / 2);
19 | padding-left: ($gutter / 2);
20 | }
21 | }
22 |
23 | @for $i from 1 through $columns {
24 | .col-#{$breakpoint}-#{$i} {
25 | @include make-col($i, $columns, $gutter);
26 | }
27 | }
28 |
29 | @each $modifier in (pull, push) {
30 | @for $i from 0 through $columns {
31 | .#{$modifier}-#{$breakpoint}-#{$i} {
32 | @include make-col-modifier($modifier, $i, $columns)
33 | }
34 | }
35 | }
36 |
37 | // `$columns - 1` because offsetting by the width of an entire row isn't possible
38 | @for $i from 0 through ($columns - 1) {
39 | @if $breakpoint-counter != 1 or $i != 0 { // Avoid emitting useless .offset-xs-0
40 | .offset-#{$breakpoint}-#{$i} {
41 | @include make-col-modifier(offset, $i, $columns)
42 | }
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/scss/mixins/_grid.scss:
--------------------------------------------------------------------------------
1 | /// Grid system
2 | //
3 | // Generate semantic grid columns with these mixins.
4 |
5 | @mixin make-container($gutter: $grid-gutter-width) {
6 | margin-left: auto;
7 | margin-right: auto;
8 | padding-left: ($gutter / 2);
9 | padding-right: ($gutter / 2);
10 | @if not $enable-flex {
11 | @include clearfix();
12 | }
13 | }
14 |
15 |
16 | // For each breakpoint, define the maximum width of the container in a media query
17 | @mixin make-container-max-widths($max-widths: $container-max-widths, $breakpoints: $grid-breakpoints) {
18 | @each $breakpoint, $container-max-width in $max-widths {
19 | @include media-breakpoint-up($breakpoint, $breakpoints) {
20 | max-width: $container-max-width;
21 | }
22 | }
23 | }
24 |
25 | @mixin make-row($gutter: $grid-gutter-width) {
26 | @if $enable-flex {
27 | display: flex;
28 | flex-wrap: wrap;
29 | } @else {
30 | @include clearfix();
31 | }
32 | margin-left: ($gutter / -2);
33 | margin-right: ($gutter / -2);
34 | }
35 |
36 | @mixin make-col($size, $columns: $grid-columns, $gutter: $grid-gutter-width) {
37 | position: relative;
38 | min-height: 1px;
39 | padding-right: ($gutter / 2);
40 | padding-left: ($gutter / 2);
41 |
42 | @if $enable-flex {
43 | flex: 0 0 percentage($size / $columns);
44 | // Add a `max-width` to ensure content within each column does not blow out
45 | // the width of the column. Applies to IE10+ and Firefox. Chrome and Safari
46 | // do not appear to require this.
47 | max-width: percentage($size / $columns);
48 | } @else {
49 | float: left;
50 | width: percentage($size / $columns);
51 | }
52 | }
53 |
54 | @mixin make-col-offset($size, $columns: $grid-columns) {
55 | margin-left: percentage($size / $columns);
56 | }
57 |
58 | @mixin make-col-push($size, $columns: $grid-columns) {
59 | left: if($size > 0, percentage($size / $columns), auto);
60 | }
61 |
62 | @mixin make-col-pull($size, $columns: $grid-columns) {
63 | right: if($size > 0, percentage($size / $columns), auto);
64 | }
65 |
66 | @mixin make-col-modifier($type, $size, $columns) {
67 | // Work around the lack of dynamic mixin @include support (https://github.com/sass/sass/issues/626)
68 | @if $type == push {
69 | @include make-col-push($size, $columns);
70 | } @else if $type == pull {
71 | @include make-col-pull($size, $columns);
72 | } @else if $type == offset {
73 | @include make-col-offset($size, $columns);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/scss/mixins/_hover.scss:
--------------------------------------------------------------------------------
1 | @mixin hover {
2 | @if $enable-hover-media-query {
3 | // See Media Queries Level 4: http://drafts.csswg.org/mediaqueries/#hover
4 | // Currently shimmed by https://github.com/twbs/mq4-hover-shim
5 | @media (hover: hover) {
6 | &:hover { @content }
7 | }
8 | }
9 | @else {
10 | &:hover { @content }
11 | }
12 | }
13 |
14 | @mixin hover-focus {
15 | @if $enable-hover-media-query {
16 | &:focus { @content }
17 | @include hover { @content }
18 | }
19 | @else {
20 | &:focus,
21 | &:hover {
22 | @content
23 | }
24 | }
25 | }
26 |
27 | @mixin plain-hover-focus {
28 | @if $enable-hover-media-query {
29 | &,
30 | &:focus {
31 | @content
32 | }
33 | @include hover { @content }
34 | }
35 | @else {
36 | &,
37 | &:focus,
38 | &:hover {
39 | @content
40 | }
41 | }
42 | }
43 |
44 | @mixin hover-focus-active {
45 | @if $enable-hover-media-query {
46 | &:focus,
47 | &:active {
48 | @content
49 | }
50 | @include hover { @content }
51 | }
52 | @else {
53 | &:focus,
54 | &:active,
55 | &:hover {
56 | @content
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/scss/mixins/_image.scss:
--------------------------------------------------------------------------------
1 | // Image Mixins
2 | // - Responsive image
3 | // - Retina image
4 |
5 |
6 | // Responsive image
7 | //
8 | // Keep images from scaling beyond the width of their parents.
9 |
10 | @mixin img-fluid($display: block) {
11 | display: $display;
12 | max-width: 100%; // Part 1: Set a maximum relative to the parent
13 | height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching
14 | }
15 |
16 |
17 | // Retina image
18 | //
19 | // Short retina mixin for setting background-image and -size.
20 |
21 | @mixin img-retina($file-1x, $file-2x, $width-1x, $height-1x) {
22 | background-image: url($file-1x);
23 |
24 | // Autoprefixer takes care of adding -webkit-min-device-pixel-ratio and -o-min-device-pixel-ratio,
25 | // but doesn't convert dppx=>dpi.
26 | // There's no such thing as unprefixed min-device-pixel-ratio since it's nonstandard.
27 | // Compatibility info: http://caniuse.com/#feat=css-media-resolution
28 | @media
29 | only screen and (min-resolution: 192dpi), // IE9-11 don't support dppx
30 | only screen and (min-resolution: 2dppx) { // Standardized
31 | background-image: url($file-2x);
32 | background-size: $width-1x $height-1x;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/scss/mixins/_list-group.scss:
--------------------------------------------------------------------------------
1 | // List Groups
2 |
3 | @mixin list-group-item-variant($state, $background, $color) {
4 | .list-group-item-#{$state} {
5 | color: $color;
6 | background-color: $background;
7 | }
8 |
9 | a.list-group-item-#{$state},
10 | button.list-group-item-#{$state} {
11 | color: $color;
12 |
13 | .list-group-item-heading {
14 | color: inherit;
15 | }
16 |
17 | @include hover-focus {
18 | color: $color;
19 | background-color: darken($background, 5%);
20 | }
21 |
22 | &.active {
23 | @include plain-hover-focus {
24 | color: #fff;
25 | background-color: $color;
26 | border-color: $color;
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/scss/mixins/_lists.scss:
--------------------------------------------------------------------------------
1 | // Lists
2 |
3 | // Unstyled keeps list items block level, just removes default browser padding and list-style
4 | @mixin list-unstyled {
5 | padding-left: 0;
6 | list-style: none;
7 | }
8 |
--------------------------------------------------------------------------------
/src/scss/mixins/_nav-divider.scss:
--------------------------------------------------------------------------------
1 | // Horizontal dividers
2 | //
3 | // Dividers (basically an hr) within dropdowns and nav lists
4 |
5 | @mixin nav-divider($color: #e5e5e5) {
6 | height: 1px;
7 | margin: ($spacer-y / 2) 0;
8 | overflow: hidden;
9 | background-color: $color;
10 | }
11 |
--------------------------------------------------------------------------------
/src/scss/mixins/_navbar-align.scss:
--------------------------------------------------------------------------------
1 | // Navbar vertical align
2 | //
3 | // Vertically center elements in the navbar.
4 | // Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.
5 |
6 | // @mixin navbar-vertical-align($element-height) {
7 | // margin-top: (($navbar-height - $element-height) / 2);
8 | // margin-bottom: (($navbar-height - $element-height) / 2);
9 | // }
10 |
--------------------------------------------------------------------------------
/src/scss/mixins/_pagination.scss:
--------------------------------------------------------------------------------
1 | // Pagination
2 |
3 | @mixin pagination-size($padding-y, $padding-x, $font-size, $line-height, $border-radius) {
4 | .page-link {
5 | padding: $padding-y $padding-x;
6 | font-size: $font-size;
7 | }
8 |
9 | .page-item {
10 | &:first-child {
11 | .page-link {
12 | @include border-left-radius($border-radius);
13 | }
14 | }
15 | &:last-child {
16 | .page-link {
17 | @include border-right-radius($border-radius);
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/scss/mixins/_progress.scss:
--------------------------------------------------------------------------------
1 | // Progress bars
2 |
3 | @mixin progress-variant($color) {
4 | &[value]::-webkit-progress-value {
5 | background-color: $color;
6 | }
7 |
8 | &[value]::-moz-progress-bar {
9 | background-color: $color;
10 | }
11 |
12 | // IE10+, Microsoft Edge
13 | &[value]::-ms-fill {
14 | background-color: $color;
15 | }
16 |
17 | // IE9
18 | @media screen and (min-width:0\0) {
19 | .progress-bar {
20 | background-color: $color;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/scss/mixins/_pulls.scss:
--------------------------------------------------------------------------------
1 | @mixin pull-left {
2 | float: left !important;
3 | }
4 | @mixin pull-right {
5 | float: right !important;
6 | }
7 |
--------------------------------------------------------------------------------
/src/scss/mixins/_reset-filter.scss:
--------------------------------------------------------------------------------
1 | // Reset filters for IE
2 | //
3 | // When you need to remove a gradient background, do not forget to use this to reset
4 | // the IE filter for IE9.
5 |
6 | @mixin reset-filter() {
7 | filter: "progid:DXImageTransform.Microsoft.gradient(enabled = false)";
8 | }
9 |
--------------------------------------------------------------------------------
/src/scss/mixins/_reset-text.scss:
--------------------------------------------------------------------------------
1 | @mixin reset-text {
2 | font-family: $font-family-base;
3 | // We deliberately do NOT reset font-size or word-wrap.
4 | font-style: normal;
5 | font-weight: normal;
6 | letter-spacing: normal;
7 | line-break: auto;
8 | line-height: $line-height-base;
9 | text-align: left; // Fallback for where `start` is not supported
10 | text-align: start;
11 | text-decoration: none;
12 | text-shadow: none;
13 | text-transform: none;
14 | white-space: normal;
15 | word-break: normal;
16 | word-spacing: normal;
17 | }
18 |
--------------------------------------------------------------------------------
/src/scss/mixins/_resize.scss:
--------------------------------------------------------------------------------
1 | // Resize anything
2 |
3 | @mixin resizable($direction) {
4 | resize: $direction; // Options: horizontal, vertical, both
5 | overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible`
6 | }
7 |
--------------------------------------------------------------------------------
/src/scss/mixins/_screen-reader.scss:
--------------------------------------------------------------------------------
1 | // Only display content to screen readers
2 | //
3 | // See: http://a11yproject.com/posts/how-to-hide-content
4 |
5 | @mixin sr-only {
6 | position: absolute;
7 | width: 1px;
8 | height: 1px;
9 | padding: 0;
10 | margin: -1px;
11 | overflow: hidden;
12 | clip: rect(0,0,0,0);
13 | border: 0;
14 | }
15 |
16 | // Use in conjunction with .sr-only to only display content when it's focused.
17 | //
18 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
19 | //
20 | // Credit: HTML5 Boilerplate
21 |
22 | @mixin sr-only-focusable {
23 | &:active,
24 | &:focus {
25 | position: static;
26 | width: auto;
27 | height: auto;
28 | margin: 0;
29 | overflow: visible;
30 | clip: auto;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/scss/mixins/_size.scss:
--------------------------------------------------------------------------------
1 | // Sizing shortcuts
2 |
3 | @mixin size($width, $height: $width) {
4 | width: $width;
5 | height: $height;
6 | }
7 |
--------------------------------------------------------------------------------
/src/scss/mixins/_tab-focus.scss:
--------------------------------------------------------------------------------
1 | // WebKit-style focus
2 |
3 | @mixin tab-focus() {
4 | // Default
5 | outline: thin dotted;
6 | // WebKit
7 | outline: 5px auto -webkit-focus-ring-color;
8 | outline-offset: -2px;
9 | }
10 |
--------------------------------------------------------------------------------
/src/scss/mixins/_table-row.scss:
--------------------------------------------------------------------------------
1 | // Tables
2 |
3 | @mixin table-row-variant($state, $background) {
4 | // Exact selectors below required to override `.table-striped` and prevent
5 | // inheritance to nested tables.
6 | .table-#{$state} {
7 | &,
8 | > th,
9 | > td {
10 | background-color: $background;
11 | }
12 | }
13 |
14 | // Hover states for `.table-hover`
15 | // Note: this is not available for cells or rows within `thead` or `tfoot`.
16 | .table-hover {
17 | $hover-background: darken($background, 5%);
18 |
19 | .table-#{$state} {
20 | @include hover {
21 | background-color: $hover-background;
22 |
23 | > td,
24 | > th {
25 | background-color: $hover-background;
26 | }
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/scss/mixins/_tag.scss:
--------------------------------------------------------------------------------
1 | // Tags
2 |
3 | @mixin tag-variant($color) {
4 | background-color: $color;
5 |
6 | &[href] {
7 | @include hover-focus {
8 | background-color: darken($color, 10%);
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/scss/mixins/_text-emphasis.scss:
--------------------------------------------------------------------------------
1 | // Typography
2 |
3 | @mixin text-emphasis-variant($parent, $color) {
4 | #{$parent} {
5 | color: $color !important;
6 | }
7 | a#{$parent} {
8 | @include hover-focus {
9 | color: darken($color, 10%);
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/scss/mixins/_text-hide.scss:
--------------------------------------------------------------------------------
1 | // CSS image replacement
2 | @mixin text-hide() {
3 | font: 0/0 a;
4 | color: transparent;
5 | text-shadow: none;
6 | background-color: transparent;
7 | border: 0;
8 | }
9 |
--------------------------------------------------------------------------------
/src/scss/mixins/_text-truncate.scss:
--------------------------------------------------------------------------------
1 | // Text truncate
2 | // Requires inline-block or block for proper styling
3 |
4 | @mixin text-truncate() {
5 | overflow: hidden;
6 | text-overflow: ellipsis;
7 | white-space: nowrap;
8 | }
--------------------------------------------------------------------------------
/src/scss/style.scss:
--------------------------------------------------------------------------------
1 |
2 | @import "variables";
3 | @import "mixins";
4 |
5 | @import "normalize";
6 | @import "print";
7 |
8 | @import "reboot";
9 | @import "alert";
10 | @import "type";
11 | @import "tables";
12 | @import "forms";
13 | @import "buttons";
14 | @import "grid";
15 | @import "modal";
16 |
17 | @import "components";
18 | @import "containers";
19 |
20 | @import "site";
21 | @import "hacks";
--------------------------------------------------------------------------------
/src/scss/utilities/_background.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Contextual backgrounds
3 | //
4 |
5 | .bg-inverse {
6 | background-color: $brand-inverse;
7 | }
8 |
9 | .bg-faded {
10 | background-color: $gray-lightest;
11 | }
12 |
13 | @include bg-variant('.bg-primary', $brand-primary);
14 |
15 | @include bg-variant('.bg-success', $brand-success);
16 |
17 | @include bg-variant('.bg-info', $brand-info);
18 |
19 | @include bg-variant('.bg-warning', $brand-warning);
20 |
21 | @include bg-variant('.bg-danger', $brand-danger);
22 |
--------------------------------------------------------------------------------
/src/scss/utilities/_clearfix.scss:
--------------------------------------------------------------------------------
1 | .clearfix {
2 | @include clearfix();
3 | }
4 |
--------------------------------------------------------------------------------
/src/scss/utilities/_display.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Display utilities
3 | //
4 |
5 | .d-block {
6 | display: block !important;
7 | }
8 | .d-inline-block {
9 | display: inline-block !important;
10 | }
11 | .d-inline {
12 | display: inline !important;
13 | }
14 |
--------------------------------------------------------------------------------
/src/scss/utilities/_flex.scss:
--------------------------------------------------------------------------------
1 | // Flex variation
2 | //
3 | // Custom styles for additional flex alignment options.
4 |
5 | @if $enable-flex {
6 | @each $breakpoint in map-keys($grid-breakpoints) {
7 | // Flex column reordering
8 | @include media-breakpoint-up($breakpoint) {
9 | .flex-#{$breakpoint}-first { order: -1; }
10 | .flex-#{$breakpoint}-last { order: 1; }
11 | .flex-#{$breakpoint}-unordered { order: 0; }
12 | }
13 |
14 | // Alignment for every item
15 | @include media-breakpoint-up($breakpoint) {
16 | .flex-items-#{$breakpoint}-top { align-items: flex-start; }
17 | .flex-items-#{$breakpoint}-middle { align-items: center; }
18 | .flex-items-#{$breakpoint}-bottom { align-items: flex-end; }
19 | }
20 |
21 | // Alignment per item
22 | @include media-breakpoint-up($breakpoint) {
23 | .flex-#{$breakpoint}-top { align-self: flex-start; }
24 | .flex-#{$breakpoint}-middle { align-self: center; }
25 | .flex-#{$breakpoint}-bottom { align-self: flex-end; }
26 | }
27 |
28 | // Horizontal alignment of item
29 | @include media-breakpoint-up($breakpoint) {
30 | .flex-items-#{$breakpoint}-left { justify-content: flex-start; }
31 | .flex-items-#{$breakpoint}-center { justify-content: center; }
32 | .flex-items-#{$breakpoint}-right { justify-content: flex-end; }
33 | .flex-items-#{$breakpoint}-around { justify-content: space-around; }
34 | .flex-items-#{$breakpoint}-between { justify-content: space-between; }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/scss/utilities/_pulls.scss:
--------------------------------------------------------------------------------
1 | @each $breakpoint in map-keys($grid-breakpoints) {
2 | @include media-breakpoint-up($breakpoint) {
3 | .pull-#{$breakpoint}-left {
4 | @include pull-left();
5 | }
6 | .pull-#{$breakpoint}-right {
7 | @include pull-right();
8 | }
9 | .pull-#{$breakpoint}-none {
10 | float: none !important;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/scss/utilities/_screenreaders.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Screenreaders
3 | //
4 |
5 | .sr-only {
6 | @include sr-only();
7 | }
8 |
9 | .sr-only-focusable {
10 | @include sr-only-focusable();
11 | }
12 |
--------------------------------------------------------------------------------
/src/scss/utilities/_spacing.scss:
--------------------------------------------------------------------------------
1 | // Width
2 |
3 | .w-100 { width: 100% !important; }
4 |
5 | // Margin and Padding
6 |
7 | .m-x-auto {
8 | margin-right: auto !important;
9 | margin-left: auto !important;
10 | }
11 |
12 | @each $prop, $abbrev in (margin: m, padding: p) {
13 | @each $size, $lengths in $spacers {
14 | $length-x: map-get($lengths, x);
15 | $length-y: map-get($lengths, y);
16 |
17 | .#{$abbrev}-a-#{$size} { #{$prop}: $length-y $length-x !important; } // a = All sides
18 | .#{$abbrev}-t-#{$size} { #{$prop}-top: $length-y !important; }
19 | .#{$abbrev}-r-#{$size} { #{$prop}-right: $length-x !important; }
20 | .#{$abbrev}-b-#{$size} { #{$prop}-bottom: $length-y !important; }
21 | .#{$abbrev}-l-#{$size} { #{$prop}-left: $length-x !important; }
22 |
23 | // Axes
24 | .#{$abbrev}-x-#{$size} {
25 | #{$prop}-right: $length-x !important;
26 | #{$prop}-left: $length-x !important;
27 | }
28 | .#{$abbrev}-y-#{$size} {
29 | #{$prop}-top: $length-y !important;
30 | #{$prop}-bottom: $length-y !important;
31 | }
32 | }
33 | }
34 |
35 | // Positioning
36 |
37 | .pos-f-t {
38 | position: fixed;
39 | top: 0;
40 | right: 0;
41 | left: 0;
42 | z-index: $zindex-navbar-fixed;
43 | }
44 |
--------------------------------------------------------------------------------
/src/scss/utilities/_text.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Text
3 | //
4 |
5 | // Alignment
6 |
7 | .text-justify { text-align: justify !important; }
8 | .text-nowrap { white-space: nowrap !important; }
9 | .text-truncate { @include text-truncate; }
10 |
11 | // Responsive alignment
12 |
13 | @each $breakpoint in map-keys($grid-breakpoints) {
14 | @include media-breakpoint-up($breakpoint) {
15 | .text-#{$breakpoint}-left { text-align: left !important; }
16 | .text-#{$breakpoint}-right { text-align: right !important; }
17 | .text-#{$breakpoint}-center { text-align: center !important; }
18 | }
19 | }
20 |
21 | // Transformation
22 |
23 | .text-lowercase { text-transform: lowercase !important; }
24 | .text-uppercase { text-transform: uppercase !important; }
25 | .text-capitalize { text-transform: capitalize !important; }
26 |
27 | // Weight and italics
28 |
29 | .font-weight-normal { font-weight: normal; }
30 | .font-weight-bold { font-weight: bold; }
31 | .font-italic { font-style: italic; }
32 |
33 | // Contextual colors
34 |
35 | @include text-emphasis-variant('.text-muted', $text-muted);
36 |
37 | @include text-emphasis-variant('.text-primary', $brand-primary);
38 |
39 | @include text-emphasis-variant('.text-success', $brand-success);
40 |
41 | @include text-emphasis-variant('.text-info', $brand-info);
42 |
43 | @include text-emphasis-variant('.text-warning', $brand-warning);
44 |
45 | @include text-emphasis-variant('.text-danger', $brand-danger);
46 |
47 | // Misc
48 |
49 | .text-hide {
50 | @include text-hide();
51 | }
52 |
--------------------------------------------------------------------------------
/src/scss/utilities/_visibility.scss:
--------------------------------------------------------------------------------
1 | // scss-lint:disable ImportantRule
2 |
3 | //
4 | // Visibility utilities
5 | //
6 |
7 | .invisible {
8 | visibility: hidden !important;
9 | }
10 |
11 | // Responsive visibility utilities
12 |
13 | @each $bp in map-keys($grid-breakpoints) {
14 | .hidden-#{$bp}-up {
15 | @include media-breakpoint-up($bp) {
16 | display: none !important;
17 | }
18 | }
19 | .hidden-#{$bp}-down {
20 | @include media-breakpoint-down($bp) {
21 | display: none !important;
22 | }
23 | }
24 | }
25 |
26 |
27 | // Print utilities
28 | //
29 | // Media queries are placed on the inside to be mixin-friendly.
30 |
31 | .visible-print-block {
32 | display: none !important;
33 |
34 | @media print {
35 | display: block !important;
36 | }
37 | }
38 | .visible-print-inline {
39 | display: none !important;
40 |
41 | @media print {
42 | display: inline !important;
43 | }
44 | }
45 | .visible-print-inline-block {
46 | display: none !important;
47 |
48 | @media print {
49 | display: inline-block !important;
50 | }
51 | }
52 |
53 | .hidden-print {
54 | @media print {
55 | display: none !important;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import path from 'path';
4 | import { Server } from 'http';
5 | import Express from 'express';
6 | import React from 'react';
7 | import { renderToString } from 'react-dom/server';
8 | import { match, RouterContext } from 'react-router';
9 | import routes from './routes';
10 | import NotFoundPage from './components/NotFoundPage';
11 |
12 | // initialize the server and configure support for ejs templates
13 | const app = new Express();
14 | const server = new Server(app);
15 | app.set('view engine', 'ejs');
16 | app.set('views', path.join(__dirname, 'views'));
17 |
18 | // define the folder that will be used for static assets
19 | app.use(Express.static(path.join(__dirname, 'static')));
20 |
21 | // universal routing and rendering
22 | app.get('*', (req, res) => {
23 | match(
24 | { routes, location: req.url },
25 | (err, redirectLocation, renderProps) => {
26 |
27 | // in case of error display the error message
28 | if (err) {
29 | return res.status(500).send(err.message);
30 | }
31 |
32 | // in case of redirect propagate the redirect to the browser
33 | if (redirectLocation) {
34 | return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
35 | }
36 |
37 | // generate the React markup for the current route
38 | let markup;
39 | if (renderProps) {
40 | // if the current route matched we have renderProps
41 | markup = renderToString( );
42 | } else {
43 | // otherwise we can render a 404 page
44 | markup = renderToString( );
45 | res.status(404);
46 | }
47 |
48 | // render the index template with the embedded React markup
49 | return res.render('index', { markup });
50 | }
51 | );
52 | });
53 |
54 | // start the server
55 | const port = process.env.PORT || 3000;
56 | const env = process.env.NODE_ENV || 'production';
57 | server.listen(port, err => {
58 | if (err) {
59 | return console.error(err);
60 | }
61 | console.info(`Server running on http://localhost:${port} [${env}]`);
62 | });
--------------------------------------------------------------------------------
/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var webpack = require('webpack');
5 |
6 | var ENV = {
7 | __BUILD__ : {
8 | PRODUCTION : JSON.stringify(false),
9 | DEVELOP : JSON.stringify(true),
10 |
11 | BASE_URL : JSON.stringify("http://localhost:3000"),
12 | BASE_URL_PORT : JSON.stringify("3000"),
13 | API_URL : JSON.stringify("http://localhost:3000"),
14 | API_PORT : JSON.stringify("3000"),
15 | USERS_API_URL : JSON.stringify("http://localhost:3100"),
16 | USERS_API_PORT : JSON.stringify("3100"),
17 | COVERAGE_API_URL : JSON.stringify("http://localhost:3200"),
18 | COVERAGE_API_PORT : JSON.stringify("3200"),
19 | WEBSOCKET_URL : JSON.stringify("ws://localhost:3000/ws"),
20 | WEBSOCKET_PORT : JSON.stringify("3000"),
21 | STATIC_ASSETS_URL : JSON.stringify("http://localhost:3000"),
22 | SEGMENT_KEY : JSON.stringify(""),
23 | SEGMENT_APP_PROPERTIES: JSON.stringify('{ "app_version": "2.0.0beta1" }'),
24 | }
25 | };
26 |
27 | module.exports = {
28 | devtool: 'cheap-module-eval-source-map',
29 | entry: [
30 | 'webpack-hot-middleware/client?path=//localhost:4000/__qri_io',
31 | './src/js/index'
32 | ],
33 | output: {
34 | // path: path.join(__dirname, 'dist'),
35 | path : __dirname,
36 | filename: 'bundle.js',
37 | publicPath: '//localhost:4000/static/'
38 | },
39 | // resolve: {
40 | // alias: {
41 | // 'react-ace': path.join(__dirname, '..', 'src', 'ace.jsx')
42 | // }
43 | // },
44 | plugins: [
45 | new webpack.DefinePlugin(ENV),
46 | // new webpack.optimize.OccurenceOrderPlugin(),
47 | new webpack.HotModuleReplacementPlugin(),
48 | new webpack.NoEmitOnErrorsPlugin()
49 | ],
50 | module: {
51 | rules: [
52 | {
53 | test: /\.js?$/,
54 | use: ['babel-loader'],
55 | exclude: /node_modules/,
56 | },
57 | {
58 | test: /\.scss$/,
59 | use: [
60 | "style-loader",
61 | "css-loader",
62 | "sass-loader"
63 | ]
64 | },
65 | {
66 | test: /\.css$/,
67 | use: [
68 | "css-loader",
69 | ]
70 | }
71 | ]
72 | }
73 | };
--------------------------------------------------------------------------------
/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var webpack = require('webpack');
5 |
6 | var ENV = {
7 | __BUILD__ : {
8 | PRODUCTION : JSON.stringify(true),
9 | DEVELOP : JSON.stringify(false),
10 | STAGING : JSON.stringify(false),
11 |
12 | BASE_URL : JSON.stringify("https://archivers.co"),
13 | API_URL : JSON.stringify("https://archivers.co"),
14 | USERS_API_URL : JSON.stringify("https://ident.archivers.space"),
15 | COVERAGE_API_URL : JSON.stringify("https://coverage.archivers.space"),
16 | WEBSOCKET_URL : JSON.stringify("wss://archivers.co/ws"),
17 | STATIC_ASSETS_URL : JSON.stringify("http://archivers.co"),
18 | // SEGMENT_KEY : JSON.stringify("FwUGnmKzryJpDpApVzdQy9rmwwWSiK1M"),
19 | SEGMENT_KEY : JSON.stringify(""),
20 | SEGMENT_APP_PROPERTIES: JSON.stringify('{ "app_version": "2.0.0beta1" }'),
21 | }
22 | };
23 |
24 | function exitOneFail() {
25 | this.plugin("done", function(stats) {
26 | if (stats.compilation.errors && stats.compilation.errors.length) {
27 | console.log(stats.compilation.errors);
28 | process.exit(1);
29 | }
30 | });
31 | }
32 |
33 | module.exports = {
34 | devtool : 'cheap-module-source-map',
35 | entry: {
36 | 'app' : './src/js/index.js',
37 | 'vendor' : [ 'react', 'react-dom' ]
38 | },
39 | output: {
40 | path: path.join(__dirname, 'dist'),
41 | publicPath : "https://s3.amazonaws.com/static.qri.io/js/archivers/co/",
42 | filename: '[name].min.js',
43 | chunkFilename: "[name].chunk.min.js"
44 | },
45 | plugins: [
46 | // new webpack.optimize.OccurenceOrderPlugin(),
47 | new webpack.DefinePlugin(ENV),
48 | new webpack.DefinePlugin({ 'process.env': { 'NODE_ENV': JSON.stringify('production') } }),
49 | exitOneFail,
50 | new webpack.optimize.UglifyJsPlugin({minimize: true}),
51 | ],
52 | module: {
53 | rules: [
54 | {
55 | test: /\.js?$/,
56 | loaders: ['babel-loader'],
57 | exclude: /node_modules/
58 | },
59 | {
60 | test: /\.scss$/,
61 | loaders: [
62 | "style-loader",
63 | "css-loader",
64 | "sass-loader"
65 | ]
66 | },
67 | {
68 | test: /\.css$/,
69 | use: [
70 | "css-loader",
71 | ]
72 | }
73 | ]
74 | }
75 | };
76 |
--------------------------------------------------------------------------------