├── .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 | 19 |

{collection.title}

20 |

{collection.description}

21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | 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 | {/**/} 45 | 46 | 47 | 48 | 49 | ); 50 | })} 51 | 52 |
hashurldescription
{ hash &&
{hash}
}
{ hash &&

{hash}

}
{url}{description}
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 | 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 | 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 | 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 | 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 && } 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 |
7 |
8 |
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 | 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 |
9 | 10 |
11 |
12 |
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 |
9 | 10 |
11 |
12 |
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 |
9 | 10 |
11 |
12 |
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 |
9 | 10 |
11 |
12 |
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 |
43 |
44 |
45 | 46 |
47 |
48 |
49 |
50 | 51 | 52 | 53 |
54 | 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 | 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 |
13 | 14 | 15 | X 16 |
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 && } 8 | 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 && } 16 | {label &&
} 17 | 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 ? : 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 | 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 | 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 ? : 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 && } 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 && } 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 && } 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 && } 9 | 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 && } 8 |