diff --git a/ui/Dockerfile b/ui/Dockerfile index 2d917caa1f..7a3b80042b 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -16,13 +16,19 @@ FROM node:14.18.1-alpine3.11 as canopy-app-build-stage RUN apk add --no-cache python g++ git make +COPY ui/canopyjs /canopyjs +ENV REACT_APP_SPLINTER_URL "/splinterd" +ENV REACT_APP_SAPLING_URL "/sapling-dev-server" +ENV REACT_APP_GRID_URL "/gridd" +WORKDIR /canopyjs +RUN npm config set unsafe-perm true \ + && npm install \ + && npm run build WORKDIR /ui COPY ui/grid-ui/package*.json ./ RUN npm config set unsafe-perm true && npm install COPY ui/grid-ui . -ENV REACT_APP_SPLINTER_URL "/splinterd" -ENV REACT_APP_SAPLING_URL "/sapling-dev-server" -ENV REACT_APP_GRID_URL "/gridd" + RUN npm run build WORKDIR /ui/build ARG REPO_VERSION @@ -42,6 +48,11 @@ RUN apk add --no-cache python g++ git make \ COPY ui/saplings /saplings COPY ui/sapling-dev-server /sapling-dev-server COPY ui/protos /protos +COPY ui/saplingjs /saplingjs + +WORKDIR /saplingjs +RUN npm install \ + && npm run build ARG PUBLIC_URL_PARTIAL ENV PUBLIC_URL $PUBLIC_URL_PARTIAL diff --git a/ui/canopyjs/.babelrc b/ui/canopyjs/.babelrc new file mode 100644 index 0000000000..67d7adc2ae --- /dev/null +++ b/ui/canopyjs/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": ["@babel/preset-react", "@babel/preset-env"], + "plugins": [ + ["@babel/transform-runtime"] + ] +} diff --git a/ui/canopyjs/.dockerignore b/ui/canopyjs/.dockerignore new file mode 100644 index 0000000000..08017e5a0a --- /dev/null +++ b/ui/canopyjs/.dockerignore @@ -0,0 +1,22 @@ +# Copyright 2018-2020 Cargill Incorporated +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +**/node_modules +**/.pnp +**/.pnp.js +**/yarn.lock +**/package-lock.json +**/coverage +**/build +**/dist diff --git a/ui/canopyjs/.env b/ui/canopyjs/.env new file mode 100644 index 0000000000..cbe9914025 --- /dev/null +++ b/ui/canopyjs/.env @@ -0,0 +1,17 @@ +# Copyright 2018-2020 Cargill Incorporated +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ISOLATION_ID=latest +DISTRO=bionic +REPO_VERSION=0.3.12-dev diff --git a/ui/canopyjs/.eslintignore b/ui/canopyjs/.eslintignore new file mode 100644 index 0000000000..bf6b87e7aa --- /dev/null +++ b/ui/canopyjs/.eslintignore @@ -0,0 +1,16 @@ +# Copyright 2018-2020 Cargill Incorporated +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +saplings/* +build/* diff --git a/ui/canopyjs/.eslintrc b/ui/canopyjs/.eslintrc new file mode 100644 index 0000000000..9f320e0238 --- /dev/null +++ b/ui/canopyjs/.eslintrc @@ -0,0 +1,29 @@ +{ + "env": { + "es6": true, + "jest": true, + "browser": true + }, + "parserOptions": { + "ecmaVersion": 2020 + }, + "extends": [ + "airbnb", + "plugin:prettier/recommended" + ], + "rules": { + "react/jsx-filename-extension": 0, + "react/prefer-stateless-function": 0, + "import/prefer-default-export": 0, + "react/forbid-prop-types": 0 + }, + "settings": { + "import/resolver": { + "node": { + "paths": [ + "src" + ] + } + } + } +} diff --git a/ui/canopyjs/.prettierrc b/ui/canopyjs/.prettierrc new file mode 100644 index 0000000000..57a1013229 --- /dev/null +++ b/ui/canopyjs/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "singleQuote": true +} diff --git a/ui/canopyjs/Dockerfile b/ui/canopyjs/Dockerfile new file mode 100644 index 0000000000..cd78b107c8 --- /dev/null +++ b/ui/canopyjs/Dockerfile @@ -0,0 +1,21 @@ +# Copyright 2018-2020 Cargill Incorporated +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM node:lts-alpine + +WORKDIR /splinter-canopyjs + +COPY . . + +RUN npm install diff --git a/ui/canopyjs/LICENSE b/ui/canopyjs/LICENSE new file mode 100644 index 0000000000..141e5aeba0 --- /dev/null +++ b/ui/canopyjs/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2020 Cargill, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/ui/canopyjs/README.md b/ui/canopyjs/README.md new file mode 100644 index 0000000000..1d221f1ad1 --- /dev/null +++ b/ui/canopyjs/README.md @@ -0,0 +1,128 @@ + + +# CanopyJS + +CanopyJS is a library for building Canopy applications. A Canopy application is +a React app that is capable of dynamically loading in saplings, which are UI +components designed to work with Splinter. + +The central component provided by CanopyJS is a React context provider called +`CanopyProvider`. This context provider should wrap the top level component of +a Canopy application. + +See [splinter.dev](https://www.splinter.dev/) for Splinter documentation, +release notes, and community information. + +## Features + +- Provides functionality for loading saplings into the Canopy application +- Implements some of the functions that are defined in SaplingJS +- Exposes shared configuration to saplings and Canopy application components + +## Configuration + +CanopyJS makes use of two endpoints, `splinterURL` and `saplingURL`. + +### splinterURL + +`splinterURL` is the URL where the Splinter daemon is running. This URL will be +used by Canopy and saplings to interact with Splinter via the Splinter daemon's +REST API. Examples of these interactions would include: +- Submitting transactions to a Scabbard service +- Managing users using the Biome module of Splinter + +### saplingURL + +`saplingURL` is the URL where saplings are being served from. On startup, +canopyJS will attempt to fetch sapling configuration from the following +endpoints: + +- `${saplingURL}/configSaplings`: Config saplings +- `${saplingURL}/userSaplings`: User saplings + +See the example in `splinter/canopy/app/saplings` for an example of these +configuration responses. + +## Example + +### App.js + +```javascript +import React from 'react'; +import { CanopyProvider } from 'canopyjs'; + +import SideNav from './components/SideNav'; + +function CanopyApp() { + return ( + + + + ); +} +export default CanopyApp; +``` + +In this example, `saplingURL` and `splinterURL` are set as React app environment +variables prior to starting up the application. The `SideNav` component gets +wrapped by the `CanopyProvider`, which gives it access to the React context +provided by CanopyJS. + +### SideNav.js + +```javascript +import React from 'react'; +import { useUserSaplings } from 'canopyjs'; + +import NavItem from './NavItem'; + +function SideNav() { + const userSaplings = useUserSaplings(); + const userSaplingRoutes = userSaplings.map( + ({ displayName, namespace, icon }) => { + return { + path: `/${namespace}`, + displayName, + logo: icon + }; + } + ); + const userSaplingTabs = userSaplingRoutes.map( + ({ path, displayName, logo }) => { + return ; + } + ); + + return ( + <> + +

Canopy

+
+
{userSaplingTabs}
+ + ); +} + +export default SideNav; +``` + +The `SideNav` component imports `useUserSaplings` from CanopyJS. The +`useUserSaplings` function exposes the part of the Canopy context that contains +user sapling configuration. This allows the `SideNav` component to render +`NavItems` for each of the user saplings. CanopyJS handles mounting the styles +and DOM elements for the currently active sapling. diff --git a/ui/canopyjs/bin/get_version b/ui/canopyjs/bin/get_version new file mode 100755 index 0000000000..8a89756e11 --- /dev/null +++ b/ui/canopyjs/bin/get_version @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +# Copyright 2016, 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------------------------ + +import os +import subprocess +import sys + +top_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + +version_file = top_dir + "/VERSION" + +with open(version_file, 'r') as f: + version_data = f.read().strip() + + +def bump_version(version): + (major, minor, patch) = version.split('.') + if 'rc' in patch: + parts = patch.split('rc') + parts[1] = str(int(parts[1]) + 1) + patch = "rc".join(parts) + else: + patch = str(int(patch) + 1) + return ".".join([major, minor, patch]) + + +def auto_version(default, strict): + output = subprocess.check_output(['git', 'describe', '--dirty']) + parts = output.decode('utf-8').strip().split('-', 3) + parts[0] = parts[0][1:] # strip the leading 'v' + if len(parts) > 1: + parts[0] = bump_version(parts[0]) + if default != parts[0]: + msg = "VERSION file and (bumped?) git describe versions differ: " \ + "{} != {}".format(default, parts[0]) + if strict: + print("ERROR: " + msg, file=sys.stderr) + sys.exit(1) + else: + print("WARNING: " + msg, file=sys.stderr) + parts[0] = default + + if len(parts) > 1: + parts[0] = "-dev".join([parts[0], parts[1].replace("-", ".")]) + if len(parts) == 4: + parts[0] = parts[0] + "-" + parts[3] + return parts[0] + else: + return parts[0] + + +def version(default): + if 'VERSION' in os.environ: + if os.environ['VERSION'] == 'AUTO_STRICT': + version = auto_version(default, strict=True) + elif os.environ['VERSION'] == 'AUTO': + version = auto_version(default, strict=False) + else: + version = os.environ['VERSION'] + else: + version = default + "-dev1" + return version + + +print(version(version_data)) diff --git a/ui/canopyjs/docker/compose/.env b/ui/canopyjs/docker/compose/.env new file mode 100644 index 0000000000..cbe9914025 --- /dev/null +++ b/ui/canopyjs/docker/compose/.env @@ -0,0 +1,17 @@ +# Copyright 2018-2020 Cargill Incorporated +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ISOLATION_ID=latest +DISTRO=bionic +REPO_VERSION=0.3.12-dev diff --git a/ui/canopyjs/docker/compose/run-lint.yaml b/ui/canopyjs/docker/compose/run-lint.yaml new file mode 100644 index 0000000000..ca13ed8df3 --- /dev/null +++ b/ui/canopyjs/docker/compose/run-lint.yaml @@ -0,0 +1,22 @@ +# Copyright 2018-2020 Cargill Incorporated +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: "3.7" + +services: + lint-canopyjs: + build: + context: ../.. + image: canopyjs:${ISOLATION_ID} + command: yarn lint diff --git a/ui/canopyjs/package.json b/ui/canopyjs/package.json new file mode 100644 index 0000000000..08d028383f --- /dev/null +++ b/ui/canopyjs/package.json @@ -0,0 +1,79 @@ +{ + "name": "splinter-canopyjs", + "version": "0.0.1", + "description": "React context provider for building Canopy applications.", + "main": "build/index.js", + "author": "Cargill Incorporated", + "license": "Apache-2.0", + "private": false, + "repository": { + "type": "git", + "url": "https://github.com/Cargill/splinter-canopyjs" + }, + "components": "build/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "prepare": "npm run build", + "lint": "eslint src/", + "build": "NODE_ENV=production rollup -c && npm run rollup:clean", + "rollup:clean": "cd build/themes; each '*/index.js' -- rm index.js" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "^1.2.25", + "@fortawesome/free-regular-svg-icons": "^5.11.2", + "@fortawesome/free-solid-svg-icons": "^5.12.0", + "@fortawesome/react-fontawesome": "^0.1.8", + "@material-ui/core": "^4.11.2", + "@material-ui/icons": "^4.11.2", + "classnames": "^2.2.6", + "react": "^16.10.2", + "react-dom": "^16.10.2" + }, + "dependencies": { + "@babel/runtime": "^7.8.4", + "@fortawesome/fontawesome-svg-core": "^1.2.25", + "@fortawesome/free-regular-svg-icons": "^5.11.2", + "@fortawesome/free-solid-svg-icons": "^5.12.0", + "@fortawesome/react-fontawesome": "^0.1.8", + "@material-ui/core": "^4.11.2", + "@material-ui/icons": "^4.11.2", + "classnames": "^2.2.6", + "history": "^4.10.1", + "prop-types": "^15.7.2", + "webpack": "^4.41.5" + }, + "devDependencies": { + "@babel/cli": "^7.8.4", + "@babel/core": "^7.8.4", + "@babel/plugin-transform-runtime": "^7.8.3", + "@babel/preset-env": "^7.8.7", + "@babel/preset-react": "^7.8.3", + "@rollup/plugin-commonjs": "^11.0.2", + "@rollup/plugin-node-resolve": "^7.1.1", + "autoprefixer": "^9.7.0", + "babel-loader": "^8.0.6", + "cli-foreachfile": "^1.0.5", + "eslint": "^6.6.0", + "eslint-config-airbnb": "18.0.1", + "eslint-config-prettier": "^6.4.0", + "eslint-plugin-import": "^2.18.2", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-prettier": "^3.1.1", + "eslint-plugin-react": "^7.14.3", + "eslint-plugin-react-hooks": "^1.7.0", + "postcss": "^8.0.0", + "postcss-base64": "^0.7.1", + "postcss-clean": "^1.1.0", + "prettier": "^1.18.2", + "react": "^16.10.2", + "react-dom": "^16.10.2", + "rollup": "^1.26.0", + "rollup-plugin-analyzer": "^3.2.2", + "rollup-plugin-babel": "^4.3.3", + "rollup-plugin-peer-deps-external": "^2.2.0", + "rollup-plugin-sass": "^1.2.2", + "rollup-plugin-terser": "^5.1.3", + "rollup-plugin-uglify": "^6.0.4", + "webpack-cli": "^3.3.10" + } +} diff --git a/ui/canopyjs/rollup.config.js b/ui/canopyjs/rollup.config.js new file mode 100644 index 0000000000..54d7ae51f5 --- /dev/null +++ b/ui/canopyjs/rollup.config.js @@ -0,0 +1,106 @@ +/** + * Copyright 2018-2020 Cargill Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import sass from 'rollup-plugin-sass'; +import babel from 'rollup-plugin-babel'; +import commonjs from '@rollup/plugin-commonjs'; +import resolve from '@rollup/plugin-node-resolve'; +import external from 'rollup-plugin-peer-deps-external'; +import analyzer from 'rollup-plugin-analyzer'; +import { uglify } from 'rollup-plugin-uglify'; +import autoprefixer from 'autoprefixer'; +import postcss from 'postcss'; +import base64 from 'postcss-base64'; +import clean from 'postcss-clean'; +import fs from 'fs'; +import packageJSON from './package.json'; + +const themes = fs.readdirSync('./src/themes'); +const components = './src/index.js'; +const minifyExtension = pathToFile => pathToFile.replace(/\.js$/, '.min.js'); + +const opts = { + extensions: ['.png', '.svg'] +}; + +const themeBundles = themes.map(theme => { + return { + input: `./src/themes/${theme}/index.js`, + output: { + file: `build/themes/${theme}/index.js`, + format: 'esm' + }, + plugins: [ + sass({ + output: true, + processor: css => + postcss([autoprefixer, base64(opts), clean()]) + .process(css) + .then(result => result.css) + }) + ] + }; +}); + +export default [ + // style themes + ...themeBundles, + // commonjs + { + input: components, + output: { + file: packageJSON.components, + format: 'cjs', + sourcemap: true + }, + plugins: [ + babel({ + exclude: '/node_modules/**', + runtimeHelpers: true + }), + external(), + resolve(), + commonjs({ + namedExports: { + 'react-is': ['isValidElementType'] + } + }), + analyzer() + ] + }, + { + input: components, + output: { + file: minifyExtension(packageJSON.components), + format: 'cjs', + sourcemap: true + }, + plugins: [ + babel({ + exclude: 'node_modules/**', + runtimeHelpers: true + }), + external(), + resolve(), + commonjs({ + namedExports: { + 'react-is': ['isValidElementType'] + } + }), + uglify(), + analyzer() + ] + } +]; diff --git a/ui/canopyjs/src/CanopyContext.js b/ui/canopyjs/src/CanopyContext.js new file mode 100644 index 0000000000..fa1a7ea5b4 --- /dev/null +++ b/ui/canopyjs/src/CanopyContext.js @@ -0,0 +1,182 @@ +/** + * Copyright 2018-2020 Cargill Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useState, createContext, useContext, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { createBrowserHistory } from 'history'; + +import { get } from './request'; +import { + mountCurrentSapling, + mountConfigSaplingStyles, + mountConfigSaplings +} from './loadSaplings'; + +export const CanopyContext = createContext({}); +const history = createBrowserHistory(); + +const fetchUserSaplings = async saplingURL => { + const response = await get(`${saplingURL}/userSaplings`); + const userSaplingsResponse = response.json; + return userSaplingsResponse; +}; + +const fetchConfigSaplings = async saplingURL => { + const response = await get(`${saplingURL}/configSaplings`); + const configSaplingsResponse = response.json; + return configSaplingsResponse; +}; + +export function CanopyProvider({ + saplingURL, + splinterURL, + appConfig, + children +}) { + const initialUserSaplings = window.sessionStorage.getItem('USER_SAPLINGS'); + const initialConfigSaplings = window.sessionStorage.getItem( + 'CONFIG_SAPLINGS' + ); + + const [userSaplings, setUserSaplings] = useState( + initialUserSaplings ? JSON.parse(initialUserSaplings) : [] + ); + const [configSaplings, setConfigSaplings] = useState( + initialConfigSaplings ? JSON.parse(initialConfigSaplings) : [] + ); + + const sessionUser = window.sessionStorage.getItem('CANOPY_USER'); + const [user, setUser] = useState( + sessionUser ? JSON.parse(sessionUser) : null + ); + + const sessionKeys = window.sessionStorage.getItem('CANOPY_KEYS'); + const [keys, setKeys] = useState( + sessionKeys ? JSON.parse(sessionKeys) : null + ); + + window.$CANOPY.getSharedConfig = () => { + return { + canopyConfig: { + splinterURL, + saplingURL + }, + appConfig + }; + }; + + window.$CANOPY.hideCanopy = () => { + const sideNavElement = document.getElementById('root'); + if (sideNavElement) { + sideNavElement.classList.add('display-none'); + } + }; + + window.$CANOPY.setUser = canopyUser => { + window.sessionStorage.setItem('CANOPY_USER', JSON.stringify(canopyUser)); + setUser(canopyUser); + }; + + window.$CANOPY.setKeys = signingKeys => { + window.sessionStorage.setItem('CANOPY_KEYS', JSON.stringify(signingKeys)); + setKeys(signingKeys); + }; + + window.$CANOPY.getUser = () => user; + + window.$CANOPY.getKeys = () => keys; + + useEffect(() => { + window.$CANOPY = window.$CANOPY || {}; + window.$CANOPY.registerApp = bootStrapFunction => { + bootStrapFunction(document.querySelector('#sapling-container')); + }; + + fetchConfigSaplings(saplingURL).then(saplings => { + mountConfigSaplings(saplings); + mountConfigSaplingStyles(saplings); + window.sessionStorage.setItem( + 'CONFIG_SAPLINGS', + JSON.stringify(saplings) + ); + }); + }, [saplingURL]); + + useEffect(() => { + window.$CANOPY.registerConfigSapling = (namespace, bootStrapFunction) => { + bootStrapFunction(); + return setConfigSaplings(currentConfigSaplings => { + return { ...currentConfigSaplings, [namespace]: bootStrapFunction }; + }); + }; + }, []); + + useEffect(() => { + if (sessionUser) { + fetchUserSaplings(saplingURL).then(saplings => { + mountCurrentSapling(saplings); + setUserSaplings(saplings); + window.sessionStorage.setItem( + 'USER_SAPLINGS', + JSON.stringify(saplings) + ); + }); + } else { + window.$CANOPY.hideCanopy(); + window.$CANOPY.redirectedFrom = window.location.href; + history.push('/login'); + } + }, [saplingURL]); + + return ( + + {children} + + ); +} + +CanopyProvider.defaultProps = { + appConfig: {} +}; + +CanopyProvider.propTypes = { + saplingURL: PropTypes.string.isRequired, + splinterURL: PropTypes.string.isRequired, + appConfig: PropTypes.object, + children: PropTypes.node.isRequired +}; + +export function useUserSaplings() { + const context = useContext(CanopyContext); + return context.userSaplings; +} + +export function useConfigSaplings() { + const context = useContext(CanopyContext); + return context.userSaplings; +} + +export function useUser() { + const context = React.useContext(CanopyContext); + return context.user; +} + +export function useKeys() { + const context = React.useContext(CanopyContext); + return context.keys; +} diff --git a/ui/canopyjs/src/components/button/Button.scss b/ui/canopyjs/src/components/button/Button.scss new file mode 100644 index 0000000000..81c0b66767 --- /dev/null +++ b/ui/canopyjs/src/components/button/Button.scss @@ -0,0 +1,42 @@ +/** + * Copyright 2018-2020 Cargill Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.button { + border: none; + background: #555555; + padding: 1rem; + + &.flat { + background: transparent; + color: #555555; + border: 1px solid #555555; + transition: all 0.2s; + + &:hover { + background: #555555; + color: #ffffff; + } + } + + &.fab { + border-radius: 50%; + background: var(--color-success); + } + + &:hover { + cursor: pointer; + } +} diff --git a/ui/canopyjs/src/components/forms/AuthForm.scss b/ui/canopyjs/src/components/forms/AuthForm.scss new file mode 100644 index 0000000000..87ba38e9f0 --- /dev/null +++ b/ui/canopyjs/src/components/forms/AuthForm.scss @@ -0,0 +1,46 @@ +/** + * Copyright 2018-2020 Cargill Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.auth-form { + padding: 1rem; + width: 30rem; + + h3 { + margin: 0; + } + + hr { + border-top: 2px solid var(--color-dark-grey); + margin-left: 0; + margin-bottom: 1rem; + width: 12rem; + } + + form { + position: relative; + display: flex; + flex-direction: column; + flex-wrap: wrap; + + button.submit { + display: flex; + margin: 2rem 0 0; + position: relative; + margin-left: auto; + background: var(--color-secondary); + } + } +} diff --git a/ui/canopyjs/src/components/index.js b/ui/canopyjs/src/components/index.js new file mode 100644 index 0000000000..63840390ce --- /dev/null +++ b/ui/canopyjs/src/components/index.js @@ -0,0 +1,20 @@ +/** + * Copyright 2018-2020 Cargill Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { NavItem } from './navigation/NavItem'; +export { SideNav } from './navigation/SideNav'; +export { Progress } from './progress/Progress'; +export { TabBox } from './tabBox/TabBox'; +export { Input } from './input/Input'; diff --git a/ui/canopyjs/src/components/input/Input.js b/ui/canopyjs/src/components/input/Input.js new file mode 100644 index 0000000000..5c378153e6 --- /dev/null +++ b/ui/canopyjs/src/components/input/Input.js @@ -0,0 +1,39 @@ +/** + * Copyright 2018-2020 Cargill Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import PropTypes from 'prop-types'; + +export const Input = ({ type, id, label, required }) => { + return ( +
+ + +
+ ); +}; + +Input.propTypes = { + type: PropTypes.string.isRequired, + id: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + required: PropTypes.bool.isRequired +}; diff --git a/ui/canopyjs/src/components/input/Input.scss b/ui/canopyjs/src/components/input/Input.scss new file mode 100644 index 0000000000..fe2d173d57 --- /dev/null +++ b/ui/canopyjs/src/components/input/Input.scss @@ -0,0 +1,65 @@ +/** + * Copyright 2018-2020 Cargill Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.input { + position: relative; + padding: 1rem 0 0; + margin-top: 0.5rem; + + label { + position: absolute; + top: 0; + display: block; + transition: 0.2s; + font-size: 0.7rem; + } + + input { + height: calc(2rem + 2px); + border: 1px solid #333333; + padding: 0.5rem; + background: transparent; + transition: border-color 0.2s; + font-size: 1rem; + width: 100%; + + &::placeholder { + color: transparent; + } + + &:placeholder-shown { + border-color: #555555; + + & ~ label { + cursor: text; + top: 1.5rem; + padding-left: 0.5rem; + font-size: 1rem; + } + } + + &:focus { + border-color: var(--color-primary); + + ~ label { + font-size: 0.7rem; + font-weight: bold; + top: 0; + padding-left: 0; + } + } + } +} diff --git a/ui/canopyjs/src/components/navigation/Logout.js b/ui/canopyjs/src/components/navigation/Logout.js new file mode 100644 index 0000000000..42fd325b37 --- /dev/null +++ b/ui/canopyjs/src/components/navigation/Logout.js @@ -0,0 +1,74 @@ +/** + * Copyright 2018-2021 Cargill Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import Icon from '@material-ui/core/Icon'; +import { get } from '../../request'; + +export const Logout = () => { + const onLogout = async () => { + if (window.sessionStorage.getItem('CANOPY_USER')) { + const { splinterURL } = window.$CANOPY.getSharedConfig().canopyConfig; + const { token } = JSON.parse( + window.sessionStorage.getItem('CANOPY_USER') + ); + + sessionStorage.removeItem('CANOPY_USER'); + if (window.sessionStorage.getItem('CANOPY_KEYS')) { + window.sessionStorage.removeItem('CANOPY_KEYS'); + if (window.sessionStorage.getItem('KEY_SECRET')) { + window.sessionStorage.removeItem('KEY_SECRET'); + } + } + + if (sessionStorage.getItem('LOGOUT')) { + try { + await get( + `${splinterURL}${window.sessionStorage.getItem('LOGOUT')}`, + request => { + request.setRequestHeader('Authorization', `Bearer ${token}`); + } + ); + window.sessionStorage.removeItem('LOGOUT'); + window.location.href = `${window.location.origin}/login`; + } catch (err) { + switch (err.status) { + case 401: + window.location.href = `${window.location.origin}/login`; + break; + default: + break; + } + } + } else { + window.location.href = `${window.location.origin}/login`; + } + } else { + window.location.href = `${window.location.origin}/login`; + } + }; + + return ( + + ); +}; diff --git a/ui/canopyjs/src/components/navigation/NavItem.js b/ui/canopyjs/src/components/navigation/NavItem.js new file mode 100644 index 0000000000..9521e251ba --- /dev/null +++ b/ui/canopyjs/src/components/navigation/NavItem.js @@ -0,0 +1,74 @@ +/** + * Copyright 2018-2020 Cargill Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import Icon from '@material-ui/core/Icon'; + +export const NavItem = props => { + const { path, icon, label, logo } = props; + + const classes = classnames('nav-tab', { + 'page-active': path === `/${window.location.pathname.split('/')[1]}` + }); + + return ( + +
+
+ {logo ? ( + {label} + ) : ( + {icon} + )} +
+
+
{label}
+
+ ); +}; + +const iconImagePropsCheck = (props, propName, componentName) => { + if (!props.icon && !props.logo) { + return new Error( + `One of 'logo' or 'icon' is required by '${componentName}' component` + ); + } + + if ( + typeof props[propName] !== 'string' && + typeof props[propName] !== 'undefined' + ) { + return new Error( + `Invalid prop '${propName}' passed to '${componentName}': must be a string` + ); + } + + return true; +}; + +NavItem.propTypes = { + icon: iconImagePropsCheck, + label: PropTypes.string.isRequired, + logo: iconImagePropsCheck, + path: PropTypes.string.isRequired +}; + +NavItem.defaultProps = { + icon: undefined, + logo: undefined +}; diff --git a/ui/canopyjs/src/components/navigation/SideNav.js b/ui/canopyjs/src/components/navigation/SideNav.js new file mode 100644 index 0000000000..d77bbc1d14 --- /dev/null +++ b/ui/canopyjs/src/components/navigation/SideNav.js @@ -0,0 +1,77 @@ +/** + * Copyright 2018-2020 Cargill Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import classnames from 'classnames'; +import Icon from '@material-ui/core/Icon'; +import { Logout } from './Logout'; +import { useUserSaplings } from '../../CanopyContext'; + +import { NavItem } from './NavItem'; + +export const SideNav = () => { + const makeUserSaplingTabs = userSaplings => + userSaplings + .map(({ displayName, namespace, icon, logo }) => { + return { + path: `/${namespace}`, + displayName, + icon, + logo + }; + }) + .map(({ path, displayName, icon, logo }) => { + return ( + + ); + }); + + return ( + <> + +
+ +
{makeUserSaplingTabs(useUserSaplings())}
+
+ +
+ + ); +}; + +function ProfileTab() { + const profileClasses = classnames('profile-tab', 'tab', { + 'page-active': `${window.location.pathname.split('/')[1]}` === 'profile' + }); + + return ( + +
+
+ person_icon +
+
+
Profile
+ +
+ ); +} diff --git a/ui/canopyjs/src/components/navigation/SideNav.scss b/ui/canopyjs/src/components/navigation/SideNav.scss new file mode 100644 index 0000000000..bdc92e0dbf --- /dev/null +++ b/ui/canopyjs/src/components/navigation/SideNav.scss @@ -0,0 +1,249 @@ +/** + * Copyright 2018-2020 Cargill Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.side-nav { + display: flex; + flex-direction: column; + position: relative; + top: 0; + left: 0; + width: 75px; + height: 100vh; + background: #595C60; + box-shadow: 0 0 6px rgba(0,0,0,.25); + + hr { + margin: 0; + border: 0; + width: 100%; + height: 1px; + + &.bottom { + margin-top: auto; + } + } +} + +.brand { + display: flex; + flex-direction: column; + align-items: center; + text-decoration: none; + width: 75px; + height: 56px; + background-color: var(--color-brand-background); + background-image: var(--brand-img-url); + background-size: 37px 37px; + background-position: 50%; + background-repeat: no-repeat; +} + +.nav-items { + margin-top: 3rem; + display: flex; + flex-direction: column; + + .nav-tab { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 75px; + height: 75px; + text-decoration: none; + transition: $transitionDuration-s; + + .border{ + height: 51px; + width: 51px; + border-radius: 17px; + display: flex; + align-self: center; + align-items: center; + justify-content: center; + + .icon { + background-color: #FFFFFF; + display: flex; + justify-content: center; + border: none; + color: var(--color-primary); + width: 37px; + height: 37px; + align-items: center; + border-radius: 10px; + outline: none; + transition: $transitionDuration-s; + + .brand-logo { + width: 1em; + height: 1em; + overflow: hidden; + font-size: 1.5rem; + flex-shrink: 0; + user-select: none; + } + } + } + + .label { + margin-top: 5px; + font-size: 11px; + text-align: center; + color: #FFFFFF; + } + + &:hover { + .border { + border: 3px solid #999999; + + .icon { + color: var(--color-primary-light); + cursor: pointer; + } + } + } + } + .page-active { + @extend .nav-tab; + .border { + border: 3px solid #ffffff; + + .icon { + cursor: pointer; + } + } + &:hover { + .border { + color: var(--color-primary-light); + border: 3px solid #ffffff; + } + } + } +} + +.canopy-items { + display: flex; + justify-self: flex-end; + flex-direction: column; + justify-content: center; + margin-top: auto; + + .tab { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 75px; + height: 75px; + text-decoration: none; + + .border{ + height: 51px; + width: 51px; + border-radius: 17px; + display: flex; + align-self: center; + align-items: center; + justify-content: center; + + .icon { + background-color: #FFFFFF; + display: flex; + justify-content: center; + border: none; + color: var(--color-primary); + width: 37px; + height: 37px; + align-items: center; + border-radius: 10px; + outline: none; + transition: $transitionDuration-s; + } + } + + .Logout { + width: 0; + height: 0; + background: none; + overflow: hidden; + position: absolute; + border: unset; + display: flex; + align-items: center; + flex-direction: column; + text-decoration: none; + } + + .label { + margin-top: 5px; + font-size: 11px; + text-align: center; + color: #FFFFFF; + } + + &:hover { + .border { + border: 3px solid #999999; + + .icon { + color: var(--color-primary-light); + } + } + .Logout { + width: 75px; + height: 75px; + margin-bottom: 144px; + outline: none; + + .border { + border: none; + } + + .icon { + color: var(--color-primary); + } + + &:hover { + .border { + border: 3px solid #999999; + + .icon { + color: var(--color-primary-light); + cursor: pointer; + } + } + } + } + } + } + + .page-active { + @extend .tab; + .border { + border: 3px solid #ffffff; + + .icon { + } + } + &:hover { + .border { + color: var(--color-primary-light); + border: 3px solid #ffffff; + } + } + } +} diff --git a/ui/canopyjs/src/components/progress/Progress.js b/ui/canopyjs/src/components/progress/Progress.js new file mode 100644 index 0000000000..0a5cf62d66 --- /dev/null +++ b/ui/canopyjs/src/components/progress/Progress.js @@ -0,0 +1,27 @@ +/** + * Copyright 2018-2020 Cargill Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import PropTypes from 'prop-types'; + +export function Progress({ max, value }) { + return ; +} + +Progress.propTypes = { + max: PropTypes.number.isRequired, + value: PropTypes.number.isRequired +}; diff --git a/ui/canopyjs/src/components/progress/Progress.scss b/ui/canopyjs/src/components/progress/Progress.scss new file mode 100644 index 0000000000..7102fff1bd --- /dev/null +++ b/ui/canopyjs/src/components/progress/Progress.scss @@ -0,0 +1,79 @@ +/** + * Copyright 2018-2020 Cargill Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$progress-size: 6rem; + +$progress-colors: ( + 'background': $color-primary, + 'gutter': $background-light, + 'leaf': $color-primary-light +); + +@function asRGBString($color) { + @return 'rgb(' + red($color) + ',' + green($color) + ',' + blue($color) + ')'; +} + +@function makeLoadingSvg($colorKey) { + $svg-ns: 'xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"'; + $svgMime: 'data:image/svg+xml;utf8,'; + $svgDimensions: 'height="100" width="100" viewBox="-10 -10 120 120"'; + $svgOpen: ''; + $svgClose: ''; + $d: 'M 85,8 C 18,20 -6,29 20,73 L 10,88 L 15,93 L 30,78 C 93,98 98,62, 85,8 z M 70,30 C 56,38 43,47 40,60 L 35,55 C 44,41 56,34 70,30 z'; + $path: ''; + @return url($svgMime+$svgOpen+$path+$svgClose); +} + +@mixin progressGutter { + border-radius: 50%; + background-color: map-get($progress-colors, 'background'); + background-size: cover; + background-image: makeLoadingSvg('gutter'); +} + +@mixin progressLeaf { + background-color: transparent; + background-size: cover; + background-image: makeLoadingSvg('leaf'); +} + +progress.progress { + appearance: none; + border: none; + width: $progress-size; + height: $progress-size; + @include progressGutter; + + &[value] { + border-radius: 50%; + color: map-get($progress-colors, 'leaf'); + background-color: map-get($progress-colors, 'background'); + } + + &[value]::-webkit-progress-bar { + @include progressGutter; + } + + &[value]::-webkit-progress-value { + @include progressLeaf; + } + + &[value]::-moz-progress-bar { + @include progressLeaf; + } +} diff --git a/ui/canopyjs/src/components/tabBox/TabBox.js b/ui/canopyjs/src/components/tabBox/TabBox.js new file mode 100644 index 0000000000..5ad1d63777 --- /dev/null +++ b/ui/canopyjs/src/components/tabBox/TabBox.js @@ -0,0 +1,73 @@ +/** + * Copyright 2018-2020 Cargill Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useState } from 'react'; +import classnames from 'classnames'; +import PropTypes from 'prop-types'; + +export function TabBox({ contents, keyExtractor }) { + const titles = contents.map(({ title }) => title); + const contentElements = contents.map(({ content }) => content); + const keys = contents.map(keyExtractor); + + const [selectedTab, setSelectedTab] = useState(0); + const Content = + contentElements[selectedTab] && + typeof contentElements[selectedTab] === 'string' + ? () => <>{contentElements[selectedTab]} + : contentElements[selectedTab]; + + return ( +
+
+ {titles.map((title, index) => { + const Title = title; + const selected = index === selectedTab; + return ( +