diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..65a2352 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,64 @@ +version: 2 + +jobs: + test: + docker: + - image: node:8.15.1-stretch + working_directory: ~/hetmech-frontend + steps: + - checkout + - run: + name: Frontend tests + command: | + npm install + npm test + npm run build + - persist_to_workspace: + root: ~/hetmech-frontend + paths: + - .git + - build + + deploy: + docker: + - image: node:8.15.1-stretch + working_directory: ~/hetmech-frontend + steps: + - attach_workspace: + at: ~/hetmech-frontend + - run: + name: Install and configure dependencies + command: | + npm install --global --silent gh-pages@2.0.1 + - add_ssh_keys: + fingerprints: + - "d5:47:d4:c3:01:e8:51:b1:6e:9a:80:39:8b:ae:cc:77" + - run: + name: Deploy build to gh-pages branch + command: | + echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config + touch build/.nojekyll # Disable Jekyll processing on GitHub Pages + gh-pages --dist build \ + --user "CircleCI Deploy Job " \ + --message \ + "Deploy webpage on `date --utc --iso-8601` + + Builds https://github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/commit/$CIRCLE_SHA1 + Pushed by $CIRCLE_BUILD_URL + + [skip ci]" + +workflows: + version: 2 + build: + jobs: + - test: + filters: + branches: + ignore: gh-pages + - deploy: + requires: + - test + filters: + branches: + only: master \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4d29575..3c3629e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* +node_modules diff --git a/README.md b/README.md index 9d9614c..4779551 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![CircleCI](https://circleci.com/gh/hetio/repurpose-frontend.svg?style=svg)](https://circleci.com/gh/hetio/repurpose-frontend) + This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). ## Available Scripts diff --git a/package-lock.json b/package-lock.json index da895d1..6f3b16f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "cra", + "name": "repurpose-frontend", "version": "0.1.0", "lockfileVersion": 1, "requires": true, @@ -926,6 +926,36 @@ "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-9.0.1.tgz", "integrity": "sha512-6It2EVfGskxZCQhuykrfnALg7oVeiI6KclWSmGDqB0AiInVrTGB9Jp9i4/Ad21u9Jde/voVQz6eFX/eSg/UsPA==" }, + "@fortawesome/fontawesome-common-types": { + "version": "0.2.21", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.21.tgz", + "integrity": "sha512-iJtcrU2BtF9Wyr0zm3tHEJy3HqA6sADExhCqCv3SKaJJKKp4ORJ40t4nyHvcWXSVFtd7r1gcdqcRsAfoREGTFA==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "1.2.21", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.21.tgz", + "integrity": "sha512-EhrgMZLJS0tTYZhUbodurZBqDgAFLDNdxJP/q5unrZJwiFo8Dd7xGvJdhAhY5WcX4khzkPQcbLTCMPHBtutD7Q==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.21" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.10.1.tgz", + "integrity": "sha512-MKH+SCt0DnVoXdemxf6JEdTRtCPwYLMCWZcwgGccYU/ab6QcDtbAMn6Xm4Zub6YqQCcaiy0hU294YdHOldSBRA==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.21" + } + }, + "@fortawesome/react-fontawesome": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.4.tgz", + "integrity": "sha512-GwmxQ+TK7PEdfSwvxtGnMCqrfEm0/HbRHArbUudsYiy9KzVCwndxa2KMcfyTQ8El0vROrq8gOOff09RF1oQe8g==", + "requires": { + "humps": "^2.0.1", + "prop-types": "^15.5.10" + } + }, "@hapi/address": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.0.0.tgz", @@ -953,17 +983,17 @@ "integrity": "sha512-JOfdekTXnJexfE8PyhZFyHvHjt81rBFSAbTIRAhF2vv/2Y1JzoKsGqxH/GpZJoF7aEfYok8JVcAHmSz1gkBieA==" }, "@hapi/topo": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.2.tgz", - "integrity": "sha512-r+aumOqJ5QbD6aLPJWqVjMAPsx5pZKz+F5yPqXZ/WWG9JTtHbQqlzrJoknJ0iJxLj9vlXtmpSdjlkszseeG8OA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.3.tgz", + "integrity": "sha512-JmS9/vQK6dcUYn7wc2YZTqzIKubAQcJKu2KCKAru6es482U5RT5fP1EXCPtlXpiK7PR0On/kpQKI4fRKkzpZBQ==", "requires": { "@hapi/hoek": "8.x.x" }, "dependencies": { "@hapi/hoek": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.1.0.tgz", - "integrity": "sha512-b1J4jxYnW+n6lC91V6Pqg9imP9BZq0HNCeM+3sbXg05rQsE9cGYrKFpZjyztVesGmNRE6R+QaEoWGATeIiUVjA==" + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.2.0.tgz", + "integrity": "sha512-pR2ZgiP562aiaQvQ98WgfqfTrm+xG+7hwHRPEiYZ+7U1OHAAb4OVZJIalCP03bMqYSioQzflzVTVrybSwDBn1Q==" } } }, @@ -1631,9 +1661,9 @@ "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==" }, "acorn-globals": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz", - "integrity": "sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.3.tgz", + "integrity": "sha512-vkR40VwS2SYO98AIeFvzWWh+xyc2qi9s7OoXSFEGIP/rOJKzjnhykaZJNnHdoq4BL2gGxI5EZOU16z896EYnOQ==", "requires": { "acorn": "^6.0.1", "acorn-walk": "^6.0.1" @@ -1918,9 +1948,9 @@ }, "dependencies": { "postcss-value-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz", - "integrity": "sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", + "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==" } } }, @@ -2344,9 +2374,9 @@ } }, "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, "batch": { "version": "0.6.1", @@ -2709,9 +2739,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000988", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000988.tgz", - "integrity": "sha512-lPj3T8poYrRc/bniW5SQPND3GRtSrQdUM/R4mCYTbZxyi3jQiggLvZH4+BYUuX0t4TXjU+vMM7KFDQg+rSzZUQ==" + "version": "1.0.30000989", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz", + "integrity": "sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==" }, "capture-exit": { "version": "2.0.0", @@ -3280,6 +3310,12 @@ "safe-buffer": "^5.0.1" } }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -3569,6 +3605,14 @@ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, + "copy-to-clipboard": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz", + "integrity": "sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w==", + "requires": { + "toggle-selection": "^1.0.6" + } + }, "core-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.0.1.tgz", @@ -4303,9 +4347,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.211", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.211.tgz", - "integrity": "sha512-GZAiK3oHrs0K+LwH+HD+bdjZ17v40oQQdXbbd3dgrwgbENvazrGpcuIADSAREWnxzo9gADB1evuizrbXsnoU2Q==" + "version": "1.3.220", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.220.tgz", + "integrity": "sha512-ZsaFWi+9J9Nsm4OmGM/BvZF3HEeZL4bte1+CcN9vHUcqdkOOVAXP4SeacPZ/W5uCQZEKPYBXg6yUjZx8/jpD0Q==" }, "elliptic": { "version": "6.5.0", @@ -4373,6 +4417,13 @@ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "requires": { "is-arrayish": "^0.2.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + } } }, "es-abstract": { @@ -4497,6 +4548,12 @@ } } }, + "eslint-config-google": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.13.0.tgz", + "integrity": "sha512-ELgMdOIpn0CFdsQS+FuxO+Ttu4p+aLaXHv9wA9yVnzqlUGV7oN/eRRnJekk7TCur6Cu2FXX0fqfIXRBaM14lpQ==", + "dev": true + }, "eslint-config-react-app": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-4.0.1.tgz", @@ -4619,6 +4676,15 @@ "lodash": "^4.17.10" } }, + "eslint-plugin-html": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-6.0.0.tgz", + "integrity": "sha512-PQcGippOHS+HTbQCStmH5MY1BF2MaU8qW/+Mvo/8xTa/ioeMXdSP+IiaBw2+nh0KEMfYQKuTz1Zo+vHynjwhbg==", + "dev": true, + "requires": { + "htmlparser2": "^3.10.1" + } + }, "eslint-plugin-import": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.16.0.tgz", @@ -4765,26 +4831,39 @@ } }, "eslint-plugin-react": { - "version": "7.12.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz", - "integrity": "sha512-1puHJkXJY+oS1t467MjbqjvX53uQ05HXwjqDgdbGBqf5j9eeydI54G3KwiJmWciQ0HTBacIKw2jgwSBSH3yfgQ==", + "version": "7.14.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.14.3.tgz", + "integrity": "sha512-EzdyyBWC4Uz2hPYBiEJrKCUi2Fn+BJ9B/pJQcjw5X+x/H2Nm59S4MJIvL4O5NEE0+WbnQwEBxWY03oUk+Bc3FA==", + "dev": true, "requires": { "array-includes": "^3.0.3", "doctrine": "^2.1.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.0.1", + "jsx-ast-utils": "^2.1.0", + "object.entries": "^1.1.0", "object.fromentries": "^2.0.0", - "prop-types": "^15.6.2", - "resolve": "^1.9.0" + "object.values": "^1.1.0", + "prop-types": "^15.7.2", + "resolve": "^1.10.1" }, "dependencies": { "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, "requires": { "esutils": "^2.0.2" } + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } } } }, @@ -5573,9 +5652,9 @@ } }, "graceful-fs": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", - "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==" + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==" }, "growly": { "version": "1.3.0", @@ -5718,6 +5797,19 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hetio-frontend-components": { + "version": "git+https://github.com/hetio/frontend-components.git#f4ec548021079212770af6d1069befda99eb76c0", + "from": "git+https://github.com/hetio/frontend-components.git#build", + "requires": { + "@fortawesome/fontawesome-svg-core": "^1.2.20", + "@fortawesome/free-solid-svg-icons": "^5.10.0", + "@fortawesome/react-fontawesome": "^0.1.4", + "color": "^3.1.2", + "react": "^16.8.6", + "react-dom": "^16.8.6", + "react-scripts": "3.0.1" + } + }, "hex-color-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", @@ -5734,9 +5826,12 @@ } }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.2.tgz", + "integrity": "sha512-CyjlXII6LMsPMyUzxpTt8fzh5QwzGqPmQXgY/Jyf4Zfp27t/FvfhwoE/8laaMUcMy816CkWF20I7NeQhwwY88w==", + "requires": { + "lru-cache": "^5.1.1" + } }, "hpack.js": { "version": "2.1.6", @@ -5906,6 +6001,11 @@ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, + "humps": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz", + "integrity": "sha1-3QLqYIG9BWjcXQcxhEY5V7qe+ao=" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -6104,9 +6204,9 @@ } }, "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, "is-binary-path": { "version": "1.0.1", @@ -8264,9 +8364,9 @@ "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" }, "node-notifier": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", - "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.1.tgz", + "integrity": "sha512-p52B+onAEHKW1OF9MGO/S7k/ahGEHfhP5/tvwYzog/5XLYOd8ZuD6vdNZdUuWMONRnKPneXV43v3s6Snx1wsCQ==", "requires": { "growly": "^1.3.0", "is-wsl": "^1.1.0", @@ -8423,6 +8523,18 @@ "object-keys": "^1.0.11" } }, + "object.entries": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "object.fromentries": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", @@ -8851,6 +8963,12 @@ } } }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", @@ -9787,12 +9905,12 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, "prompts": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.1.0.tgz", - "integrity": "sha512-+x5TozgqYdOwWsQFZizE/Tra3fKvAoy037kOyU6cgz84n8f6zxngLOV4O32kTwt9FcLCxAqw0P/c8rOr9y+Gfg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.2.1.tgz", + "integrity": "sha512-VObPvJiWPhpZI6C5m60XOzTfnYg/xc/an+r9VYymj9WJW3B/DIH+REzjpAACPf8brwPeP+7vz3bIim3S+AaMjw==", "requires": { - "kleur": "^3.0.2", - "sisteransi": "^1.0.0" + "kleur": "^3.0.3", + "sisteransi": "^1.0.3" } }, "prop-types": { @@ -9819,6 +9937,12 @@ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, "psl": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", @@ -10140,6 +10264,30 @@ "webpack-dev-server": "3.2.1", "webpack-manifest-plugin": "2.0.4", "workbox-webpack-plugin": "4.2.0" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "requires": { + "esutils": "^2.0.2" + } + }, + "eslint-plugin-react": { + "version": "7.12.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz", + "integrity": "sha512-1puHJkXJY+oS1t467MjbqjvX53uQ05HXwjqDgdbGBqf5j9eeydI54G3KwiJmWciQ0HTBacIKw2jgwSBSH3yfgQ==", + "requires": { + "array-includes": "^3.0.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1", + "object.fromentries": "^2.0.0", + "prop-types": "^15.6.2", + "resolve": "^1.9.0" + } + } } }, "read-pkg": { @@ -10417,6 +10565,39 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + }, + "dependencies": { + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + } + } + }, "requireindex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", @@ -10467,6 +10648,292 @@ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" }, + "rewire": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/rewire/-/rewire-4.0.1.tgz", + "integrity": "sha512-+7RQ/BYwTieHVXetpKhT11UbfF6v1kGhKFrtZN7UDL2PybMsSt/rpLWeEUGF5Ndsl1D5BxiCB14VDJyoX+noYw==", + "dev": true, + "requires": { + "eslint": "^4.19.1" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "eslint": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "dev": true, + "requires": { + "ajv": "^5.3.0", + "babel-code-frame": "^6.22.0", + "chalk": "^2.1.0", + "concat-stream": "^1.6.0", + "cross-spawn": "^5.1.0", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.4", + "esquery": "^1.0.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.0.1", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^3.0.6", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.9.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^1.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.3.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "~2.0.1", + "table": "4.0.2", + "text-table": "~0.2.0" + } + }, + "eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "dev": true, + "requires": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + } + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + } + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, "rgb-regex": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", @@ -10515,6 +10982,21 @@ "aproba": "^1.1.1" } }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "*" + } + }, "rxjs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", @@ -10892,19 +11374,12 @@ "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", "requires": { "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - } } }, "sisteransi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.2.tgz", - "integrity": "sha512-ZcYcZcT69nSLAR2oLN2JwNmLkJEKGooFMCdvOkFrToUt/WfcRWqhIg4P4KwY4dmLbuyXIx4o4YmPsvMRJYJd/w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.3.tgz", + "integrity": "sha512-SbEG75TzH8G7eVXFSN5f9EExILKfly7SUvVY5DhhYLvfhKqhDFY0OzevWa/zwak0RLRfWS5AvfMWpd9gJvr5Yg==" }, "slash": { "version": "2.0.0", @@ -11608,6 +12083,11 @@ "repeat-string": "^1.6.1" } }, + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -11646,9 +12126,9 @@ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, "tsutils": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.14.1.tgz", - "integrity": "sha512-kiuZzD1uUA5DxGj/uxbde+ymp6VVdAxdzOIlAFbYKrPyla8/uiJ9JLBm1QsPhOm4Muj0/+cWEDP99yoCUcSl6Q==", + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", "requires": { "tslib": "^1.8.1" } @@ -12188,9 +12668,9 @@ } }, "webpack-sources": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.1.tgz", - "integrity": "sha512-XSz38193PTo/1csJabKaV4b53uRVotlMgqJXm3s3eje0Bu6gQTxYDqpD38CmQfDBA+gN+QqaGjasuC8I/7eW3Q==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", "requires": { "source-list-map": "^2.0.0", "source-map": "~0.6.1" diff --git a/package.json b/package.json index 8a30254..586eba5 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,19 @@ { - "name": "cra", + "name": "repurpose-frontend", "version": "0.1.0", "private": true, + "homepage": "./", "dependencies": { + "copy-to-clipboard": "^3.2.0", + "hetio-frontend-components": "git+https://github.com/hetio/frontend-components.git#build", "react": "^16.8.6", "react-dom": "^16.8.6", "react-scripts": "3.0.1" }, "scripts": { "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", + "build": "node ./scripts/build-non-split.js", + "test": "react-scripts test --passWithNoTests", "eject": "react-scripts eject" }, "eslintConfig": { @@ -27,5 +30,11 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "eslint-config-google": "^0.13.0", + "eslint-plugin-html": "^6.0.0", + "eslint-plugin-react": "^7.14.3", + "rewire": "^4.0.1" } } diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index a11777c..0000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/index.html b/public/index.html index dd1ccfd..dde08e3 100644 --- a/public/index.html +++ b/public/index.html @@ -1,38 +1,9 @@ - - - - - - - - - - - React App - - - -
- - - + + + + + +
diff --git a/public/manifest.json b/public/manifest.json deleted file mode 100644 index 1f2f141..0000000 --- a/public/manifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "short_name": "React App", - "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/scripts/build-non-split.js b/scripts/build-non-split.js new file mode 100644 index 0000000..8a2066c --- /dev/null +++ b/scripts/build-non-split.js @@ -0,0 +1,19 @@ +// from https://github.com/facebook/create-react-app/issues/5306#issuecomment-447948123 + +const rewire = require('rewire'); +const defaults = rewire('react-scripts/scripts/build.js'); +const config = defaults.__get__('config'); + +// Consolidate chunk files instead +config.optimization.splitChunks = { + cacheGroups: { + default: false + } +}; +// Move runtime into bundle instead of separate file +config.optimization.runtimeChunk = false; + +// JS +config.output.filename = 'static/js/[name].js'; +// CSS. "5" is MiniCssPlugin +config.plugins[5].options.filename = 'static/css/[name].css'; diff --git a/src/App.css b/src/App.css deleted file mode 100644 index b41d297..0000000 --- a/src/App.css +++ /dev/null @@ -1,33 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - animation: App-logo-spin infinite 20s linear; - height: 40vmin; - pointer-events: none; -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.js b/src/App.js deleted file mode 100644 index ce9cbd2..0000000 --- a/src/App.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import logo from './logo.svg'; -import './App.css'; - -function App() { - return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
- ); -} - -export default App; diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index a754b20..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './App'; - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..2cb297f --- /dev/null +++ b/src/app.css @@ -0,0 +1,72 @@ +#root { + text-align: center; +} + +section { + margin: 20px 0; +} +table tr th { + height: 40px; +} +textarea { + overflow-y: hidden !important; +} +sub { + position: relative; + top: 5px; +} +.tab_button { + position: relative; + width: 33.33333%; + height: 40px; + margin: 0; + outline: none; +} +.tab_button[data-disabled='true'] { + opacity: 1; +} +.tab_button:after { + display: block; + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 2px; + background: var(--light-gray); + transition: background 0.25s ease; +} +.tab_button[data-disabled='false']:after { + background: var(--blue); +} +@media screen and (max-width: 480px) { + .tab_button { + width: 100%; + } +} +.check_button { + width: 100%; + height: 25px; + margin: 0; + padding: 0; +} +td.left > .dynamic_field { + padding-left: 5px; +} +.table_attic { + display: flex; + justify-content: space-between; + align-items: center; + margin: 10px 0; +} +.info_table td { + height: 20px; +} +.info_table td:first-child { + width: 120px; +} +.neo4j_button { + width: 50%; + height: 100%; + margin: 0; +} \ No newline at end of file diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..f7df878 --- /dev/null +++ b/src/app.js @@ -0,0 +1,178 @@ +import React from 'react'; +import { Component } from 'react'; + +import { Button } from 'hetio-frontend-components'; +import { fetchData } from './data.js'; +import { Diseases } from './diseases.js'; +import { DiseasePredictions } from './disease-predictions.js'; +import { DiseaseInfo } from './disease-info.js'; +import { Genes } from './genes.js'; +import { GeneInfo } from './gene-info.js'; +import { GenePredictions } from './gene-predictions.js'; +import { Features } from './features.js'; +import { FeatureInfo } from './feature-info.js'; +import { FeaturePredictions } from './feature-predictions.js'; + +import './app.css'; + +// main app component +export class App extends Component { + // initialize component + constructor() { + super(); + + this.state = {}; + this.state.tab = 'diseases'; + this.state.disease = null; + this.state.gene = null; + this.state.feature = null; + this.state.diseases = []; + this.state.genes = []; + this.state.features = []; + + // listen for back/forward navigation (history) + window.addEventListener('popstate', this.loadStateFromUrl); + + fetchData().then((results) => { + this.setState( + { + diseases: results.diseases, + genes: results.genes, + features: results.features + }, + this.loadStateFromUrl + ); + }); + } + + // set active tab + setTab = (tab) => { + this.setState({ tab: tab }, this.updateUrl); + }; + + // set selected disease + setDisease = (disease) => { + this.setState({ disease: disease }, this.updateUrl); + }; + + // set selected gene + setGene = (gene) => { + this.setState({ gene: gene }, this.updateUrl); + }; + + // set selected feature + setFeature = (feature) => { + this.setState({ feature: feature }, this.updateUrl); + }; + + // update url based on state + updateUrl = () => { + const params = new URLSearchParams(); + params.set('tab', this.state.tab); + if (this.state.tab === 'diseases' && this.state.disease) + params.set('id', this.state.disease.disease_code.replace(':', '_')); + if (this.state.tab === 'genes' && this.state.gene) + params.set('id', this.state.gene.gene_code); + if (this.state.tab === 'features' && this.state.feature) + params.set('id', this.state.feature.feature); + + const url = + window.location.origin + + window.location.pathname + + '?' + + params.toString(); + window.history.pushState({}, '', url); + + if (params.get('id')) document.title = params.get('id'); + }; + + // load state from url + loadStateFromUrl = () => { + const params = new URLSearchParams(window.location.search); + const newState = {}; + if (params.get('tab')) newState.tab = params.get('tab'); + let id = params.get('id'); + if (newState.tab === 'diseases') { + if (id) id = id.replace('_', ':'); + newState.disease = this.state.diseases.find( + (disease) => disease.disease_code === id + ); + } + if (newState.tab === 'genes') { + newState.gene = this.state.genes.find((gene) => gene.gene_code === id); + } + if (newState.tab === 'features') { + newState.feature = this.state.features.find( + (feature) => feature.feature === id + ); + } + + this.setState(newState); + }; + + // display component + render() { + return ( + <> + + + +
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+ + ); + } +} diff --git a/src/data.js b/src/data.js new file mode 100644 index 0000000..fd6b915 --- /dev/null +++ b/src/data.js @@ -0,0 +1,50 @@ +const diseasesUrl = + 'https://raw.githubusercontent.com/dhimmel/het.io-dag-data/54dd91f7c3c378b4064e8a99b022d4c637fe413f/browser/disease-summary-table.txt'; +const genesUrl = + 'https://raw.githubusercontent.com/dhimmel/het.io-dag-data/54dd91f7c3c378b4064e8a99b022d4c637fe413f/browser/gene-summary-table.txt'; +const featuresUrl = + 'https://raw.githubusercontent.com/dhimmel/het.io-dag-data/54dd91f7c3c378b4064e8a99b022d4c637fe413f/browser/feature-summary-table.txt'; + +// get data from above urls +export async function fetchData() { + const diseases = await (await fetch(diseasesUrl)).text(); + const genes = await (await fetch(genesUrl)).text(); + const features = await (await fetch(featuresUrl)).text(); + + return { + diseases: assembleData(diseases), + genes: assembleData(genes), + features: assembleData(features) + }; +} + +// turn tsv data into expected format +export function assembleData(results) { + const newData = []; + results = results + .trim() + .split('\n') + .map((row) => row.trim()) + .filter((row) => row) + .map((row) => + row + .split('\t') + .map((cell) => cell.trim()) + .filter((cell) => cell) + ); + + for (const datum of results) { + const newDatum = {}; + for ( + let index = 0; + index < results[0].length && index < datum.length; + index++ + ) + newDatum[results[0][index]] = datum[index]; + + newData.push(newDatum); + } + newData.shift(); + + return newData; +} diff --git a/src/disease-info.js b/src/disease-info.js new file mode 100644 index 0000000..55a4a87 --- /dev/null +++ b/src/disease-info.js @@ -0,0 +1,78 @@ +import React from 'react'; +import { Component } from 'react'; + +import { toFixed } from 'hetio-frontend-components'; +import { DynamicField } from 'hetio-frontend-components'; +import { compareObjects } from 'hetio-frontend-components'; + +const infoUrl = + 'https://raw.githubusercontent.com/dhimmel/het.io-dag-data/54dd91f7c3c378b4064e8a99b022d4c637fe413f/browser/disease-info/'; + +export class DiseaseInfo extends Component { + // initialize component + constructor(props) { + super(props); + + this.state = {}; + this.state.predictions = []; + } + + // when component updates + componentDidUpdate(prevProps) { + if ( + this.props.disease && + !compareObjects(prevProps.disease, this.props.disease) + ) { + getInfo(this.props.disease.disease_code).then((info) => { + this.setState({ info: info }); + }); + } + } + + // display component + render() { + const disease = this.props.disease || {}; + const name = disease.disease_name || '-'; + const info = (this.state.info || {}).xref || {}; + const fields = [ + ['Dis. Ont. id', disease.disease_code || '-'], + ['EFO id', info.EFO || '-'], + ['UMLS id', info.UMLS_CUI || '-'], + ['pathophysiology', disease.pathophysiology || '-'], + ['associations', disease.associations || '-'], + ['auroc', toFixed(disease.auroc * 100) + '%'] + ]; + + return ( +
+
+

+ Info about{' '} + + {name} + +

+ + + {fields.map((field, index) => ( + + + + + ))} + +
{field[0]} + +
+
+ ); + } +} + +async function getInfo(id) { + const info = await (await fetch( + infoUrl + id.replace(':', '_') + '.json' + )).json(); + + return info; +} diff --git a/src/disease-predictions.js b/src/disease-predictions.js new file mode 100644 index 0000000..5e68daa --- /dev/null +++ b/src/disease-predictions.js @@ -0,0 +1,187 @@ +import React from 'react'; +import { Component } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faAngleLeft } from '@fortawesome/free-solid-svg-icons'; +import { faAngleRight } from '@fortawesome/free-solid-svg-icons'; +import { faChartBar } from '@fortawesome/free-solid-svg-icons'; + +import { Button } from 'hetio-frontend-components'; +import { IconButton } from 'hetio-frontend-components'; +import { DynamicField } from 'hetio-frontend-components'; +import { Table } from 'hetio-frontend-components'; +import { toComma } from 'hetio-frontend-components'; +import { toFixed } from 'hetio-frontend-components'; +import { toGradient } from 'hetio-frontend-components'; +import { compareObjects } from 'hetio-frontend-components'; +import { assembleData } from './data.js'; + +const predictionsUrl = + 'https://raw.githubusercontent.com/dhimmel/het.io-dag-data/54dd91f7c3c378b4064e8a99b022d4c637fe413f/browser/disease-tables/'; + +export class DiseasePredictions extends Component { + // initialize component + constructor(props) { + super(props); + + this.state = {}; + this.state.predictions = []; + } + + // when component updates + componentDidUpdate(prevProps) { + if ( + this.props.disease && + !compareObjects(prevProps.disease, this.props.disease) + ) { + getPredictions(this.props.disease.disease_code).then((predictions) => { + this.setState({ predictions: predictions }); + }); + } + } + + // display component + render() { + return ( + <> +
+
+

+ Predictions for{' '} + + {(this.props.disease || {}).disease_name || ''} + +

+
+ + {toComma(this.state.predictions.length)} entries + + this.setState({ showMore: !this.state.showMore })} + tooltipText='Expand table' + /> +
+ + Other +
+ Assoc + , + <> + Mean +
+ Pred + , + 'Prediction' + ]} + headStyles={[ + { width: 25 }, + { width: 100 }, + { width: 100 }, + { width: 100 }, + { width: 75 }, + { width: 75 }, + { width: 75 } + ]} + headClasses={[ + '', + 'small left', + 'small left', + 'small', + 'small', + 'small', + 'small' + ]} + headTooltips={['', '', '', '', '', '', '']} + bodyContents={[ + (datum, field, value) => ( + + ), + (datum, field, value) => ( + {value}} fullValue={value} /> + ), + (datum, field, value) => , + (datum, field, value) => , + (datum, field, value) => ( + + ), + (datum, field, value) => ( + + ), + (datum, field, value) => ( + + ) + ]} + bodyStyles={[ + null, + null, + null, + null, + null, + (datum, field, value) => ({ + background: toGradient(value, [ + [0, 'rgba(255, 255, 255, 0)'], + [25, 'rgba(233, 30, 99, 0.25)'] + ]) + }), + (datum, field, value) => ({ + background: toGradient(value, [ + [0, 'rgba(255, 255, 255, 0)'], + [100, 'rgba(233, 30, 99, 0.5)'] + ]) + }) + ]} + bodyClasses={['small left', 'left']} + /> + + + ); + } +} + +async function getPredictions(id) { + const predictions = await (await fetch( + predictionsUrl + id.replace(':', '_') + '.txt' + )).text(); + + return assembleData(predictions); +} diff --git a/src/diseases.js b/src/diseases.js new file mode 100644 index 0000000..e67c354 --- /dev/null +++ b/src/diseases.js @@ -0,0 +1,143 @@ +import React from 'react'; +import { Component } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faEye } from '@fortawesome/free-solid-svg-icons'; +import { faAngleLeft } from '@fortawesome/free-solid-svg-icons'; +import { faAngleRight } from '@fortawesome/free-solid-svg-icons'; + +import { IconButton } from 'hetio-frontend-components'; +import { DynamicField } from 'hetio-frontend-components'; +import { Table } from 'hetio-frontend-components'; +import { Button } from 'hetio-frontend-components'; +import { toComma } from 'hetio-frontend-components'; +import { toFixed } from 'hetio-frontend-components'; +import { toGradient } from 'hetio-frontend-components'; +import { compareObjects } from 'hetio-frontend-components'; + +export class Diseases extends Component { + // initialize component + constructor() { + super(); + + this.state = {}; + } + + // display component + render() { + return ( +
+
+ + {toComma(this.props.diseases.length)} entries + + this.setState({ showMore: !this.state.showMore })} + tooltipText='Expand table' + /> +
+
+ Patho- +
+ physiology + , + 'Assoc', + 'AUROC' + ]} + headStyles={[ + { width: 25 }, + { width: 100 }, + { width: 200 }, + { width: 100 }, + { width: 75 }, + { width: 75 } + ]} + headClasses={[ + '', + 'small left', + 'small left', + 'small', + 'small', + 'small' + ]} + bodyTooltips={[ + (datum, field, value) => + 'See predictions for "' + datum.disease_name + '"', + ]} + bodyContents={[ + (datum, field, value) => ( + + ), + (datum, field, value) => ( + {value}} fullValue={value} /> + ), + (datum, field, value) => , + (datum, field, value) => , + (datum, field, value) => ( + + ), + (datum, field, value) => ( + + ) + ]} + bodyStyles={[ + null, + null, + null, + null, + null, + (datum, field, value) => ({ + background: toGradient(value * 100, [ + [0, 'rgba(255, 255, 255, 0)'], + [50, 'rgba(255, 255, 255, 0)'], + [100, 'rgba(233, 30, 99, 0.5)'] + ]) + }) + ]} + bodyClasses={[null, 'small left', 'left']} + /> + + ); + } +} diff --git a/src/feature-info.js b/src/feature-info.js new file mode 100644 index 0000000..0822231 --- /dev/null +++ b/src/feature-info.js @@ -0,0 +1,75 @@ +import React from 'react'; +import { Component } from 'react'; + +import { toFixed } from 'hetio-frontend-components'; +import { DynamicField } from 'hetio-frontend-components'; +import { compareObjects } from 'hetio-frontend-components'; + +const infoUrl = + 'https://raw.githubusercontent.com/dhimmel/het.io-dag-data/54dd91f7c3c378b4064e8a99b022d4c637fe413f/browser/feature-info/'; + +export class FeatureInfo extends Component { + // initialize component + constructor(props) { + super(props); + + this.state = {}; + this.state.predictions = []; + } + + // when component updates + componentDidUpdate(prevProps) { + if ( + this.props.feature && + !compareObjects(prevProps.feature, this.props.feature) + ) { + getInfo(this.props.feature.feature).then((info) => { + this.setState({ info: info }); + }); + } + } + + // display component + render() { + const feature = this.props.feature || {}; + const name = feature.metapath || '-'; + const info = this.state.info || {}; + const fields = [ + ['description', info.description || '-'], + ['metapath', info.metapath_long || '-'], + ['metric', info.metric || '-'], + ['auroc', toFixed(feature.auroc) + '%'], + ['stand coef', feature.standardized_coefficient || '-'] + ]; + + return ( +
+
+

+ Info about{' '} + + {name} + +

+
+ + {fields.map((field, index) => ( + + + + + ))} + +
{field[0]} + +
+
+ ); + } +} + +async function getInfo(id) { + const info = await (await fetch(infoUrl + id + '.json')).json(); + + return info; +} diff --git a/src/feature-predictions.js b/src/feature-predictions.js new file mode 100644 index 0000000..bdf0df9 --- /dev/null +++ b/src/feature-predictions.js @@ -0,0 +1,168 @@ +import React from 'react'; +import { Component } from 'react'; +import { faAngleLeft } from '@fortawesome/free-solid-svg-icons'; +import { faAngleRight } from '@fortawesome/free-solid-svg-icons'; + +import { IconButton } from 'hetio-frontend-components'; +import { DynamicField } from 'hetio-frontend-components'; +import { Table } from 'hetio-frontend-components'; +import { toComma } from 'hetio-frontend-components'; +import { toFixed } from 'hetio-frontend-components'; +import { toGradient } from 'hetio-frontend-components'; +import { compareObjects } from 'hetio-frontend-components'; +import { assembleData } from './data.js'; + +const predictionsUrl = + 'https://raw.githubusercontent.com/dhimmel/het.io-dag-data/54dd91f7c3c378b4064e8a99b022d4c637fe413f/browser/feature-tables/'; + +export class FeaturePredictions extends Component { + // initialize component + constructor(props) { + super(props); + + this.state = {}; + this.state.predictions = []; + } + + // when component updates + componentDidUpdate(prevProps) { + if ( + this.props.feature && + !compareObjects(prevProps.feature, this.props.feature) + ) { + getPredictions(this.props.feature.feature).then((predictions) => { + this.setState({ predictions: predictions }); + }); + } + } + + // display component + render() { + return ( + <> +
+
+

+ Predictions for{' '} + + {(this.props.feature || {}).metapath || ''} + +

+
+ + {toComma(this.state.predictions.length)} entries + + this.setState({ showMore: !this.state.showMore })} + tooltipText='Expand table' + /> +
+ + Patho- +
+ physiology + , + 'Assoc', + 'AUROC', + <> + Model +
+ AUROC + + ]} + headStyles={[ + { width: 75 }, + { width: 150 }, + { width: 100 }, + { width: 75 }, + { width: 75 }, + { width: 75 } + ]} + headClasses={[ + 'small left', + 'small left', + 'small', + 'small', + 'small', + 'small' + ]} + bodyContents={[ + (datum, field, value) => ( + {value}} fullValue={value} /> + ), + (datum, field, value) => , + (datum, field, value) => , + (datum, field, value) => ( + + ), + (datum, field, value) => ( + + ), + (datum, field, value) => ( + + ) + ]} + bodyStyles={[ + null, + null, + null, + null, + (datum, field, value) => ({ + background: toGradient(value * 100, [ + [0, 'rgba(255, 255, 255, 0)'], + [100, 'rgba(233, 30, 99, 0.5)'] + ]) + }), + (datum, field, value) => ({ + background: toGradient(value * 100, [ + [0, 'rgba(255, 255, 255, 0)'], + [100, 'rgba(233, 30, 99, 0.5)'] + ]) + }) + ]} + bodyClasses={['small left', 'left']} + /> + + + ); + } +} + +async function getPredictions(id) { + const predictions = await (await fetch( + predictionsUrl + id.replace(':', '_') + '.txt' + )).text(); + + return assembleData(predictions); +} diff --git a/src/features.js b/src/features.js new file mode 100644 index 0000000..e553998 --- /dev/null +++ b/src/features.js @@ -0,0 +1,122 @@ +import React from 'react'; +import { Component } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faEye } from '@fortawesome/free-solid-svg-icons'; +import { faAngleLeft } from '@fortawesome/free-solid-svg-icons'; +import { faAngleRight } from '@fortawesome/free-solid-svg-icons'; + +import { IconButton } from 'hetio-frontend-components'; +import { DynamicField } from 'hetio-frontend-components'; +import { Table } from 'hetio-frontend-components'; +import { Button } from 'hetio-frontend-components'; +import { toComma } from 'hetio-frontend-components'; +import { toFixed } from 'hetio-frontend-components'; +import { toGradient } from 'hetio-frontend-components'; +import { compareObjects } from 'hetio-frontend-components'; + +export class Features extends Component { + // initialize component + constructor() { + super(); + + this.state = {}; + } + + // display component + render() { + return ( +
+
+ + {toComma(this.props.features.length)} entries + + this.setState({ showMore: !this.state.showMore })} + tooltipText='Expand table' + /> +
+
+ Stand +
+ Coef + + ]} + headStyles={[ + { width: 25 }, + { width: 100 }, + { width: 100 }, + { width: 75 }, + { width: 75 } + ]} + headClasses={['', 'small left', 'small left', 'small', 'small']} + bodyTooltips={[ + (datum, field, value) => + 'See predictions for "' + datum.metapath + '"' + ]} + bodyContents={[ + (datum, field, value) => ( + + ), + (datum, field, value) => , + (datum, field, value) => , + (datum, field, value) => ( + + ), + (datum, field, value) => + ]} + bodyStyles={[ + null, + null, + null, + (datum, field, value) => ({ + background: toGradient(value * 100, [ + [50, 'rgba(255, 255, 255, 0)'], + [75, 'rgba(233, 30, 99, 0.5)'] + ]) + }) + ]} + bodyClasses={[null, 'small left', 'left']} + /> + + ); + } +} diff --git a/src/gene-info.js b/src/gene-info.js new file mode 100644 index 0000000..07bfd70 --- /dev/null +++ b/src/gene-info.js @@ -0,0 +1,79 @@ +import React from 'react'; +import { Component } from 'react'; + +import { toFixed } from 'hetio-frontend-components'; +import { DynamicField } from 'hetio-frontend-components'; +import { compareObjects } from 'hetio-frontend-components'; + +const infoUrl = + 'https://raw.githubusercontent.com/dhimmel/het.io-dag-data/54dd91f7c3c378b4064e8a99b022d4c637fe413f/browser/gene-info/'; + +export class GeneInfo extends Component { + // initialize component + constructor(props) { + super(props); + + this.state = {}; + this.state.predictions = []; + } + + // when component updates + componentDidUpdate(prevProps) { + if (this.props.gene && !compareObjects(prevProps.gene, this.props.gene)) { + getInfo(this.props.gene.gene_code).then((info) => { + this.setState({ info: info }); + }); + } + } + + // display component + render() { + const gene = this.props.gene || {}; + const name = gene.gene_symbol || '-'; + const info = this.state.info || {}; + const fields = [ + [ + 'aliases', + info.aliases && info.aliases.length ? info.aliases.join(', ') : '-' + ], + ['HGNC id', gene.gene_code || '-'], + ['Entrez id', info.entrez || '-'], + ['Ensembl id', info.ensembl || '-'], + ['Uniprot id', info.uniprot || '-'], + ['associations', gene.associations || '-'], + ['mean prediction', toFixed(gene.mean_prediction) + '%'] + ]; + + return ( +
+
+

+ Info about{' '} + + {name} + +

+
+ + {fields.map((field, index) => ( + + + + + ))} + +
{field[0]} + +
+
+ ); + } +} + +async function getInfo(id) { + const info = await (await fetch( + infoUrl + id.replace(':', '_') + '.json' + )).json(); + + return info; +} diff --git a/src/gene-predictions.js b/src/gene-predictions.js new file mode 100644 index 0000000..fff3db7 --- /dev/null +++ b/src/gene-predictions.js @@ -0,0 +1,181 @@ +import React from 'react'; +import { Component } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faAngleLeft } from '@fortawesome/free-solid-svg-icons'; +import { faAngleRight } from '@fortawesome/free-solid-svg-icons'; +import { faChartBar } from '@fortawesome/free-solid-svg-icons'; + +import { Button } from 'hetio-frontend-components'; +import { IconButton } from 'hetio-frontend-components'; +import { DynamicField } from 'hetio-frontend-components'; +import { Table } from 'hetio-frontend-components'; +import { toComma } from 'hetio-frontend-components'; +import { toFixed } from 'hetio-frontend-components'; +import { toGradient } from 'hetio-frontend-components'; +import { compareObjects } from 'hetio-frontend-components'; +import { assembleData } from './data.js'; + +const predictionsUrl = + 'https://raw.githubusercontent.com/dhimmel/het.io-dag-data/54dd91f7c3c378b4064e8a99b022d4c637fe413f/browser/gene-tables/'; + +export class GenePredictions extends Component { + // initialize component + constructor(props) { + super(props); + + this.state = {}; + this.state.predictions = []; + } + + // when component updates + componentDidUpdate(prevProps) { + if (this.props.gene && !compareObjects(prevProps.gene, this.props.gene)) { + getPredictions(this.props.gene.gene_code).then((predictions) => { + this.setState({ predictions: predictions }); + }); + } + } + + // display component + render() { + return ( + <> +
+
+

+ Predictions for{' '} + + {(this.props.gene || {}).gene_symbol || ''} + +

+
+ + {toComma(this.state.predictions.length)} entries + + this.setState({ showMore: !this.state.showMore })} + tooltipText='Expand table' + /> +
+ + Patho- +
+ physiology + , + 'Status', + <> + Other +
+ Assoc + , + 'Prediction' + ]} + headStyles={[ + { width: 25 }, + { width: 100 }, + { width: 100 }, + { width: 100 }, + { width: 75 }, + { width: 75 }, + { width: 75 } + ]} + headClasses={[ + '', + 'small left', + 'small left', + 'small', + 'small', + 'small', + 'small' + ]} + headTooltips={['', '', '', '', '', '', '']} + bodyContents={[ + (datum, field, value) => ( + + ), + (datum, field, value) => ( + {value}} fullValue={value} /> + ), + (datum, field, value) => , + (datum, field, value) => , + (datum, field, value) => ( + + ), + (datum, field, value) => ( + + ), + (datum, field, value) => ( + + ) + ]} + bodyStyles={[ + null, + null, + null, + null, + null, + (datum, field, value) => ({ + background: toGradient(value, [ + [0, 'rgba(255, 255, 255, 0)'], + [25, 'rgba(233, 30, 99, 0.25)'] + ]) + }), + (datum, field, value) => ({ + background: toGradient(value, [ + [0, 'rgba(255, 255, 255, 0)'], + [100, 'rgba(233, 30, 99, 0.5)'] + ]) + }) + ]} + bodyClasses={[null, 'small left', 'left']} + /> + + + ); + } +} + +async function getPredictions(id) { + const predictions = await (await fetch( + predictionsUrl + id.replace(':', '_') + '.txt' + )).text(); + + return assembleData(predictions); +} diff --git a/src/genes.js b/src/genes.js new file mode 100644 index 0000000..f48f612 --- /dev/null +++ b/src/genes.js @@ -0,0 +1,126 @@ +import React from 'react'; +import { Component } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faEye } from '@fortawesome/free-solid-svg-icons'; +import { faAngleLeft } from '@fortawesome/free-solid-svg-icons'; +import { faAngleRight } from '@fortawesome/free-solid-svg-icons'; + +import { IconButton } from 'hetio-frontend-components'; +import { DynamicField } from 'hetio-frontend-components'; +import { Table } from 'hetio-frontend-components'; +import { Button } from 'hetio-frontend-components'; +import { toComma } from 'hetio-frontend-components'; +import { toFixed } from 'hetio-frontend-components'; +import { toGradient } from 'hetio-frontend-components'; +import { compareObjects } from 'hetio-frontend-components'; + +export class Genes extends Component { + // initialize component + constructor() { + super(); + + this.state = {}; + } + + // display component + render() { + return ( +
+
+ + {toComma(this.props.genes.length)} entries + + this.setState({ showMore: !this.state.showMore })} + tooltipText='Expand table' + /> +
+
+ Mean +
+ Pred + + ]} + headStyles={[ + { width: 25 }, + { width: 100 }, + { width: 100 }, + { width: 75 }, + { width: 75 } + ]} + headClasses={['', 'small left', 'small left', 'small', 'small']} + bodyTooltips={[ + (datum, field, value) => + 'See predictions for "' + datum.gene_name + '"' + ]} + bodyContents={[ + (datum, field, value) => ( + + ), + (datum, field, value) => ( + {value}} fullValue={value} /> + ), + (datum, field, value) => , + (datum, field, value) => , + (datum, field, value) => ( + + ) + ]} + bodyStyles={[ + null, + null, + null, + null, + (datum, field, value) => ({ + background: toGradient(value, [ + [0, 'rgba(255, 255, 255, 0)'], + [25, 'rgba(233, 30, 99, 0.5)'] + ]) + }) + ]} + bodyClasses={[null, 'small left', 'left']} + /> + + ); + } +} diff --git a/src/index.css b/src/index.css deleted file mode 100644 index 4a1df4d..0000000 --- a/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", - "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", - monospace; -} diff --git a/src/index.js b/src/index.js index 87d1be5..05bab46 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,7 @@ import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import App from './App'; -import * as serviceWorker from './serviceWorker'; +import { render } from 'react-dom'; -ReactDOM.render(, document.getElementById('root')); +import { App } from './app'; -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: https://bit.ly/CRA-PWA -serviceWorker.unregister(); +// render/run app +render(, document.getElementById('root')); diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 6b60c10..0000000 --- a/src/logo.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/serviceWorker.js b/src/serviceWorker.js deleted file mode 100644 index f8c7e50..0000000 --- a/src/serviceWorker.js +++ /dev/null @@ -1,135 +0,0 @@ -// This optional code is used to register a service worker. -// register() is not called by default. - -// This lets the app load faster on subsequent visits in production, and gives -// it offline capabilities. However, it also means that developers (and users) -// will only see deployed updates on subsequent visits to a page, after all the -// existing tabs open on the page have been closed, since previously cached -// resources are updated in the background. - -// To learn more about the benefits of this model and instructions on how to -// opt-in, read https://bit.ly/CRA-PWA - -const isLocalhost = Boolean( - window.location.hostname === 'localhost' || - // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || - // 127.0.0.1/8 is considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) -); - -export function register(config) { - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { - // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; - } - - window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); - - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' - ); - }); - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, config); - } - }); - } -} - -function registerValidSW(swUrl, config) { - navigator.serviceWorker - .register(swUrl) - .then(registration => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker == null) { - return; - } - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' - ); - - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); - - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } - } - } - }; - }; - }) - .catch(error => { - console.error('Error during service worker registration:', error); - }); -} - -function checkValidServiceWorker(swUrl, config) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl) - .then(response => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type'); - if ( - response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } - }) - .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); - }); -} - -export function unregister() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready.then(registration => { - registration.unregister(); - }); - } -}