├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── license.txt ├── package-lock.json ├── package.json ├── public ├── config.json ├── favicon.ico ├── index.html └── manifest.json └── src ├── components ├── App.js ├── LoadScreen.js ├── Main.js ├── UserAccount.js └── esri │ └── map │ └── Map.js ├── index.js ├── redux ├── index.js ├── reducers │ ├── auth.js │ ├── config.js │ └── map.js ├── sagas │ ├── _auth.js │ ├── _config.js │ └── index.js └── store.js ├── registerServiceWorker.js ├── styles ├── fonts.css ├── fonts │ ├── Montserrat-Black.ttf │ ├── Montserrat-BlackItalic.ttf │ ├── Montserrat-Bold.ttf │ ├── Montserrat-BoldItalic.ttf │ ├── Montserrat-ExtraBold.ttf │ ├── Montserrat-ExtraBoldItalic.ttf │ ├── Montserrat-ExtraLight.ttf │ ├── Montserrat-ExtraLightItalic.ttf │ ├── Montserrat-Italic.ttf │ ├── Montserrat-Light.ttf │ ├── Montserrat-LightItalic.ttf │ ├── Montserrat-Medium.ttf │ ├── Montserrat-MediumItalic.ttf │ ├── Montserrat-Regular.ttf │ ├── Montserrat-SemiBold.ttf │ ├── Montserrat-SemiBoldItalic.ttf │ ├── Montserrat-Thin.ttf │ └── Montserrat-ThinItalic.ttf ├── global.js └── images │ ├── Esri-React-Logo.svg │ └── Topo-Abs-BG.svg └── utils ├── map.js ├── request.js └── session.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # production 7 | /build 8 | _redirects 9 | _headers 10 | 11 | # misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | /out 18 | /sandboxes 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # Esri ArcGIS JS API + React + Redux 6 | Detailed documentation can be found on the wiki: https://github.com/Esri/esri-react-boot/wiki 7 | 8 | 9 | 10 | 11 | 12 | ## Features 13 | 14 | - Ready to deploy React application 15 | - Loads Esri's ArcGIS JS API 16 | - Map component that supports 2D/3D 17 | - Built in Authentication with ArcGIS REST JS 18 | - Includes Calcite-React with UI examples 19 | 20 | ## Tech 21 | 22 | This project uses a number of open source libraries. We encourage you to become familiar with these libraries before you begin here. 23 | 24 | | Library | Source | 25 | | ------ | ------ | 26 | | React | https://reactjs.org/ | 27 | | Redux | https://redux.js.org/ | 28 | | Create-React-App | https://facebook.github.io/create-react-app/ | 29 | | Redux-Saga | https://github.com/redux-saga/redux-saga | 30 | | esri-loader | https://github.com/Esri/esri-loader | 31 | | ArcGIS JS API | https://developers.arcgis.com/javascript/ | 32 | | ArcGIS REST JS | https://github.com/Esri/arcgis-rest-js | 33 | | Styled-Components | https://www.styled-components.com/ | 34 | | Calcite-React | https://github.com/Esri/calcite-react | 35 | 36 | ## Usage 37 | 38 | The project utilizes Node & Node Package Manager to install dependencies and run a local development server. As part of Create-React-App you'll need to install Node (includes NPM) before you can run the Esri-React-Boot: 39 | 40 | - Node - https://nodejs.org/en/ 41 | 42 | ### Installing 43 | 44 | Clone the repository and install the dependencies: 45 | 46 | ``` 47 | $ npm install 48 | ``` 49 | 50 | ### Running 51 | 52 | Build and run live server: 53 | 54 | ``` 55 | $ npm start 56 | ``` 57 | 58 | * defaults to http://localhost:3000 59 | 60 | ## Issues 61 | 62 | Find a bug or want to request a new feature? Please let us know by submitting an issue. 63 | 64 | ## Contributing 65 | 66 | Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). 67 | 68 | ## Licensing 69 | Copyright 2019 Esri 70 | 71 | Licensed under the Apache License, Version 2.0 (the "License"); 72 | you may not use this file except in compliance with the License. 73 | You may obtain a copy of the License at 74 | 75 | http://www.apache.org/licenses/LICENSE-2.0 76 | 77 | Unless required by applicable law or agreed to in writing, software 78 | distributed under the License is distributed on an "AS IS" BASIS, 79 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 80 | See the License for the specific language governing permissions and 81 | limitations under the License. 82 | 83 | A copy of the license is available in the repository's [license.txt](license.txt) file. 84 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Apache License - 2.0 2 | 3 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 4 | 5 | 1. Definitions. 6 | 7 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 8 | 9 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 10 | 11 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control 12 | with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management 13 | of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial 14 | ownership of such entity. 15 | 16 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 17 | 18 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, 19 | and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to 22 | compiled object code, generated documentation, and conversions to other media types. 23 | 24 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice 25 | that is included in or attached to the work (an example is provided in the Appendix below). 26 | 27 | "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 28 | editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes 29 | of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, 30 | the Work and Derivative Works thereof. 31 | 32 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work 33 | or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual 34 | or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of 35 | electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on 36 | electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for 37 | the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing 38 | by the copyright owner as "Not a Contribution." 39 | 40 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and 41 | subsequently incorporated within the Work. 42 | 43 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, 44 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, 45 | publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 46 | 47 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, 48 | non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, 49 | sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are 50 | necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was 51 | submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work 52 | or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You 53 | under this License for that Work shall terminate as of the date such litigation is filed. 54 | 55 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, 56 | and in Source or Object form, provided that You meet the following conditions: 57 | 58 | 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and 59 | 60 | 2. You must cause any modified files to carry prominent notices stating that You changed the files; and 61 | 62 | 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices 63 | from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 64 | 65 | 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a 66 | readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the 67 | Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the 68 | Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever 69 | such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. 70 | 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, 71 | provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to 72 | Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your 73 | modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with 74 | the conditions stated in this License. 75 | 76 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You 77 | to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, 78 | nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 79 | 80 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except 81 | as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 82 | 83 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides 84 | its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, 85 | any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for 86 | determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under 87 | this License. 88 | 89 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required 90 | by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, 91 | including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the 92 | use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or 93 | any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 94 | 95 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a 96 | fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting 97 | 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 98 | to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your 99 | accepting any such warranty or additional liability. 100 | 101 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-jsapi-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "/", 6 | "dependencies": { 7 | "@esri/arcgis-rest-auth": "^2.6.1", 8 | "@esri/arcgis-rest-portal": "^2.6.1", 9 | "@esri/arcgis-rest-request": "^2.6.1", 10 | "calcite-react": "^0.44.0", 11 | "esri-loader": "^2.12.0", 12 | "framer-motion": "^1.6.18", 13 | "js-cookie": "^2.2.1", 14 | "react": "^16.12.0", 15 | "react-dom": "^16.12.0", 16 | "react-redux": "^7.1.3", 17 | "react-router-dom": "^5.1.2", 18 | "react-transition-group": "^4.3.0", 19 | "redux": "^4.0.4", 20 | "redux-saga": "^1.1.3", 21 | "styled-components": "^4.4.1" 22 | }, 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build", 26 | "test": "react-scripts test --env=jsdom", 27 | "eject": "react-scripts eject" 28 | }, 29 | "devDependencies": { 30 | "react-scripts": "^3.3.0" 31 | }, 32 | "browserslist": { 33 | "development": [ 34 | "last 2 chrome versions", 35 | "last 2 firefox versions", 36 | "last 2 edge versions" 37 | ], 38 | "production": [ 39 | ">1%", 40 | "last 4 versions", 41 | "Firefox ESR", 42 | "not ie < 11" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /public/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appVersion": "2.0.0", 3 | 4 | "apiUrl": "api", 5 | "loginUrl": "api/auth/arcgis", 6 | "logoutUrl": "api/logout", 7 | "jsapiUrl": "https://js.arcgis.com/4.12/", 8 | "jsapiV4": true, 9 | 10 | "clientId": "hkdtqXBMmZ8JJ5tD", 11 | 12 | "sessionId": "esri-react-boot_session", 13 | 14 | "mapConfig": { 15 | "basemap": "dark-gray-vector", 16 | "center": [-95, 38], 17 | "zoom": 4 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | 14 | 15 | 16 | 25 | Esri-React-Boot 26 | 27 | 28 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Esri-React-Bootstrap App", 3 | "name": "Esri-React-Bootstrap", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "128x128", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | // React imports 13 | import React, { useEffect } from "react"; 14 | import { Route, Redirect } from "react-router-dom"; 15 | 16 | // Redux imports 17 | import { useSelector, useDispatch } from "react-redux"; 18 | import { fetchConfig } from "../redux/reducers/config"; 19 | import { checkAuth, startAuth, completeAuth } from "../redux/reducers/auth"; 20 | 21 | // Component imports 22 | import LoadScreen from "./LoadScreen"; 23 | import Main from "./Main"; 24 | 25 | // Component definition 26 | const App = props => { 27 | // we'll use the url to determin sign-in state 28 | const { pathname } = props.location; 29 | // redux store state 30 | const user = useSelector(state => state.auth.user); 31 | const config = useSelector(state => state.config); 32 | const dispatch = useDispatch(); 33 | 34 | // when the component mounts request the config and load it into the Redux state 35 | useEffect(() => { 36 | dispatch(fetchConfig()); 37 | }, [dispatch]); 38 | 39 | // once the component mounts and the config loads, check if we have a saved session 40 | useEffect(() => { 41 | // if the config isn't yet loaded then skip this effect 42 | if (!config.loaded) { 43 | return; 44 | } 45 | 46 | const { portalUrl, clientId, sessionId } = config; 47 | 48 | dispatch(checkAuth({ portalUrl, clientId, sessionId })); 49 | }, [config, dispatch]); 50 | 51 | // if there's no stored session, we'll watch the url path to see if we need to kick off an authentication 52 | // this can happen automatically with a portalUrl property in the config 53 | // or if the user requests a login through an event 54 | useEffect(() => { 55 | // if the config isn't yet loaded then skip this effect 56 | if (!config.loaded) { 57 | return; 58 | } 59 | 60 | const { portalUrl, clientId, sessionId } = config; 61 | 62 | // we'll start the authentication here and it will return here to complete 63 | if (portalUrl && !user && pathname !== "/auth") { 64 | dispatch(startAuth({ portalUrl, clientId, sessionId })); 65 | } else if (pathname === "/auth" && !user) { 66 | dispatch(completeAuth({ portalUrl, clientId, sessionId })); 67 | } 68 | }, [config, user, pathname, dispatch]); 69 | 70 | // set a halt state to allow the authentication process to complete before 71 | // we redirect to the main component 72 | let signInRequested = false; 73 | if (pathname === "/auth") { 74 | signInRequested = true; 75 | } 76 | 77 | // RENDER RETURN 78 | // app is initializing for the following reasons, show the load screen 79 | // 1. config is not yet loaded 80 | // 2. authentication is required but there is no user information 81 | // 3. authentication is not required but user has requested to sign-in 82 | if ( 83 | !config.loaded || 84 | (config.portalUrl && !user) || 85 | (signInRequested && !user) 86 | ) { 87 | return ; 88 | } 89 | 90 | // App is initialized and user is authenticated if needed, route to main component 91 | return ( 92 | <> 93 | 94 | 95 | 96 | ); 97 | }; 98 | 99 | export default App; 100 | -------------------------------------------------------------------------------- /src/components/LoadScreen.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | // React 13 | import React from "react"; 14 | 15 | // Components 16 | import Loader from "calcite-react/Loader"; 17 | import background from "../styles/images/Topo-Abs-BG.svg"; 18 | import logo from "../styles/images/Esri-React-Logo.svg"; 19 | 20 | // Styled & Motion Components 21 | import styled from "styled-components"; 22 | import { motion } from "framer-motion"; 23 | 24 | const Container = styled.div` 25 | position: absolute; 26 | height: 100%; 27 | width: 100%; 28 | background: rgba(255, 255, 255, 0.1) url(${background}) no-repeat center/cover; 29 | background-blend-mode: screen; 30 | `; 31 | 32 | const Wrapper = styled.div` 33 | position: absolute; 34 | top: 50%; 35 | left: 50%; 36 | transform: translate(-50%); 37 | `; 38 | 39 | const Title = styled.div` 40 | position: absolute; 41 | right: 0; 42 | bottom: 0; 43 | z-index: 20; 44 | display: flex; 45 | align-items: center; 46 | justify-content: flex-end; 47 | width: 100%; 48 | padding: 2em; 49 | text-align: right; 50 | color: white; 51 | `; 52 | 53 | const Label = styled.h1` 54 | font-size: 3em; 55 | text-shadow: -2px 2px 8px rgba(0, 0, 0, 0.25); 56 | `; 57 | 58 | const Logo = styled.img` 59 | width: 5em; 60 | height: 100%; 61 | margin-right: 1em; 62 | `; 63 | 64 | const FadingContainer = () => ( 65 | 75 | 76 | 77 | ); 78 | 79 | const LoadScreen = props => { 80 | if (!props.isLoading) { 81 | return ( 82 | 83 | 84 | 85 | 86 | 87 | <Logo src={logo}></Logo> 88 | <Label>Esri-React-Boot</Label> 89 | 90 | 91 | ); 92 | } 93 | 94 | return ; 95 | }; 96 | 97 | export default LoadScreen; 98 | -------------------------------------------------------------------------------- /src/components/Main.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | // React imports 13 | import React from "react"; 14 | 15 | // Redux imports 16 | import { useSelector, useDispatch } from "react-redux"; 17 | import { mapLoaded } from "../redux/reducers/map"; 18 | import { startAuth, logout } from "../redux/reducers/auth"; 19 | 20 | // Component imports 21 | import TopNav from "calcite-react/TopNav"; 22 | import TopNavBrand from "calcite-react/TopNav/TopNavBrand"; 23 | import TopNavTitle from "calcite-react/TopNav/TopNavTitle"; 24 | import TopNavList from "calcite-react/TopNav/TopNavList"; 25 | import TopNavLink from "calcite-react/TopNav/TopNavLink"; 26 | import Map from "./esri/map/Map"; 27 | import LoadScreen from "./LoadScreen"; 28 | import UserAccount from "./UserAccount"; 29 | import logo from "../styles/images/Esri-React-Logo.svg"; 30 | 31 | // Styled Components 32 | import styled from "styled-components"; 33 | 34 | const Container = styled.div` 35 | display: flex; 36 | flex-direction: column; 37 | position: absolute; 38 | width: 100%; 39 | height: 100%; 40 | text-align: center; 41 | `; 42 | 43 | const MapWrapper = styled.div` 44 | display: flex; 45 | flex: 1; 46 | flex-direction: column; 47 | position: relative; 48 | z-index: 0; 49 | overflow: hidden; 50 | `; 51 | 52 | const Logo = styled(TopNavBrand)` 53 | justify-content: center; 54 | & img { 55 | height: 55px; 56 | } 57 | `; 58 | 59 | const Nav = styled(TopNav)` 60 | && { 61 | z-index: 5; 62 | } 63 | `; 64 | 65 | const NavList = styled(TopNavList)` 66 | text-align: left; 67 | `; 68 | 69 | // Component definition 70 | const Main = props => { 71 | const auth = useSelector(state => state.auth); 72 | const config = useSelector(state => state.config); 73 | const isMapLoaded = useSelector(state => state.map.loaded); 74 | const dispatch = useDispatch(); 75 | 76 | // Sign in button click event 77 | const signIn = () => { 78 | const { clientId, sessionId, popup } = config; 79 | dispatch( 80 | startAuth({ 81 | clientId, 82 | sessionId, 83 | popup, 84 | signInRequest: true 85 | }) 86 | ); 87 | }; 88 | 89 | // Sign out button click event 90 | const signOut = () => { 91 | dispatch(logout(config.sessionId)); 92 | }; 93 | 94 | return ( 95 | 96 | 97 | 98 | 121 | 122 | 123 | 124 | 125 | 126 | ); 127 | }; 128 | 129 | export default Main; 130 | -------------------------------------------------------------------------------- /src/components/UserAccount.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | /** 13 | * This is an example component used to showcase authentication 14 | * @type {Class} 15 | */ 16 | 17 | // React 18 | import React from "react"; 19 | 20 | // Components 21 | import TopNavActionsList from "calcite-react/TopNav/TopNavActionsList"; 22 | import ArcgisAccount from "calcite-react/ArcgisAccount"; 23 | import ArcgisAccountMenuItem from "calcite-react/ArcgisAccount/ArcgisAccountMenuItem"; 24 | import Button from "calcite-react/Button"; 25 | 26 | // Class 27 | const UserAccount = props => { 28 | const signedInActionsComponent = props.user ? ( 29 | 30 | console.log("switch account clicked")} 35 | onRequestSignOut={props.signOut} 36 | > 37 | console.log("Profile & Settings clicked")} 39 | > 40 | Profile & Settings 41 | 42 | console.log("My Esri clicked")}> 43 | My Esri 44 | 45 | console.log("Training clicked")}> 46 | Training 47 | 48 | console.log("Community & Forums clicked")} 50 | > 51 | Community & Forums 52 | 53 | console.log("ArcGIS Online clicked")} 55 | > 56 | ArcGIS Online 57 | 58 | 59 | 60 | ) : null; 61 | 62 | const signedOutActionsComponent = ( 63 | 64 | 67 | 68 | ); 69 | 70 | let outputComponent = props.loggedIn 71 | ? signedInActionsComponent 72 | : signedOutActionsComponent; 73 | 74 | return
{outputComponent}
; 75 | }; 76 | 77 | export default UserAccount; 78 | -------------------------------------------------------------------------------- /src/components/esri/map/Map.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | // NOTE 13 | // This is a "special" react component that does not strictly play by 14 | // React's rules. 15 | // 16 | // Conceptually, this component creates a "portal" in React by 17 | // closing its render method off from updates (by simply rendering a div and 18 | // never accepting re-renders) then reconnecting itself to the React lifecycle 19 | // by listening for any new props (using componentWillReceiveProps) 20 | 21 | // React imports 22 | import React from "react"; 23 | 24 | // ESRI ArcGIS API 25 | import { loadMap } from "../../../utils/map"; 26 | 27 | // Styled Components 28 | import styled from "styled-components"; 29 | 30 | const Container = styled.div` 31 | height: 100%; 32 | width: 100%; 33 | `; 34 | 35 | // Component 36 | const Map = props => { 37 | // set an ID for the map to attach to 38 | const containerID = "map-view-container"; 39 | 40 | // load map with config properties 41 | loadMap(containerID, props.mapConfig).then(() => { 42 | // call the map loaded event when we get the map view back 43 | props.onMapLoaded(); 44 | }); 45 | 46 | // Compnent template 47 | return ; 48 | }; 49 | 50 | export default Map; 51 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | // React // 13 | import React from "react"; 14 | import ReactDOM from "react-dom"; 15 | 16 | // Redux // 17 | import { Provider } from "react-redux"; 18 | import { initStore } from "./redux/store"; 19 | 20 | // React Router // 21 | import { BrowserRouter, Route } from "react-router-dom"; 22 | 23 | // Components // 24 | import { homepage } from "../package.json"; 25 | import App from "./components/App"; 26 | 27 | // Styles // 28 | import CalciteThemeProvider from "calcite-react/CalciteThemeProvider"; 29 | import { GlobalStyle } from "./styles/global"; 30 | import "./styles/fonts.css"; 31 | 32 | // App runs at the root locally, but under /{homepage} in production 33 | let basename; 34 | process.env.NODE_ENV !== "production" ? (basename = "") : (basename = homepage); 35 | 36 | // Create Redux Store 37 | export const store = initStore(); 38 | 39 | // App entry point 40 | ReactDOM.render( 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | , 49 | document.getElementById("root") 50 | ); 51 | -------------------------------------------------------------------------------- /src/redux/index.js: -------------------------------------------------------------------------------- 1 | export { default as auth } from './reducers/auth'; 2 | export { default as config } from './reducers/config'; 3 | export { default as map } from './reducers/map'; 4 | -------------------------------------------------------------------------------- /src/redux/reducers/auth.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | // ACTION TYPES // 13 | export const types = { 14 | AUTH_START: "AUTH_START", 15 | AUTH_COMPLETE: "AUTH_COMPLETE", 16 | AUTH_SUCCESS: "AUTH_SUCCESS", 17 | AUTH_FAIL: "AUTH_FAIL", 18 | AUTH_CHECK: "AUTH_CHECK", 19 | LOGOUT: "LOGOUT" 20 | }; 21 | 22 | // REDUCERS // 23 | export const initialState = { 24 | user: null, 25 | portal: null, 26 | token: null, 27 | loggedIn: false 28 | }; 29 | 30 | export default (state = initialState, action) => { 31 | switch (action.type) { 32 | case types.AUTH_SUCCESS: 33 | const { user, portal, token } = action.payload; 34 | 35 | return { 36 | user, 37 | portal, 38 | token, 39 | loggedIn: true 40 | }; 41 | 42 | case types.AUTH_FAIL: 43 | return { 44 | user: null, 45 | portal: null, 46 | token: null, 47 | loggedIn: false 48 | }; 49 | 50 | default: 51 | return state; 52 | } 53 | }; 54 | 55 | // ACTIONS // 56 | export const checkAuth = options => ({ 57 | type: types.AUTH_CHECK, 58 | payload: options 59 | }); 60 | 61 | export const startAuth = options => ({ 62 | type: types.AUTH_START, 63 | payload: options 64 | }); 65 | 66 | export const completeAuth = options => ({ 67 | type: types.AUTH_COMPLETE, 68 | payload: options 69 | }); 70 | 71 | export const logout = sessionId => ({ 72 | type: types.LOGOUT, 73 | payload: { 74 | sessionId: sessionId 75 | } 76 | }); 77 | -------------------------------------------------------------------------------- /src/redux/reducers/config.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | // ACTION TYPES // 13 | export const types = { 14 | SET_CONFIG: "SET_CONFIG", 15 | CONFIG_FETCH: "CONFIG_FETCH" 16 | }; 17 | 18 | // REDUCERS // 19 | export const initialState = { 20 | loaded: false 21 | }; 22 | 23 | export default (state = initialState, action) => { 24 | switch (action.type) { 25 | case types.SET_CONFIG: 26 | return { 27 | ...state, 28 | ...action.payload, 29 | loaded: true 30 | }; 31 | default: 32 | return state; 33 | } 34 | }; 35 | 36 | // ACTIONS // 37 | export const fetchConfig = () => ({ type: types.CONFIG_FETCH }); 38 | -------------------------------------------------------------------------------- /src/redux/reducers/map.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | // ACTION TYPES // 13 | export const types = { 14 | MAP_LOADED: "MAP_LOADED" 15 | }; 16 | 17 | // REDUCERS // 18 | export const initialState = { 19 | loaded: false 20 | }; 21 | 22 | export default (state = initialState, action) => { 23 | switch (action.type) { 24 | case types.MAP_LOADED: 25 | return { 26 | ...state, 27 | loaded: true 28 | }; 29 | default: 30 | return state; 31 | } 32 | }; 33 | 34 | // ACTIONS // 35 | export const mapLoaded = () => ({ type: types.MAP_LOADED, payload: {} }); 36 | -------------------------------------------------------------------------------- /src/redux/sagas/_auth.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | import { call, put, takeLatest } from "redux-saga/effects"; 13 | import { types } from "../reducers/auth"; 14 | import { 15 | signIn, 16 | completeSignIn, 17 | signOut, 18 | restoreSession 19 | } from "../../utils/session"; 20 | 21 | // WORKER // 22 | function* startAuth(action) { 23 | try { 24 | // first try to restore the session from a saved cookie if it exists 25 | let authInfos = yield call(restoreSession, action.payload.sessionId); 26 | 27 | // if there isn't a session cookie then we can start a new session 28 | if (!authInfos) { 29 | authInfos = yield call(signIn, action.payload); 30 | } 31 | 32 | // check for a response and finish by sending the authentication info to the Redux store 33 | if (authInfos) { 34 | yield put({ 35 | type: types.AUTH_SUCCESS, 36 | payload: authInfos 37 | }); 38 | } else { 39 | // error catching if we need it 40 | yield put({ type: types.AUTH_FAIL }); 41 | } 42 | } catch (e) { 43 | yield put({ type: types.AUTH_FAIL }); 44 | console.error("SAGA ERROR: auth/startAuth, ", e); 45 | } 46 | } 47 | 48 | function* completeAuth(action) { 49 | try { 50 | const authInfos = yield call(completeSignIn, action.payload); 51 | 52 | // check for a response and finish by sending the authentication info to the Redux store 53 | if (authInfos) { 54 | yield put({ 55 | type: types.AUTH_SUCCESS, 56 | payload: authInfos 57 | }); 58 | } else { 59 | // error catching if we need it 60 | yield put({ type: types.AUTH_FAIL }); 61 | } 62 | } catch (e) { 63 | yield put({ type: types.AUTH_FAIL }); 64 | console.error("SAGA ERROR: auth/startAuth, ", e); 65 | } 66 | } 67 | 68 | function* checkAuth(action) { 69 | try { 70 | let authInfos = yield call(restoreSession, action.payload.sessionId); 71 | 72 | // check for a response and finish by sending the authentication info to the Redux store 73 | if (authInfos) { 74 | yield put({ 75 | type: types.AUTH_SUCCESS, 76 | payload: authInfos 77 | }); 78 | } else { 79 | // putting a fail call here just means that we didn't need to login 80 | yield put({ type: types.AUTH_FAIL }); 81 | } 82 | } catch (e) { 83 | yield put({ type: types.AUTH_FAIL }); 84 | console.error("SAGA ERROR: auth/checkAuth, ", e); 85 | } 86 | } 87 | 88 | function* authLogout(action) { 89 | try { 90 | yield call(signOut, action.payload.sessionId); 91 | 92 | window.location.reload(); 93 | } catch (e) { 94 | console.error("SAGA ERROR: auth/logout, ", e); 95 | } 96 | } 97 | 98 | // WATCHER // 99 | export function* watchStartAPI() { 100 | yield takeLatest(types.AUTH_CHECK, checkAuth); 101 | yield takeLatest(types.AUTH_START, startAuth); 102 | yield takeLatest(types.AUTH_COMPLETE, completeAuth); 103 | yield takeLatest(types.LOGOUT, authLogout); 104 | } 105 | -------------------------------------------------------------------------------- /src/redux/sagas/_config.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | import { call, put, takeLatest } from "redux-saga/effects"; 13 | import { types as configTypes } from "../reducers/config"; 14 | import { getAppConfig } from "../../utils/request"; 15 | 16 | // WORKER // 17 | function* fetchConfig(action) { 18 | try { 19 | // fetch config 20 | const config = yield call(getAppConfig); 21 | 22 | // load config into Redux store 23 | yield put({ 24 | type: configTypes.SET_CONFIG, 25 | payload: config 26 | }); 27 | } catch (e) { 28 | console.error("SAGA ERROR: config/fetchConfig, ", e); 29 | } 30 | } 31 | 32 | // WATCHER // 33 | export function* watchFetchConfig() { 34 | yield takeLatest(configTypes.CONFIG_FETCH, fetchConfig); 35 | } 36 | -------------------------------------------------------------------------------- /src/redux/sagas/index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | /** 13 | * Combine your Sagas into one output 14 | */ 15 | 16 | import { all, fork } from 'redux-saga/effects'; 17 | 18 | import * as authSagas from './_auth'; 19 | import * as configSagas from './_config'; 20 | 21 | export default function* rootSaga() { 22 | yield all([ 23 | ...Object.values(authSagas), 24 | ...Object.values(configSagas), 25 | // more sagas from different files 26 | ].map(fork)); 27 | } 28 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | /** 13 | * Configure and create the Redux here 14 | * includes Saga 15 | * @type {Object} This is the store object that Redux uses 16 | */ 17 | 18 | // REDUX IMPORTS // 19 | import { applyMiddleware, combineReducers, compose, createStore } from 'redux'; 20 | import createSagaMiddleware from 'redux-saga'; 21 | import rootSaga from './sagas/index'; 22 | 23 | import * as reducers from './'; 24 | 25 | export function initStore() { 26 | // Setup Redux dev tools 27 | // NOTE - Redux Devtool issue - see https://github.com/zalmoxisus/redux-devtools-extension/issues/619 28 | const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 29 | 30 | // Setup Redux store 31 | const rootReducer = combineReducers(reducers); 32 | const sagaMiddleware = createSagaMiddleware(); 33 | 34 | const store = createStore( 35 | rootReducer, 36 | composeEnhancer(applyMiddleware(sagaMiddleware)) 37 | ); 38 | 39 | // Run sagas 40 | sagaMiddleware.run(rootSaga); 41 | 42 | return store; 43 | } 44 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === "localhost" || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === "[::1]" || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener("load", () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (!isLocalhost) { 36 | // Is not local host. Just register service worker 37 | registerValidSW(swUrl); 38 | } else { 39 | // This is running on localhost. Lets check if a service worker still exists or not. 40 | checkValidServiceWorker(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === "installed") { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log("New content is available; please refresh."); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log("Content is cached for offline use."); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error("Error during service worker registration:", error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get("content-type").indexOf("javascript") === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | "No internet connection found. App is running in offline mode." 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ("serviceWorker" in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/styles/fonts.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @font-face { 3 | font-family: "Montserrat"; 4 | src: url("./fonts/Montserrat-Thin.ttf") format("truetype"); 5 | font-weight: 100; 6 | font-style: normal; 7 | } 8 | @font-face { 9 | font-family: "Montserrat"; 10 | src: url("./fonts/Montserrat-ThinItalic.ttf") format("truetype"); 11 | font-weight: 100; 12 | font-style: italic; 13 | } 14 | @font-face { 15 | font-family: "Montserrat"; 16 | src: url("./fonts/Montserrat-ExtraLight.ttf") format("truetype"); 17 | font-weight: 200; 18 | font-style: normal; 19 | } 20 | @font-face { 21 | font-family: "Montserrat"; 22 | src: url("./fonts/Montserrat-ExtraLightItalic.ttf") format("truetype"); 23 | font-weight: 200; 24 | font-style: italic; 25 | } 26 | @font-face { 27 | font-family: "Montserrat"; 28 | src: url("./fonts/Montserrat-Light.ttf") format("truetype"); 29 | font-weight: 300; 30 | font-style: normal; 31 | } 32 | @font-face { 33 | font-family: "Montserrat"; 34 | src: url("./fonts/Montserrat-LightItalic.ttf") format("truetype"); 35 | font-weight: 300; 36 | font-style: italic; 37 | } 38 | @font-face { 39 | font-family: "Montserrat"; 40 | src: url("./fonts/Montserrat-Regular.ttf") format("truetype"); 41 | font-weight: 400; 42 | font-style: normal; 43 | } 44 | @font-face { 45 | font-family: "Montserrat"; 46 | src: url("./fonts/Montserrat-Italic.ttf") format("truetype"); 47 | font-weight: 400; 48 | font-style: italic; 49 | } 50 | @font-face { 51 | font-family: "Montserrat"; 52 | src: url("./fonts/Montserrat-Medium.ttf") format("truetype"); 53 | font-weight: 500; 54 | font-style: normal; 55 | } 56 | @font-face { 57 | font-family: "Montserrat"; 58 | src: url("./fonts/Montserrat-MediumItalic.ttf") format("truetype"); 59 | font-weight: 500; 60 | font-style: italic; 61 | } 62 | @font-face { 63 | font-family: "Montserrat"; 64 | src: url("./fonts/Montserrat-SemiBold.ttf") format("truetype"); 65 | font-weight: 600; 66 | font-style: normal; 67 | } 68 | @font-face { 69 | font-family: "Montserrat"; 70 | src: url("./fonts/Montserrat-SemiBoldItalic.ttf") format("truetype"); 71 | font-weight: 600; 72 | font-style: italic; 73 | } 74 | @font-face { 75 | font-family: "Montserrat"; 76 | src: url("./fonts/Montserrat-Bold.ttf") format("truetype"); 77 | font-weight: 700; 78 | font-style: normal; 79 | } 80 | @font-face { 81 | font-family: "Montserrat"; 82 | src: url("./fonts/Montserrat-BoldItalic.ttf") format("truetype"); 83 | font-weight: 700; 84 | font-style: italic; 85 | } 86 | @font-face { 87 | font-family: "Montserrat"; 88 | src: url("./fonts/Montserrat-ExtraBold.ttf") format("truetype"); 89 | font-weight: 800; 90 | font-style: normal; 91 | } 92 | @font-face { 93 | font-family: "Montserrat"; 94 | src: url("./fonts/Montserrat-ExtraBoldItalic.ttf") format("truetype"); 95 | font-weight: 800; 96 | font-style: italic; 97 | } 98 | @font-face { 99 | font-family: "Montserrat"; 100 | src: url("./fonts/Montserrat-Black.ttf") format("truetype"); 101 | font-weight: 900; 102 | font-style: normal; 103 | } 104 | @font-face { 105 | font-family: "Montserrat"; 106 | src: url("./fonts/Montserrat-Black.ttf") format("truetype"); 107 | font-weight: 900; 108 | font-style: italic; 109 | } 110 | -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-Black.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-BlackItalic.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-BoldItalic.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-ExtraBold.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-ExtraLight.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-Italic.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-Light.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-LightItalic.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-Medium.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-MediumItalic.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-SemiBold.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-Thin.ttf -------------------------------------------------------------------------------- /src/styles/fonts/Montserrat-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/esri-react-boot/59e0a723f381bd1fc4add400acbb436aa04f8c6a/src/styles/fonts/Montserrat-ThinItalic.ttf -------------------------------------------------------------------------------- /src/styles/global.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | import { createGlobalStyle } from "styled-components"; 13 | 14 | // Global page styling 15 | // override AGIS JS API Widgets here 16 | export const GlobalStyle = createGlobalStyle` 17 | html, 18 | body { 19 | height: 100%; 20 | font-family: Montserrat, sans-serif; 21 | } 22 | 23 | body { 24 | margin: 0; 25 | padding: 0; 26 | font-weight: 300; 27 | letter-spacing: 1px; 28 | font-size: 16px; 29 | } 30 | 31 | .esri-layer-list__item{ 32 | &::before { 33 | width: 100%; 34 | left: 0; 35 | } 36 | } 37 | `; 38 | -------------------------------------------------------------------------------- /src/styles/images/Esri-React-Logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | Artboard 1 16 | 19 | 22 | 25 | 26 | 47 | 48 | 51 | 54 | 57 | 58 | 59 | 62 | 65 | 68 | 69 | 70 | 73 | 76 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/utils/map.js: -------------------------------------------------------------------------------- 1 | import { loadModules } from "esri-loader"; 2 | 3 | export function loadMap(element, mapOptions) { 4 | return loadModules(["esri/Map", "esri/views/MapView"], { 5 | css: true 6 | }).then(([Map, MapView]) => { 7 | if (!element) { 8 | // component or app was likely destroyed 9 | return; 10 | } 11 | // create the Map 12 | const map = new Map(mapOptions); 13 | // show the map at the element 14 | let view = new MapView({ 15 | map, 16 | container: element, 17 | ...mapOptions 18 | }); 19 | // wait for the view to load TODO: may not need this? 20 | return view.when(() => { 21 | // return a reference to the view 22 | return view; 23 | }); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Esri 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License.​ 11 | 12 | function status(response) { 13 | if (response.status >= 200 && response.status < 300) { 14 | return Promise.resolve(response); 15 | } else { 16 | return Promise.reject(new Error(response.statusText)); 17 | } 18 | } 19 | 20 | function handleResponse(handleAs, response) { 21 | switch (handleAs) { 22 | case "text": 23 | return response.text(); 24 | default: 25 | return response.json(); 26 | } 27 | } 28 | 29 | function objectToUrlSearchParams(obj) { 30 | const body = new URLSearchParams(); 31 | for (var prop in obj) { 32 | if (obj.hasOwnProperty(prop)) { 33 | body.append(prop, obj[prop]); 34 | } 35 | } 36 | 37 | // add f=json if not included 38 | if (!body.has("f")) { 39 | body.append("f", "json"); 40 | } 41 | 42 | return body.toString(); 43 | } 44 | 45 | function getHeaders(isFormData) { 46 | const headers = {}; 47 | 48 | if (!isFormData) { 49 | headers["content-type"] = "application/x-www-form-urlencoded"; 50 | } 51 | 52 | return new Headers(headers); 53 | } 54 | 55 | function getRequestBody(data, isFormData) { 56 | // if formdata, make formdata 57 | if (isFormData) { 58 | return this.objectToFormData(data); 59 | } 60 | 61 | // Not formdata, make url param 62 | return objectToUrlSearchParams(data); 63 | } 64 | 65 | /** 66 | * Make a request using fetch() 67 | * @param { Object } params Object containing key/value parameters to pass to fetch() 68 | * @return { Promise} Promise returned by fetch() 69 | */ 70 | export function makeRequest(params) { 71 | return new Promise((resolve, reject) => { 72 | let url = params.url; 73 | const data = params.data || {}; 74 | const headers = getHeaders(params.isFormData); 75 | const options = { 76 | method: params.method || "get", 77 | headers 78 | }; 79 | 80 | if (!params.hideCredentials) { 81 | options.credentials = "include"; 82 | } 83 | 84 | let body = getRequestBody(data, params.isFormData); 85 | 86 | if (options.method === "get") { 87 | url = `${url}?${body}`; 88 | } else { 89 | options.body = body; 90 | } 91 | 92 | fetch(url, options) 93 | .then(status) 94 | .then(handleResponse.bind(null, params.handleAs)) 95 | .then(function(data) { 96 | // Handle successful requests that are actually errors... 97 | if (data.error) { 98 | reject(data.error); 99 | return; 100 | } 101 | resolve(data); 102 | }) 103 | .catch(function(error) { 104 | reject(error); 105 | }); 106 | }); 107 | } 108 | 109 | /** 110 | * Make a request for the App Config [public/config] with makeRequest() 111 | * @return { Promise} Promise returned by makeRequest() 112 | */ 113 | export function getAppConfig() { 114 | return new Promise((resolve, reject) => { 115 | makeRequest({ 116 | url: `/config.json`, 117 | method: "get" 118 | }).then(resp => resolve(resp)); 119 | }); 120 | } 121 | -------------------------------------------------------------------------------- /src/utils/session.js: -------------------------------------------------------------------------------- 1 | import { UserSession } from "@esri/arcgis-rest-auth"; 2 | import { getPortal } from "@esri/arcgis-rest-portal"; 3 | import * as Cookies from "js-cookie"; 4 | 5 | /** 6 | * sign in using OAuth 7 | */ 8 | export function signIn(options) { 9 | const { 10 | portalUrl = "https://www.arcgis.com/", 11 | clientId, 12 | popup = false 13 | } = options; 14 | 15 | // only need to call the begin method, the rest is handled either in this method 16 | // or in the completeSignIn update 17 | UserSession.beginOAuth2({ 18 | clientId, 19 | portalUrl, 20 | popup, 21 | redirectUri: `${window.location.origin}/auth` 22 | }); // TODO can use .then to complete auth here and save an update cycle? 23 | } 24 | 25 | /** 26 | * sign in using OAuth pop up 27 | */ 28 | export async function completeSignIn(options) { 29 | const { 30 | portalUrl = "https://www.arcgis.com/", 31 | clientId, 32 | sessionId = `${portalUrl}_session` 33 | } = options; 34 | 35 | const session = UserSession.completeOAuth2({ clientId, portalUrl }); 36 | 37 | const token = session.token; 38 | 39 | saveSession(session, sessionId); 40 | 41 | const user = await session.getUser(); 42 | 43 | const portal = await getPortal(null, { 44 | portal: session.portal, 45 | authentication: session 46 | }); 47 | 48 | return { user, portal, token }; 49 | } 50 | 51 | /** 52 | * make sure the user is not logged in the next time they load the app 53 | */ 54 | export function signOut(sessionId) { 55 | deleteSession(sessionId); 56 | } 57 | 58 | /** 59 | * restore a previously saved session 60 | */ 61 | export async function restoreSession(sessionId) { 62 | let authInfos = null; 63 | 64 | const serializedSession = Cookies.get(sessionId); 65 | const session = 66 | serializedSession && UserSession.deserialize(serializedSession); 67 | 68 | if (session) { 69 | const user = await session.getUser(); 70 | 71 | const portal = await getPortal(null, { 72 | portal: session.portal, 73 | authentication: session 74 | }); 75 | 76 | const token = session.token; 77 | 78 | authInfos = { 79 | user, 80 | portal, 81 | token 82 | }; 83 | } 84 | 85 | return authInfos; 86 | } 87 | 88 | // save session & user for next time the user loads the app 89 | function saveSession(session, sessionId) { 90 | // get expiration from session 91 | const expires = session.tokenExpires; 92 | 93 | Cookies.set(sessionId, session.serialize(), { 94 | expires, 95 | sameSite: "strict" 96 | }); 97 | } 98 | 99 | // delete a previously saved session 100 | function deleteSession(sessionId) { 101 | Cookies.remove(sessionId); 102 | } 103 | --------------------------------------------------------------------------------