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 |
88 |
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 |
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 |
--------------------------------------------------------------------------------