├── .babelrc ├── .eslintrc.js ├── .gitignore ├── README.md ├── contributing.md ├── dojoConfig.js ├── img ├── screenshot1.jpg ├── screenshot2.jpg ├── screenshot3.jpg └── user.png ├── index.html ├── license.txt ├── package-lock.json ├── package.json ├── server.js ├── src ├── components │ ├── __tests__ │ │ └── webscene.test.jsx │ ├── app.jsx │ ├── header │ │ ├── __tests__ │ │ │ ├── header.test.jsx │ │ │ ├── identity-nav.test.jsx │ │ │ └── scenes-nav.test.jsx │ │ ├── header.jsx │ │ ├── identity-nav.jsx │ │ └── scenes-nav.jsx │ ├── webscene.jsx │ └── widgets │ │ ├── __tests__ │ │ ├── shadows-nav.test.jsx │ │ └── time-nav.test.jsx │ │ ├── shadows-nav.jsx │ │ └── time-nav.jsx ├── constants │ ├── action-types.js │ └── app-constants.js ├── main.jsx ├── middleware │ ├── .eslintrc.js │ ├── __tests__ │ │ ├── arcgis-authentication.test.js │ │ └── arcgis-sceneview.test.js │ ├── arcgis-authentication.js │ ├── arcgis-sceneview.js │ └── arcgis-sceneview │ │ ├── environment.js │ │ ├── highlights.js │ │ └── interaction.js ├── reducer │ ├── app.js │ ├── environment │ │ ├── __tests__ │ │ │ ├── actions.test.js │ │ │ ├── date.test.js │ │ │ ├── shadows.test.js │ │ │ └── utcoffset.test.js │ │ ├── actions.js │ │ ├── date.js │ │ ├── index.js │ │ ├── shadows.js │ │ └── utcoffset.js │ ├── selection │ │ ├── __tests__ │ │ │ ├── actions.test.js │ │ │ └── selection.test.js │ │ ├── actions.js │ │ ├── index.js │ │ └── item │ │ │ ├── index.js │ │ │ ├── layer.js │ │ │ └── oid.js │ ├── user │ │ ├── __tests__ │ │ │ ├── actions.test.js │ │ │ ├── email.test.js │ │ │ ├── fullname.test.js │ │ │ ├── thumbnailurl.test.js │ │ │ ├── username.test.js │ │ │ └── websceneitems.test.js │ │ ├── actions.js │ │ ├── email.js │ │ ├── fullname.js │ │ ├── index.js │ │ ├── thumbnailurl.js │ │ ├── username.js │ │ └── websceneitems.js │ └── webscene │ │ ├── __tests__ │ │ ├── actions.test.js │ │ ├── id.test.js │ │ └── name.test.js │ │ ├── actions.js │ │ ├── id.js │ │ ├── index.js │ │ └── name.js ├── store │ └── store.jsx └── styles │ ├── main.css │ └── range-slider.css └── webpack.config.babel.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-2"], 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "airbnb", 3 | "rules": { 4 | "react/forbid-prop-types": 0, 5 | "import/no-named-as-default": 0, 6 | "no-param-reassign": ["error", { "props": false }], 7 | }, 8 | "env": { 9 | "browser": true, 10 | "jest": true, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | .vscode/* 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ArcGIS JS API 4.x / React / Redux Boilerplate 2 | 3 | Web scene viewer boilerplate web application using React and Redux, including the ArcGIS JS API 4 | as middleware. The example application displays a web scene with a simple sun position slider. 5 | 6 | ![Cover image](/img/screenshot1.jpg?raw=true "Cover image") 7 | 8 | ## Features 9 | 10 | This boilerplate example integrates: 11 | 12 | * [ArcGIS JS API 4.x](https://developers.arcgis.com/javascript/) 13 | * [React](https://facebook.github.io/react/) 14 | * [Redux](http://redux.js.org/) 15 | * [React Redux](https://github.com/reactjs/react-redux) 16 | * [Redux Thunk](https://github.com/gaearon/redux-thunk) 17 | * [Calcite Web](http://esri.github.io/calcite-web/) 18 | 19 | It provides two useful Redux 20 | [middleware](https://medium.com/@jacobp100/you-arent-using-redux-middleware-enough-94ffe991e6) examples: 21 | 22 | * [arcgis-authentication](/src/middleware/arcgis-authentication.js) - handles Portal login. 23 | * [arcgis-sceneview](/src/middleware/arcgis-sceneview.js) - opens a SceneView with a WebScene, handle selection, and environment changes. 24 | 25 | It provides support for: 26 | 27 | * [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension) - You can browse 28 | the state and dispatch actions live while the application is running. 29 | * [Hot Module Replacement](https://webpack.js.org/concepts/hot-module-replacement/) - Updated 30 | **components** or **reducers** are automatically replaced while the application is running. 31 | The state is preserved during updates, and the SceneView does not need to be reloaded. 32 | * Tests with [Jest](http://facebook.github.io/jest/) and [Enzyme](http://airbnb.io/enzyme/index.html). 33 | 34 | ![Redux Dev Tools](/img/screenshot2.jpg?raw=true "Redux Dev Tools") 35 | 36 | ![Hot Module Replacement](/img/screenshot3.jpg?raw=true "Hot Module Replacement") 37 | 38 | ## Instructions 39 | 40 | ### Installing 41 | 42 | Download the repository and install the dependencies: 43 | 44 | ``` 45 | $ npm install 46 | ``` 47 | 48 | ### Registering your App 49 | 50 | For this code to work, you need to 51 | [add](http://doc.arcgis.com/en/marketplace/provider/add-item-to-agol.htm) and 52 | [register](http://doc.arcgis.com/en/marketplace/provider/register-app.htm) an app in ArcGIS Online, 53 | add the correct redirect URI (e.g. `http://localhost:8080`), and add the App ID to [/src/constants/app-constants.js](/src/constants/app-constants.js). 54 | 55 | * [How to add an app in ArcGIS Online](http://doc.arcgis.com/en/marketplace/provider/add-item-to-agol.htm) 56 | * [How to register an app in ArcGIS Online](http://doc.arcgis.com/en/marketplace/provider/register-app.htm) 57 | * Make sure to set the correct redirect URI (e.g. `http://localhost:8080`) 58 | 59 | Finally, update [/src/constants/app-constants.js](/src/constants/app-constants.js) to contain your App ID (and portal URL if not ArcGIS Online): 60 | 61 | ```javascript 62 | export const APP_ID = ''; 63 | export const APP_PORTAL_URL = 'https://www.arcgis.com'; 64 | ``` 65 | 66 | ### Running 67 | 68 | Run tests: 69 | 70 | ``` 71 | $ npm test 72 | ``` 73 | 74 | Build and run live server: 75 | 76 | ``` 77 | $ npm start 78 | ``` 79 | 80 | ## Requirements 81 | 82 | * Notepad or your favorite editor 83 | * [npm](https://www.npmjs.com/) 84 | * Web browser with access to the Internet 85 | 86 | ## Resources 87 | 88 | * [ArcGIS for JavaScript API Resource Center](https://developers.arcgis.com/javascript/) 89 | * [How to add an app in ArcGIS Online](http://doc.arcgis.com/en/marketplace/provider/add-item-to-agol.htm) 90 | * [How to register an app in ArcGIS Online](http://doc.arcgis.com/en/marketplace/provider/register-app.htm) 91 | * [React](https://facebook.github.io/react/) 92 | * [Redux](http://redux.js.org/) 93 | * [React Redux](https://github.com/reactjs/react-redux) 94 | * [Redux Thunk](https://github.com/gaearon/redux-thunk) 95 | * [Calcite Web](http://esri.github.io/calcite-web/) 96 | * [Why you aren't using Redux middleware enough](https://medium.com/@jacobp100/you-arent-using-redux-middleware-enough-94ffe991e6) 97 | * [Writing Tests - Redux](http://redux.js.org/docs/recipes/WritingTests.html) 98 | * [Jest Tests](http://facebook.github.io/jest/) 99 | * [Enzyme Tests](http://airbnb.io/enzyme/index.html) 100 | * [ArcGIS Blog](http://blogs.esri.com/esri/arcgis/) 101 | * [twitter@esri](http://twitter.com/esri) 102 | 103 | ## Issues 104 | 105 | Find a bug or want to request a new feature? Please let us know by submitting an issue. 106 | 107 | ## Contributing 108 | 109 | Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). 110 | 111 | ## Licensing 112 | Copyright 2017 Esri 113 | 114 | Licensed under the Apache License, Version 2.0 (the "License"); 115 | you may not use this file except in compliance with the License. 116 | You may obtain a copy of the License at 117 | 118 | http://www.apache.org/licenses/LICENSE-2.0 119 | 120 | Unless required by applicable law or agreed to in writing, software 121 | distributed under the License is distributed on an "AS IS" BASIS, 122 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 123 | See the License for the specific language governing permissions and 124 | limitations under the License. 125 | 126 | A copy of the license is available in the repository's [license.txt](/license.txt) file. 127 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). 2 | -------------------------------------------------------------------------------- /dojoConfig.js: -------------------------------------------------------------------------------- 1 | window.dojoConfig = { 2 | async: true, 3 | deps: ['app/bundle'], 4 | packages: [{ 5 | name: 'app', 6 | location: `${location.origin}${location.pathname}dist`, 7 | main: 'bundle', 8 | }], 9 | }; 10 | -------------------------------------------------------------------------------- /img/screenshot1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/react-redux-js4/fbcac10643c17754dcd392400a6a7d349863feca/img/screenshot1.jpg -------------------------------------------------------------------------------- /img/screenshot2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/react-redux-js4/fbcac10643c17754dcd392400a6a7d349863feca/img/screenshot2.jpg -------------------------------------------------------------------------------- /img/screenshot3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/react-redux-js4/fbcac10643c17754dcd392400a6a7d349863feca/img/screenshot3.jpg -------------------------------------------------------------------------------- /img/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/react-redux-js4/fbcac10643c17754dcd392400a6a7d349863feca/img/user.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ArcGIS React Redux 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /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 102 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-js4", 3 | "version": "1.0.0", 4 | "description": "Boilerplate ArcGIS JS API 4.x app using React and Redux", 5 | "main": "dojoConfig.js", 6 | "scripts": { 7 | "test": "jest", 8 | "start": "node server" 9 | }, 10 | "author": "Michael Van den Bergh", 11 | "license": "Apache-2.0", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/ArcGIS/react-redux-js4.git" 15 | }, 16 | "devDependencies": { 17 | "babel-loader": "^7.1.2", 18 | "babel-plugin-transform-es2015-modules-amd": "^6.3.13", 19 | "babel-preset-es2015": "^6.3.13", 20 | "babel-preset-react": "^6.24.1", 21 | "babel-preset-stage-0": "^6.5.0", 22 | "babel-preset-stage-2": "^6.24.1", 23 | "css-loader": "^0.28.9", 24 | "enzyme": "^2.9.1", 25 | "eslint": "^3.19.0", 26 | "eslint-config-airbnb": "^15.1.0", 27 | "eslint-loader": "^1.9.0", 28 | "eslint-plugin-import": "^2.8.0", 29 | "eslint-plugin-jsx-a11y": "^5.1.1", 30 | "eslint-plugin-react": "^7.5.1", 31 | "jest": "^20.0.4", 32 | "react-hot-loader": "^4.0.0-beta.15", 33 | "react-test-renderer": "^15.6.2", 34 | "style-loader": "^0.18.2", 35 | "webpack": "^3.10.0", 36 | "webpack-dev-middleware": "^1.12.2", 37 | "webpack-dev-server": "^2.11.0" 38 | }, 39 | "dependencies": { 40 | "prop-types": "^15.6.0", 41 | "react": "^15.6.2", 42 | "react-dom": "^15.6.2", 43 | "react-redux": "^5.0.6", 44 | "redux": "^3.7.2", 45 | "redux-thunk": "^2.2.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const WebpackDevServer = require('webpack-dev-server'); 3 | const config = require('./webpack.config.babel'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | publicPath: config.output.publicPath, 7 | hot: true, 8 | historyApiFallback: true, 9 | // It suppress error shown in console, so it has to be set to false. 10 | quiet: false, 11 | // It suppress everything except error, so it has to be set to false as well 12 | // to see success build. 13 | noInfo: false, 14 | stats: { 15 | // Config for minimal console.log mess. 16 | assets: false, 17 | colors: true, 18 | version: false, 19 | hash: false, 20 | timings: false, 21 | chunks: false, 22 | chunkModules: false, 23 | }, 24 | }).listen(8080, 'localhost', (err) => { 25 | if (err) { 26 | console.log(err); 27 | } 28 | 29 | console.log('Listening at localhost:8080'); 30 | }); 31 | -------------------------------------------------------------------------------- /src/components/__tests__/webscene.test.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import React from 'react'; 18 | import { mount } from 'enzyme'; 19 | import { WebSceneView } from '../webscene'; 20 | 21 | function setup() { 22 | const props = { 23 | init: jest.fn(), 24 | websceneId: 'a1b2c3', 25 | }; 26 | const wrapper = mount(); 27 | 28 | return { props, wrapper }; 29 | } 30 | 31 | describe('components', () => { 32 | describe('', () => { 33 | it('should render self and call init()', () => { 34 | const { props, wrapper } = setup(); 35 | expect(wrapper.find('div').hasClass('websceneview')).toBe(true); 36 | expect(props.init).toHaveBeenCalled(); 37 | expect(props.init).toHaveBeenCalledWith(wrapper.find('div').node, 'a1b2c3'); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/components/app.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import React from 'react'; 18 | import PropTypes from 'prop-types'; 19 | import { connect } from 'react-redux'; 20 | 21 | import Header from './header/header'; 22 | import WebSceneView from './webscene'; 23 | import TimeNav from './widgets/time-nav'; 24 | import ShadowsNav from './widgets/shadows-nav'; 25 | 26 | 27 | const App = ({ websceneId, name }) => ( 28 |
29 |
30 |
31 | 32 | {name &&
33 | 34 | 35 |
} 36 |
37 |
38 | ); 39 | 40 | App.propTypes = { 41 | name: PropTypes.string, 42 | websceneId: PropTypes.string, 43 | }; 44 | 45 | App.defaultProps = { 46 | name: null, 47 | websceneId: null, 48 | }; 49 | 50 | const mapStateToProps = ({ webscene: { name } }) => ({ 51 | name, 52 | }); 53 | 54 | export default connect(mapStateToProps)(App); 55 | -------------------------------------------------------------------------------- /src/components/header/__tests__/header.test.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import React from 'react'; 18 | import { shallow } from 'enzyme'; 19 | import Header from '../header'; 20 | 21 | function setup() { 22 | const props = {}; 23 | const wrapper = shallow(
); 24 | 25 | return { props, wrapper }; 26 | } 27 | 28 | describe('components', () => { 29 | describe('
', () => { 30 | it('should render self', () => { 31 | const { wrapper } = setup(); 32 | 33 | expect(wrapper.find('header').hasClass('top-nav')).toBe(true); 34 | expect(wrapper.find('a').hasClass('top-nav-title')).toBe(true); 35 | expect(wrapper.find('a').text()).toBe('ArcGIS React Redux'); 36 | expect(wrapper.find('Connect(ScenesNav)').length).toBe(1); 37 | expect(wrapper.find('Connect(IdentityNav)').length).toBe(1); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/components/header/__tests__/identity-nav.test.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import React from 'react'; 18 | import { mount } from 'enzyme'; 19 | import { IdentityNav } from '../identity-nav'; 20 | 21 | function setupSignedIn() { 22 | const props = { 23 | username: 'user123', 24 | fullname: 'John Doe', 25 | thumbnailurl: 'http://bla.jpg', 26 | signIn: jest.fn(), 27 | signOut: jest.fn(), 28 | }; 29 | const wrapper = mount(); 30 | 31 | return { props, wrapper }; 32 | } 33 | 34 | function setupNotSignedIn() { 35 | const props = { 36 | username: null, 37 | fullname: null, 38 | thumbnailurl: null, 39 | signIn: jest.fn(), 40 | signOut: jest.fn(), 41 | }; 42 | const wrapper = mount(); 43 | 44 | return { props, wrapper }; 45 | } 46 | 47 | describe('components', () => { 48 | describe(' (signed in)', () => { 49 | it('should render self and show identity dropdown', () => { 50 | const { wrapper } = setupSignedIn(); 51 | expect(wrapper.find('.dropdown').hasClass('hidden')).toBe(false); 52 | expect(wrapper.find('#sign-in').hasClass('hidden')).toBe(true); 53 | expect(wrapper.find('.dropdown-title').text()).toBe('user123'); 54 | expect(wrapper.find('img').prop('src')).toBe('http://bla.jpg'); 55 | expect(wrapper.find('.shortname').text()).toBe('John'); 56 | expect(wrapper.find('#sign-out').text()).toBe('Sign Out'); 57 | }); 58 | 59 | it('should call signOut when Sign Out button is clicked', () => { 60 | const { props, wrapper } = setupSignedIn(); 61 | wrapper.find('#sign-out').simulate('click'); 62 | expect(props.signOut).toHaveBeenCalled(); 63 | }); 64 | }); 65 | 66 | describe(' (not signed in)', () => { 67 | it('should render self and hide identity dropdown', () => { 68 | const { wrapper } = setupNotSignedIn(); 69 | expect(wrapper.find('.dropdown').hasClass('hidden')).toBe(true); 70 | expect(wrapper.find('#sign-in').hasClass('hidden')).toBe(false); 71 | }); 72 | 73 | it('should call signIn when Sign In button is clicked', () => { 74 | const { props, wrapper } = setupNotSignedIn(); 75 | wrapper.find('#sign-in').simulate('click'); 76 | expect(props.signIn).toHaveBeenCalled(); 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /src/components/header/__tests__/scenes-nav.test.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import React from 'react'; 18 | import { mount } from 'enzyme'; 19 | import { ScenesNav } from '../scenes-nav'; 20 | 21 | function setup() { 22 | const props = { 23 | websceneItems: [{ 24 | id: '12345', 25 | title: 'Webscene 1', 26 | }, 27 | { 28 | id: '67890', 29 | title: 'Webscene 2', 30 | }], 31 | loadWebScene: jest.fn(), 32 | }; 33 | const wrapper = mount(); 34 | 35 | return { props, wrapper }; 36 | } 37 | 38 | describe('components', () => { 39 | describe('', () => { 40 | it('should render self with two scenes in the dropdown', () => { 41 | const { wrapper } = setup(); 42 | expect(wrapper.find('.dropdown-link').length).toBe(2); 43 | expect(wrapper.find('.dropdown-link').at(0).text()).toBe('Webscene 1'); 44 | expect(wrapper.find('.dropdown-link').at(0).props().href).toBe('/?id=12345'); 45 | expect(wrapper.find('.dropdown-link').at(1).text()).toBe('Webscene 2'); 46 | expect(wrapper.find('.dropdown-link').at(1).props().href).toBe('/?id=67890'); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /src/components/header/header.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | import IdentityNav from './identity-nav'; 20 | import ScenesNav from './scenes-nav'; 21 | 22 | const Header = () => ( 23 |
24 | ArcGIS React Redux 25 | 28 | 31 |
32 | ); 33 | 34 | export default Header; 35 | -------------------------------------------------------------------------------- /src/components/header/identity-nav.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import React, { Component } from 'react'; 18 | import PropTypes from 'prop-types'; 19 | import { connect } from 'react-redux'; 20 | 21 | import { DEFAULT_THUMBNAIL_URL } from '../../constants/app-constants'; 22 | import * as actions from '../../reducer/user/actions'; 23 | 24 | export class IdentityNav extends Component { 25 | constructor(props) { 26 | super(props); 27 | this.state = { 28 | open: false, 29 | }; 30 | } 31 | 32 | toggleMenu() { 33 | this.setState({ open: !this.state.open }); 34 | } 35 | 36 | collapseMenu() { 37 | this.setState({ open: false }); 38 | } 39 | 40 | render() { 41 | return ( 42 |
43 |
this.collapseMenu()} 46 | > 47 | 52 | 63 |
64 | 68 |
69 | ); 70 | } 71 | } 72 | 73 | IdentityNav.propTypes = { 74 | username: PropTypes.string, 75 | fullname: PropTypes.string, 76 | thumbnailurl: PropTypes.string, 77 | signIn: PropTypes.func.isRequired, 78 | signOut: PropTypes.func.isRequired, 79 | }; 80 | 81 | IdentityNav.defaultProps = { 82 | username: '', 83 | fullname: '', 84 | thumbnailurl: '', 85 | }; 86 | 87 | const mapStateToProps = ({ user: { username, fullname, thumbnailurl } }) => ({ 88 | username, 89 | fullname, 90 | thumbnailurl, 91 | }); 92 | 93 | const mapDispatchToProps = dispatch => ({ 94 | signIn() { 95 | return () => { 96 | dispatch(actions.signIn()); 97 | }; 98 | }, 99 | signOut() { 100 | return () => { 101 | dispatch(actions.signOut()); 102 | }; 103 | }, 104 | }); 105 | 106 | export default connect(mapStateToProps, mapDispatchToProps)(IdentityNav); 107 | -------------------------------------------------------------------------------- /src/components/header/scenes-nav.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import React, { Component } from 'react'; 18 | import PropTypes from 'prop-types'; 19 | import { connect } from 'react-redux'; 20 | 21 | import { DEFAULT_SCENE_ID } from '../../constants/app-constants'; 22 | 23 | export class ScenesNav extends Component { 24 | constructor(props) { 25 | super(props); 26 | this.state = { 27 | open: false, 28 | mouse: false, 29 | }; 30 | } 31 | 32 | mouseEnter() { 33 | this.setState({ 34 | mouse: true, 35 | }); 36 | } 37 | 38 | mouseLeave() { 39 | this.setState({ 40 | mouse: false, 41 | }); 42 | } 43 | 44 | toggleMenu() { 45 | this.setState({ 46 | open: !this.state.open, 47 | }); 48 | } 49 | 50 | collapseMenu() { 51 | if (this.state.mouse) return; 52 | this.setState({ open: false }); 53 | } 54 | 55 | 56 | render() { 57 | return ( 58 |
this.collapseMenu()} 61 | onMouseEnter={() => this.mouseEnter()} 62 | onMouseLeave={() => this.mouseLeave()} 63 | > 64 | 68 | 86 |
87 | ); 88 | } 89 | } 90 | 91 | ScenesNav.propTypes = { 92 | username: PropTypes.string, 93 | websceneItems: PropTypes.array, 94 | }; 95 | 96 | ScenesNav.defaultProps = { 97 | username: null, 98 | websceneItems: [], 99 | }; 100 | 101 | const mapStateToProps = ({ user: { username, websceneItems } }) => ({ 102 | username, 103 | websceneItems, 104 | }); 105 | 106 | export default connect(mapStateToProps)(ScenesNav); 107 | -------------------------------------------------------------------------------- /src/components/webscene.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import React from 'react'; 18 | import PropTypes from 'prop-types'; 19 | import { connect } from 'react-redux'; 20 | import { bindActionCreators } from 'redux'; 21 | 22 | import { initScene } from '../reducer/webscene/actions'; 23 | 24 | export class WebSceneView extends React.Component { 25 | componentDidMount() { 26 | if (this.props.websceneId) { 27 | this.props.init(this.sceneView, this.props.websceneId); 28 | } 29 | } 30 | 31 | render() { 32 | return ( 33 |
{ this.sceneView = ref; }} /> 34 | ); 35 | } 36 | } 37 | 38 | WebSceneView.propTypes = { 39 | init: PropTypes.func.isRequired, 40 | websceneId: PropTypes.string, 41 | }; 42 | 43 | WebSceneView.defaultProps = { 44 | websceneId: null, 45 | }; 46 | 47 | function mapDispatchToProps(dispatch) { 48 | return { 49 | init: bindActionCreators(initScene, dispatch), 50 | }; 51 | } 52 | 53 | export default connect(null, mapDispatchToProps)(WebSceneView); 54 | -------------------------------------------------------------------------------- /src/components/widgets/__tests__/shadows-nav.test.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import React from 'react'; 18 | import { mount } from 'enzyme'; 19 | import { ShadowsNav } from '../shadows-nav'; 20 | 21 | function setup(value) { 22 | const props = { 23 | shadows: value, 24 | setShadows: jest.fn(), 25 | }; 26 | const wrapper = mount(); 27 | 28 | return { props, wrapper }; 29 | } 30 | 31 | describe('components', () => { 32 | describe('', () => { 33 | it('should render self with checkbox in the checked state', () => { 34 | const { wrapper } = setup(true); 35 | expect(wrapper.find('.shadow-nav').exists()).toBe(true); 36 | expect(wrapper.find('input').props().id).toBe('shadows'); 37 | expect(wrapper.find('input').props().type).toBe('checkbox'); 38 | expect(wrapper.find('input').props().checked).toBe(true); 39 | }); 40 | 41 | 42 | it('should render self with checkbox in the unchecked state', () => { 43 | const { wrapper } = setup(false); 44 | expect(wrapper.find('.shadow-nav').exists()).toBe(true); 45 | expect(wrapper.find('input').props().id).toBe('shadows'); 46 | expect(wrapper.find('input').props().checked).toBe(false); 47 | }); 48 | 49 | it('should call setShadows when checkbox is deselected', () => { 50 | const { props, wrapper } = setup(true); 51 | wrapper.find('input').simulate('change'); 52 | expect(props.setShadows).toHaveBeenCalledWith(true); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /src/components/widgets/__tests__/time-nav.test.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import React from 'react'; 18 | import { mount } from 'enzyme'; 19 | import { TimeNav } from '../time-nav'; 20 | 21 | function setup(date) { 22 | const props = { 23 | date, 24 | setDate: jest.fn(), 25 | }; 26 | const wrapper = mount(); 27 | 28 | return { props, wrapper }; 29 | } 30 | 31 | describe('components', () => { 32 | describe('', () => { 33 | it('should render self with range slider in the correct position', () => { 34 | const { wrapper } = setup(new Date(Date.UTC(2017, 3, 15, 12, 30))); 35 | expect(wrapper.find('.time-nav').exists()).toBe(true); 36 | expect(wrapper.find('input').props().id).toBe('time'); 37 | expect(wrapper.find('input').props().type).toBe('range'); 38 | expect(wrapper.find('input').props().min).toBe('0'); 39 | expect(wrapper.find('input').props().max).toBe('23.99'); 40 | expect(wrapper.find('input').props().step).toBe('0.02'); 41 | expect(wrapper.find('input').props().value).toBe(12.5); 42 | }); 43 | 44 | 45 | it('should call setDate when range slider is changed', () => { 46 | const { props, wrapper } = setup(new Date(Date.UTC(2017, 3, 15, 12, 30))); 47 | wrapper.find('input').simulate('change'); 48 | expect(props.setDate).toHaveBeenCalledWith(new Date(Date.UTC(2017, 3, 15, 12, 30))); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /src/components/widgets/shadows-nav.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import React, { Component } from 'react'; 18 | import PropTypes from 'prop-types'; 19 | import { connect } from 'react-redux'; 20 | import { bindActionCreators } from 'redux'; 21 | 22 | import * as actions from '../../reducer/environment/actions'; 23 | 24 | export class ShadowsNav extends Component { 25 | constructor(props) { 26 | super(props); 27 | this.handleChange = this.handleChange.bind(this); 28 | } 29 | 30 | handleChange(event) { 31 | this.props.setShadows(event.target.checked); 32 | } 33 | 34 | render() { 35 | return ( 36 |
37 |
38 |
39 | 49 |
50 |
51 |
52 | ); 53 | } 54 | } 55 | 56 | ShadowsNav.propTypes = { 57 | shadows: PropTypes.bool, 58 | setShadows: PropTypes.func.isRequired, 59 | }; 60 | 61 | ShadowsNav.defaultProps = { 62 | shadows: false, 63 | }; 64 | 65 | const mapStateToProps = ({ environment: { shadows } }) => ({ 66 | shadows, 67 | }); 68 | 69 | function mapDispatchToProps(dispatch) { 70 | return { 71 | setShadows: bindActionCreators(actions.setShadows, dispatch), 72 | }; 73 | } 74 | 75 | export default connect(mapStateToProps, mapDispatchToProps)(ShadowsNav); 76 | -------------------------------------------------------------------------------- /src/components/widgets/time-nav.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import React, { Component } from 'react'; 18 | import PropTypes from 'prop-types'; 19 | import { connect } from 'react-redux'; 20 | import { bindActionCreators } from 'redux'; 21 | 22 | import * as actions from '../../reducer/environment/actions'; 23 | 24 | const pad2 = value => (value.toString().length === 1 ? `0${value.toString()}` : value.toString()); 25 | 26 | export class TimeNav extends Component { 27 | constructor(props) { 28 | super(props); 29 | this.handleChange = this.handleChange.bind(this); 30 | } 31 | 32 | getTimeString() { 33 | if (!this.props.date) return ''; 34 | 35 | const hours = this.props.date.getUTCHours(); 36 | const minutes = this.props.date.getUTCMinutes(); 37 | return `${pad2(hours)}:${pad2(minutes)}`; 38 | } 39 | 40 | getSliderValue() { 41 | if (!this.props.date) return 0; 42 | 43 | const value = this.props.date.getUTCHours() + (this.props.date.getUTCMinutes() / 60); 44 | return value; 45 | } 46 | 47 | handleChange(event) { 48 | const date = new Date(this.props.date); 49 | 50 | const hours = Math.floor(event.target.value); 51 | const minutes = Math.floor(60 * (event.target.value % 1)); 52 | 53 | date.setUTCHours(hours); 54 | date.setUTCMinutes(minutes); 55 | 56 | this.props.setDate(date); 57 | } 58 | 59 | render() { 60 | return ( 61 |
62 |
63 | 64 | 74 |
75 |
76 | ); 77 | } 78 | } 79 | 80 | TimeNav.propTypes = { 81 | date: PropTypes.instanceOf(Date), 82 | setDate: PropTypes.func.isRequired, 83 | }; 84 | 85 | TimeNav.defaultProps = { 86 | date: new Date(Date.UTC(2017, 3, 15, 12, 0)), 87 | }; 88 | 89 | const mapStateToProps = ({ environment: { date } }) => ({ 90 | date, 91 | }); 92 | 93 | function mapDispatchToProps(dispatch) { 94 | return { 95 | setDate: bindActionCreators(actions.setDate, dispatch), 96 | }; 97 | } 98 | 99 | export default connect(mapStateToProps, mapDispatchToProps)(TimeNav); 100 | -------------------------------------------------------------------------------- /src/constants/action-types.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | /** 18 | * Authentication 19 | */ 20 | export const GET_IDENTITY = 'GET_IDENTITY'; 21 | export const SET_IDENTITY = 'SET_IDENTITY'; 22 | export const SIGN_IN = 'SIGN_IN'; 23 | export const SIGN_OUT = 'SIGN_OUT'; 24 | export const GET_USER_WEBSCENES = 'GET_USER_WEBSCENES'; 25 | export const SET_USER_WEBSCENES = 'SET_USER_WEBSCENES'; 26 | 27 | /** 28 | * Web Scene 29 | */ 30 | export const INIT_SCENE = 'INIT_SCENE'; 31 | 32 | /** 33 | * Selection 34 | */ 35 | export const SELECTION_SET = 'SELECTION_SET'; 36 | export const SELECTION_ADD = 'SELECTION_ADD'; 37 | export const SELECTION_REMOVE = 'SELECTION_REMOVE'; 38 | export const SELECTION_RESET = 'SELECTION_RESET'; 39 | 40 | /** 41 | * Environment 42 | */ 43 | export const SET_ENVIRONMENT = 'SET_ENVIRONMENT'; 44 | export const SET_DATE = 'SET_DATE'; 45 | export const SET_SHADOWS = 'SET_SHADOWS'; 46 | -------------------------------------------------------------------------------- /src/constants/app-constants.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | export const APP_ID = ''; 18 | export const APP_PORTAL_URL = 'https://www.arcgis.com'; 19 | 20 | export const DEFAULT_SCENE_ID = '63a16e0c9f364d0fab9d55f40bf71771'; 21 | export const DEFAULT_THUMBNAIL_URL = '/img/user.png'; 22 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import React from 'react'; 18 | import { render } from 'react-dom'; 19 | import { Provider } from 'react-redux'; 20 | 21 | import './styles/main.css'; 22 | import './styles/range-slider.css'; 23 | 24 | import App from './components/app'; 25 | import store from './store/store'; 26 | 27 | 28 | const getURLParameter = name => 29 | decodeURIComponent((new RegExp(`[?|&]${name}=([^&;]+?)(&|#|;|$)`).exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null; 30 | 31 | 32 | const node = document.getElementById('app-container'); 33 | 34 | render( 35 | 36 | 37 | , 38 | node, 39 | ); 40 | 41 | if (module.hot) { 42 | module.hot.accept(); 43 | } 44 | -------------------------------------------------------------------------------- /src/middleware/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ESLint will not find these Esri modules because they are loaded as externals. 3 | * Specifying them as 'core-modules' will prevent ESLint from stumbling over them. 4 | */ 5 | module.exports = { 6 | "rules": { 7 | "import/no-extraneous-dependencies": 0, 8 | "import/extensions": 0, 9 | }, 10 | "settings": { 11 | "import/core-modules": [ 12 | "esri/config", 13 | "esri/identity/OAuthInfo", 14 | "esri/identity/IdentityManager", 15 | "esri/portal/Portal", 16 | "esri/views/SceneView", 17 | "esri/WebScene", 18 | ], 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/middleware/__tests__/arcgis-authentication.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import IdentityManager from 'esri/identity/IdentityManager'; 18 | import OAuthInfo from 'esri/identity/OAuthInfo'; 19 | import Portal from 'esri/portal/Portal'; 20 | 21 | import authentication from '../arcgis-authentication'; 22 | 23 | import * as types from '../../constants/action-types'; 24 | import { APP_ID, APP_PORTAL_URL } from '../../constants/app-constants'; 25 | 26 | 27 | /** 28 | * Mocks 29 | */ 30 | jest.mock('esri/config', () => ({ request: { corsEnabledServers: [] } }), { virtual: true }); 31 | 32 | jest.mock('esri/identity/OAuthInfo', () => { 33 | const OAuthInfoMock = jest.fn(); 34 | OAuthInfoMock.prototype.portalUrl = 'http://bla'; 35 | return OAuthInfoMock; 36 | }, { virtual: true }); 37 | 38 | jest.mock('esri/identity/IdentityManager', () => { 39 | const IdentityManagerMock = jest.fn(); 40 | IdentityManagerMock.registerOAuthInfos = jest.fn(); 41 | IdentityManagerMock.checkSignInStatus = jest.fn(() => Promise.resolve()); 42 | IdentityManagerMock.getCredential = jest.fn(); 43 | IdentityManagerMock.destroyCredentials = jest.fn(); 44 | return IdentityManagerMock; 45 | }, { virtual: true }); 46 | 47 | jest.mock('esri/portal/Portal', () => { 48 | const PortalMock = jest.fn(); 49 | PortalMock.prototype.load = jest.fn(() => Promise.resolve()); 50 | PortalMock.prototype.user = { 51 | username: 'user123', 52 | fullName: 'John', 53 | email: 'john@gmail.com', 54 | thumbnailUrl: 'http://blabla.jpg', 55 | }; 56 | PortalMock.prototype.queryItems = jest.fn(() => Promise.resolve({ results: [{ id: 0, name: 'web scene' }] })); 57 | return PortalMock; 58 | }, { virtual: true }); 59 | 60 | 61 | /** 62 | * Middleware stuff 63 | */ 64 | const create = () => { 65 | const store = { 66 | getState: jest.fn(() => ({})), 67 | dispatch: jest.fn(), 68 | }; 69 | const next = jest.fn(); 70 | const invoke = action => authentication(store)(next)(action); 71 | return { store, next, invoke }; 72 | }; 73 | 74 | 75 | /** 76 | * Tests 77 | */ 78 | describe('async actions', () => { 79 | it('passes through non-function action', () => { 80 | const { next, invoke } = create(); 81 | const action = { type: 'TEST' }; 82 | invoke(action); 83 | expect(next).toHaveBeenCalledWith(action); 84 | }); 85 | 86 | it('is initialized with OAuthInfo and Portal', () => { 87 | expect(OAuthInfo).toHaveBeenCalledWith({ 88 | appId: APP_ID, 89 | popup: false, 90 | portalUrl: APP_PORTAL_URL, 91 | }); 92 | expect(Portal).toHaveBeenCalled(); 93 | }); 94 | 95 | it('loads portal and dispatches SET_IDENTITY on GET_IDENTITY', () => { 96 | const { next, invoke, store } = create(); 97 | const action = { type: types.GET_IDENTITY }; 98 | expect.assertions(5); 99 | invoke(action) 100 | .then(() => { 101 | expect(Portal.mock.instances[0].load).toHaveBeenCalled(); 102 | expect(store.dispatch).toHaveBeenCalledWith({ 103 | type: types.SET_IDENTITY, 104 | username: 'user123', 105 | fullname: 'John', 106 | email: 'john@gmail.com', 107 | thumbnailurl: 'http://blabla.jpg', 108 | }); 109 | expect(store.dispatch).toHaveBeenCalledWith({ 110 | type: types.GET_USER_WEBSCENES, 111 | }); 112 | }); 113 | expect(next).toHaveBeenCalledWith(action); 114 | expect(IdentityManager.checkSignInStatus).toHaveBeenCalledWith('http://bla/sharing'); 115 | }); 116 | 117 | it('calls getCredential on SIGN_IN', () => { 118 | const { next, invoke } = create(); 119 | const action = { type: types.SIGN_IN }; 120 | invoke(action); 121 | expect(IdentityManager.getCredential).toHaveBeenCalledWith('http://bla/sharing'); 122 | expect(next).toHaveBeenCalledWith(action); 123 | }); 124 | 125 | it('calls destroyCredentials on SIGN_OUT', () => { 126 | const { next, invoke } = create(); 127 | const action = { type: types.SIGN_OUT }; 128 | invoke(action); 129 | expect(IdentityManager.destroyCredentials).toHaveBeenCalled(); 130 | expect(next).toHaveBeenCalledWith(action); 131 | }); 132 | 133 | it('loads user web scenes and dispatches SET_USER_WEBSCENES', () => { 134 | const { next, invoke, store } = create(); 135 | const action = { type: types.GET_USER_WEBSCENES }; 136 | expect.assertions(2); 137 | invoke(action) 138 | .then(() => { 139 | expect(store.dispatch).toHaveBeenCalledWith({ 140 | type: types.SET_USER_WEBSCENES, 141 | websceneItems: [{ id: 0, name: 'web scene' }], 142 | }); 143 | }); 144 | expect(next).toHaveBeenCalledWith(action); 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /src/middleware/__tests__/arcgis-sceneview.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import SceneView from 'esri/views/SceneView'; 18 | import WebScene from 'esri/WebScene'; 19 | 20 | import sceneviewMiddelware from '../arcgis-sceneview'; 21 | 22 | import { 23 | INIT_SCENE, 24 | SELECTION_SET, 25 | SELECTION_TOGGLE, 26 | SELECTION_RESET, 27 | SET_ENVIRONMENT, 28 | } from '../../constants/action-types'; 29 | 30 | import { registerClickEvent } from '../arcgis-sceneview/interaction'; 31 | import { updateHighlights } from '../arcgis-sceneview/highlights'; 32 | import { setEnvironment } from '../arcgis-sceneview/environment'; 33 | 34 | 35 | /** 36 | * Mocks 37 | */ 38 | jest.mock('esri/config', () => ({ request: { corsEnabledServers: [] } }), { virtual: true }); 39 | 40 | 41 | jest.mock('esri/views/SceneView', () => { 42 | const MockSceneView = jest.fn(); 43 | MockSceneView.prototype.watch = jest.fn(); 44 | MockSceneView.prototype.on = jest.fn(); 45 | MockSceneView.highlight = jest.fn(); 46 | MockSceneView.prototype.whenLayerView = jest.fn(() => Promise.resolve()); 47 | return MockSceneView; 48 | }, { virtual: true }); 49 | 50 | 51 | jest.mock('esri/WebScene', () => { 52 | const MockWebScene = jest.fn(); 53 | MockWebScene.prototype.then = callback => callback(); 54 | MockWebScene.prototype.layers = { 55 | items: [{ 56 | popupEnabled: true, 57 | }], 58 | getItemAt: jest.fn(), 59 | }; 60 | MockWebScene.prototype.portalItem = { 61 | title: 'WebScene title', 62 | }; 63 | MockWebScene.prototype.initialViewProperties = { 64 | environment: { 65 | lighting: { 66 | displayUTCOffset: -1, 67 | date: new Date(Date.UTC(2017, 1, 1, 12)), 68 | directShadowsEnabled: true, 69 | }, 70 | }, 71 | }; 72 | return MockWebScene; 73 | }, { virtual: true }); 74 | 75 | 76 | jest.mock('../arcgis-sceneview/interaction', () => ({ 77 | registerClickEvent: jest.fn(), 78 | }), { virtual: true }); 79 | 80 | 81 | jest.mock('../arcgis-sceneview/highlights', () => ({ 82 | updateHighlights: jest.fn(), 83 | }), { virtual: true }); 84 | 85 | jest.mock('../arcgis-sceneview/environment', () => ({ 86 | setEnvironment: jest.fn(), 87 | }), { virtual: true }); 88 | 89 | 90 | /** 91 | * Middleware stuff 92 | */ 93 | const create = () => { 94 | const store = { 95 | getState: jest.fn(() => ({ 96 | selection: [{ 97 | layer: 'foo', 98 | OID: 3, 99 | }], 100 | environment: { 101 | date: new Date(Date.UTC(2017, 1, 1, 11)), 102 | utcoffset: -3, 103 | shadows: true, 104 | }, 105 | })), 106 | dispatch: jest.fn(), 107 | }; 108 | const next = jest.fn(); 109 | const invoke = action => sceneviewMiddelware(store)(next)(action); 110 | return { store, next, invoke }; 111 | }; 112 | 113 | 114 | /** 115 | * Tests 116 | */ 117 | describe('async actions', () => { 118 | it('passes through non-function action', () => { 119 | const { next, invoke } = create(); 120 | const action = { type: 'TEST' }; 121 | invoke(action); 122 | expect(next).toHaveBeenCalledWith(action); 123 | }); 124 | }); 125 | 126 | 127 | describe('Arcgis SceneView middleware - scene loading', () => { 128 | it('initializes a new Scene View with Web Scene on INIT_SCENE', () => { 129 | const { next, invoke, store } = create(); 130 | const container = { 131 | appendChild: jest.fn(), 132 | }; 133 | const newDiv = document.createElement('DIV'); 134 | const action = { 135 | type: INIT_SCENE, 136 | container, 137 | id: 'abc1234', 138 | }; 139 | expect.hasAssertions(); 140 | invoke(action) 141 | .then(() => { 142 | expect(next).toHaveBeenCalledWith({ 143 | type: INIT_SCENE, 144 | container, 145 | id: 'abc1234', 146 | name: 'WebScene title', 147 | }); 148 | expect(store.dispatch).toHaveBeenCalledWith({ 149 | type: SET_ENVIRONMENT, 150 | UTCOffset: -1, 151 | date: new Date(Date.UTC(2017, 1, 1, 11)), 152 | shadows: true, 153 | }); 154 | expect(updateHighlights).toHaveBeenCalledWith({ 155 | map: {}, 156 | }, [{ 157 | layer: 'foo', 158 | OID: 3, 159 | }]); 160 | }); 161 | expect(SceneView).toHaveBeenCalledWith({ container: newDiv }); 162 | expect(container.appendChild).toHaveBeenCalledWith(newDiv); 163 | expect(registerClickEvent).toHaveBeenCalled(); 164 | expect(WebScene).toHaveBeenCalledWith({ portalItem: { id: 'abc1234' } }); 165 | }); 166 | }); 167 | 168 | describe('Arcgis SceneView middleware - selection', () => { 169 | it('updates highlights on SELECTION_SET', () => { 170 | const { next, invoke } = create(); 171 | const action = { 172 | type: SELECTION_SET, 173 | layer: 'foo', 174 | OID: 3, 175 | }; 176 | invoke(action); 177 | expect(next).toHaveBeenCalledWith(action); 178 | expect(updateHighlights).toHaveBeenCalledWith({ 179 | map: {}, 180 | }, [{ 181 | layer: 'foo', 182 | OID: 3, 183 | }]); 184 | }); 185 | 186 | 187 | it('updates highlights on SELECTION_RESET', () => { 188 | const { next, invoke } = create(); 189 | const action = { 190 | type: SELECTION_RESET, 191 | }; 192 | invoke(action); 193 | expect(next).toHaveBeenCalledWith(action); 194 | expect(updateHighlights).toHaveBeenCalledWith({ 195 | map: {}, 196 | }, [{ 197 | layer: 'foo', 198 | OID: 3, 199 | }]); 200 | }); 201 | 202 | 203 | it('updates highlights on SELECTION_TOGGLE', () => { 204 | const { next, invoke } = create(); 205 | const action = { 206 | type: SELECTION_TOGGLE, 207 | layer: 'foo', 208 | OID: 3, 209 | }; 210 | invoke(action); 211 | expect(next).toHaveBeenCalledWith(action); 212 | expect(updateHighlights).toHaveBeenCalledWith({ 213 | map: {}, 214 | }, [{ 215 | layer: 'foo', 216 | OID: 3, 217 | }]); 218 | }); 219 | }); 220 | 221 | describe('Arcgis SceneView middleware - environment', () => { 222 | it('updates environment on SET_ENVIRONMENT', () => { 223 | const { next, invoke } = create(); 224 | const action = { 225 | type: SET_ENVIRONMENT, 226 | date: new Date(Date.UTC(2017, 1, 1, 11)), 227 | UTCOffset: -3, 228 | shadows: true, 229 | }; 230 | invoke(action); 231 | expect(next).toHaveBeenCalledWith(action); 232 | expect(setEnvironment).toHaveBeenCalledWith( 233 | { map: {} }, 234 | new Date(Date.UTC(2017, 1, 1, 14)), 235 | -3, 236 | true, 237 | ); 238 | }); 239 | }); 240 | -------------------------------------------------------------------------------- /src/middleware/arcgis-authentication.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | /*eslint-disable */ 17 | import esriConfig from 'esri/config'; 18 | import OAuthInfo from 'esri/identity/OAuthInfo'; 19 | import IdentityManager from 'esri/identity/IdentityManager'; 20 | import Portal from 'esri/portal/Portal'; 21 | /* eslint-enable */ 22 | 23 | import { APP_ID, APP_PORTAL_URL } from '../constants/app-constants'; 24 | 25 | import { 26 | GET_IDENTITY, 27 | SET_IDENTITY, 28 | SIGN_IN, 29 | SIGN_OUT, 30 | GET_USER_WEBSCENES, 31 | SET_USER_WEBSCENES, 32 | } from '../constants/action-types'; 33 | 34 | 35 | esriConfig.portalUrl = APP_PORTAL_URL; 36 | const info = new OAuthInfo({ appId: APP_ID, popup: false, portalUrl: APP_PORTAL_URL }); 37 | const portal = new Portal({ authMode: 'immediate' }); 38 | 39 | 40 | IdentityManager.registerOAuthInfos([info]); 41 | 42 | /** 43 | * Middleware function with the signature 44 | * 45 | * storeInstance => 46 | * functionToCallWithAnActionThatWillSendItToTheNextMiddleware => 47 | * actionThatDispatchWasCalledWith => 48 | * valueToUseAsTheReturnValueOfTheDispatchCall 49 | * 50 | * Typically written as 51 | * 52 | * store => next => action => result 53 | */ 54 | const arcgisMiddleWare = store => next => (action) => { 55 | switch (action.type) { 56 | case GET_IDENTITY: 57 | next(action); 58 | return IdentityManager.checkSignInStatus(`${info.portalUrl}/sharing`) 59 | .then(() => portal.load()) 60 | .then(() => { 61 | store.dispatch({ 62 | type: SET_IDENTITY, 63 | username: portal.user.username, 64 | fullname: portal.user.fullName, 65 | email: portal.user.email, 66 | thumbnailurl: portal.user.thumbnailUrl, 67 | }); 68 | 69 | store.dispatch({ type: GET_USER_WEBSCENES }); 70 | }); 71 | 72 | 73 | case SIGN_IN: 74 | IdentityManager.getCredential(`${info.portalUrl}/sharing`); 75 | return next(action); 76 | 77 | 78 | case SIGN_OUT: 79 | IdentityManager.destroyCredentials(); 80 | window.location.reload(); 81 | return next(action); 82 | 83 | 84 | case GET_USER_WEBSCENES: 85 | next(action); 86 | return portal.queryItems({ 87 | query: `owner:${portal.user.username} AND type: Web Scene`, 88 | sortField: 'modified', 89 | sortOrder: 'desc', 90 | num: 15, 91 | }) 92 | .then(({ results }) => store.dispatch({ 93 | type: SET_USER_WEBSCENES, 94 | websceneItems: results, 95 | })); 96 | 97 | 98 | default: 99 | return next(action); 100 | } 101 | }; 102 | 103 | 104 | export default arcgisMiddleWare; 105 | -------------------------------------------------------------------------------- /src/middleware/arcgis-sceneview.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import esriConfig from 'esri/config'; 18 | import SceneView from 'esri/views/SceneView'; 19 | import WebScene from 'esri/WebScene'; 20 | 21 | 22 | import { 23 | INIT_SCENE, 24 | SELECTION_SET, 25 | SELECTION_ADD, 26 | SELECTION_REMOVE, 27 | SELECTION_RESET, 28 | SET_ENVIRONMENT, 29 | SET_DATE, 30 | SET_SHADOWS, 31 | } from '../constants/action-types'; 32 | 33 | import { registerClickEvent } from './arcgis-sceneview/interaction'; 34 | import { updateHighlights } from './arcgis-sceneview/highlights'; 35 | import { setEnvironment } from './arcgis-sceneview/environment'; 36 | 37 | 38 | esriConfig.request.corsEnabledServers.push('a.tile.stamen.com'); 39 | esriConfig.request.corsEnabledServers.push('b.tile.stamen.com'); 40 | esriConfig.request.corsEnabledServers.push('c.tile.stamen.com'); 41 | esriConfig.request.corsEnabledServers.push('d.tile.stamen.com'); 42 | 43 | 44 | const arcgis = {}; 45 | 46 | window.arcgis = arcgis; 47 | 48 | /** 49 | * Middleware function with the signature 50 | * 51 | * storeInstance => 52 | * functionToCallWithAnActionThatWillSendItToTheNextMiddleware => 53 | * actionThatDispatchWasCalledWith => 54 | * valueToUseAsTheReturnValueOfTheDispatchCall 55 | * 56 | * Typically written as 57 | * 58 | * store => next => action => result 59 | */ 60 | const arcgisMiddleWare = store => next => (action) => { 61 | switch (action.type) { 62 | /** 63 | * Initialize scene view on a viewport container. 64 | */ 65 | case INIT_SCENE: { 66 | if (!action.id || !action.container) break; 67 | 68 | // if sceneview container is already initialized, just add it back to the DOM. 69 | if (arcgis.container) { 70 | action.container.appendChild(arcgis.container); 71 | break; 72 | } 73 | 74 | // Otherwise, create a new container element and a new scene view. 75 | arcgis.container = document.createElement('DIV'); 76 | action.container.appendChild(arcgis.container); 77 | arcgis.sceneView = new SceneView({ container: arcgis.container }); 78 | 79 | registerClickEvent(arcgis.sceneView, store); 80 | 81 | // Initialize web scene 82 | const webScene = new WebScene({ portalItem: { id: action.id } }); 83 | arcgis.sceneView.map = webScene; 84 | 85 | // When initialized... 86 | return webScene 87 | .then(() => { 88 | webScene.layers.items.forEach((layer) => { layer.popupEnabled = false; }); 89 | 90 | next({ ...action, name: webScene.portalItem.title }); 91 | 92 | return arcgis.sceneView.whenLayerView(webScene.layers.getItemAt(0)); 93 | }) 94 | .then(() => { 95 | // Update the environment settings (either from the state or from the scene) 96 | const webSceneEnvironment = arcgis.sceneView.map.initialViewProperties.environment; 97 | const date = new Date(webSceneEnvironment.lighting.date); 98 | date.setUTCHours(date.getUTCHours() + webSceneEnvironment.lighting.displayUTCOffset); 99 | 100 | const { environment } = store.getState(); 101 | 102 | store.dispatch({ 103 | type: SET_ENVIRONMENT, 104 | date: environment.date !== null ? environment.date : date, 105 | UTCOffset: webSceneEnvironment.lighting.displayUTCOffset, 106 | shadows: environment.shadows !== null ? 107 | environment.shadows : 108 | webSceneEnvironment.lighting.directShadowsEnabled, 109 | }); 110 | 111 | // Update the selection highlights 112 | const { selection } = store.getState(); 113 | updateHighlights(arcgis.sceneView, selection); 114 | }); 115 | } 116 | 117 | 118 | /** 119 | * Update highlights and reports on selection change. 120 | */ 121 | case SELECTION_SET: 122 | case SELECTION_ADD: 123 | case SELECTION_REMOVE: 124 | case SELECTION_RESET: { 125 | next(action); 126 | 127 | // Update needs to happen after the action dispatched, to have the correct selection. 128 | const { selection } = store.getState(); 129 | updateHighlights(arcgis.sceneView, selection); 130 | 131 | break; 132 | } 133 | 134 | case SET_ENVIRONMENT: 135 | case SET_DATE: 136 | case SET_SHADOWS: { 137 | next(action); 138 | 139 | // Update needs to happen after the action dispatched, to have the correct environment. 140 | const { environment: { date, utcoffset, shadows } } = store.getState(); 141 | const newDate = new Date(date); 142 | newDate.setUTCHours(newDate.getUTCHours() - utcoffset); 143 | setEnvironment(arcgis.sceneView, newDate, utcoffset, shadows); 144 | break; 145 | } 146 | 147 | default: { 148 | next(action); 149 | break; 150 | } 151 | } 152 | 153 | return Promise.resolve(); 154 | }; 155 | 156 | 157 | export default arcgisMiddleWare; 158 | -------------------------------------------------------------------------------- /src/middleware/arcgis-sceneview/environment.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | export const setEnvironment = (view, date, utcOffset, shadows) => { 18 | view.environment.lighting.date = date; 19 | view.environment.lighting.displayUTCOffset = utcOffset; 20 | view.environment.lighting.directShadowsEnabled = shadows; 21 | }; 22 | 23 | export default setEnvironment; 24 | -------------------------------------------------------------------------------- /src/middleware/arcgis-sceneview/highlights.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | const highlights = []; 18 | 19 | export const updateHighlights = (view, selection) => { 20 | while (highlights.length) { 21 | highlights[0].remove(); 22 | highlights.splice(0, 1); 23 | } 24 | 25 | view.layerViews.items.forEach(layerView => 26 | highlights.push( 27 | layerView.highlight( 28 | selection 29 | .filter(item => item.layer === layerView.layer.id) 30 | .map(item => item.OID), 31 | ), 32 | ), 33 | ); 34 | }; 35 | 36 | export default updateHighlights; 37 | -------------------------------------------------------------------------------- /src/middleware/arcgis-sceneview/interaction.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { 18 | SELECTION_SET, 19 | SELECTION_ADD, 20 | SELECTION_REMOVE, 21 | SELECTION_RESET, 22 | } from '../../constants/action-types'; 23 | 24 | 25 | export const getSelectionType = (shiftKey, altKey) => { 26 | if (shiftKey) return SELECTION_ADD; 27 | if (altKey) return SELECTION_REMOVE; 28 | return SELECTION_SET; 29 | }; 30 | 31 | 32 | export const registerClickEvent = (view, store) => 33 | view.on('click', event => 34 | view.hitTest(event.screenPoint) 35 | .then((response) => { 36 | const graphic = response.results && response.results[0] && response.results[0].graphic; 37 | 38 | if (graphic) { 39 | store.dispatch({ 40 | type: getSelectionType(event.native.shiftKey, event.native.altKey), 41 | layer: graphic.layer.id, 42 | OID: graphic.attributes[graphic.layer.objectIdField], 43 | }); 44 | 45 | return; 46 | } 47 | 48 | if (!event.native.shiftKey && !event.native.altKey) { 49 | store.dispatch({ type: SELECTION_RESET }); 50 | } 51 | })); 52 | 53 | 54 | export default registerClickEvent; 55 | -------------------------------------------------------------------------------- /src/reducer/app.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { combineReducers } from 'redux'; 18 | 19 | import user from './user/index'; 20 | import webscene from './webscene/index'; 21 | import environment from './environment/index'; 22 | import selection from './selection/index'; 23 | 24 | export default combineReducers({ 25 | user, 26 | webscene, 27 | environment, 28 | selection, 29 | }); 30 | -------------------------------------------------------------------------------- /src/reducer/environment/__tests__/actions.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import * as actions from '../actions'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('actions', () => { 22 | it('should create an action to set the environment', () => { 23 | const expectedAction = { 24 | type: types.SET_ENVIRONMENT, 25 | date: new Date(2017), 26 | UTCOffset: -1, 27 | shadows: true, 28 | }; 29 | expect(actions.setEnvironment(new Date(2017), -1, true)).toEqual(expectedAction); 30 | }); 31 | 32 | 33 | it('should create an action to set the date', () => { 34 | const expectedAction = { 35 | type: types.SET_DATE, 36 | date: new Date(2017), 37 | }; 38 | expect(actions.setDate(new Date(2017))).toEqual(expectedAction); 39 | }); 40 | 41 | 42 | it('should create an action to set the shadows', () => { 43 | const expectedAction = { 44 | type: types.SET_SHADOWS, 45 | shadows: false, 46 | }; 47 | expect(actions.setShadows(false)).toEqual(expectedAction); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /src/reducer/environment/__tests__/date.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import reducer from '../date'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('date reducer', () => { 22 | it('should return the initial state', () => { 23 | expect(reducer(undefined, {})).toEqual(null); 24 | }); 25 | 26 | 27 | it('should handle SET_DATE', () => { 28 | expect(reducer(false, { 29 | type: types.SET_DATE, 30 | date: new Date(Date.UTC(2015, 4, 16, 11, 30)), 31 | })).toEqual(new Date(Date.UTC(2015, 4, 16, 11, 30))); 32 | }); 33 | 34 | 35 | it('should handle SET_ENVIRONMENT', () => { 36 | expect(reducer(false, { 37 | type: types.SET_ENVIRONMENT, 38 | date: new Date(Date.UTC(2015, 4, 16, 11, 30)), 39 | })).toEqual(new Date(Date.UTC(2015, 4, 16, 11, 30))); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/reducer/environment/__tests__/shadows.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import reducer from '../shadows'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('selection reducer', () => { 22 | it('should return the initial state', () => { 23 | expect(reducer(undefined, {})).toEqual(null); 24 | }); 25 | 26 | 27 | it('should handle SET_SHADOWS', () => { 28 | expect(reducer(false, { 29 | type: types.SET_SHADOWS, 30 | shadows: true, 31 | })).toEqual(true); 32 | }); 33 | 34 | 35 | it('should handle SET_ENVIRONMENT', () => { 36 | expect(reducer(false, { 37 | type: types.SET_ENVIRONMENT, 38 | shadows: true, 39 | })).toEqual(true); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/reducer/environment/__tests__/utcoffset.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import reducer from '../utcoffset'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('UTCOffset reducer', () => { 22 | it('should return the initial state', () => { 23 | expect(reducer(undefined, {})).toEqual(0); 24 | }); 25 | 26 | 27 | it('should handle SET_ENVIRONMENT', () => { 28 | expect(reducer(false, { 29 | type: types.SET_ENVIRONMENT, 30 | date: new Date(2017), 31 | UTCOffset: -3, 32 | shadows: true, 33 | })).toEqual(-3); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/reducer/environment/actions.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import * as types from '../../constants/action-types'; 18 | 19 | 20 | export function setEnvironment(date, UTCOffset, shadows) { 21 | return { 22 | type: types.SET_ENVIRONMENT, 23 | date, 24 | UTCOffset, 25 | shadows, 26 | }; 27 | } 28 | 29 | export function setDate(date) { 30 | return { 31 | type: types.SET_DATE, 32 | date, 33 | }; 34 | } 35 | 36 | export function setShadows(shadows) { 37 | return { 38 | type: types.SET_SHADOWS, 39 | shadows, 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/reducer/environment/date.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { SET_ENVIRONMENT, SET_DATE } from '../../constants/action-types'; 18 | 19 | const initialState = null; 20 | 21 | export default (state = initialState, action) => { 22 | switch (action.type) { 23 | case SET_ENVIRONMENT: 24 | case SET_DATE: 25 | return new Date(action.date) || initialState; 26 | default: 27 | return state; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/reducer/environment/index.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { combineReducers } from 'redux'; 18 | import date from './date'; 19 | import utcoffset from './utcoffset'; 20 | import shadows from './shadows'; 21 | 22 | export default combineReducers({ 23 | date, 24 | utcoffset, 25 | shadows, 26 | }); 27 | -------------------------------------------------------------------------------- /src/reducer/environment/shadows.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { SET_ENVIRONMENT, SET_SHADOWS } from '../../constants/action-types'; 18 | 19 | const initialState = null; 20 | 21 | export default (state = initialState, action) => { 22 | switch (action.type) { 23 | case SET_ENVIRONMENT: 24 | case SET_SHADOWS: 25 | return action.shadows; 26 | default: 27 | return state; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/reducer/environment/utcoffset.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { SET_ENVIRONMENT } from '../../constants/action-types'; 18 | 19 | const initialState = 0; 20 | 21 | export default (state = initialState, action) => { 22 | switch (action.type) { 23 | case SET_ENVIRONMENT: 24 | return action.UTCOffset; 25 | default: 26 | return state; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/reducer/selection/__tests__/actions.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import * as actions from '../actions'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('actions', () => { 22 | it('should create an action to set the selection', () => { 23 | const expectedAction = { 24 | type: types.SELECTION_SET, 25 | layer: 'foo', 26 | OID: 3, 27 | }; 28 | expect(actions.selectionSet('foo', 3)).toEqual(expectedAction); 29 | }); 30 | 31 | 32 | it('should create an action to add to the selection', () => { 33 | const expectedAction = { 34 | type: types.SELECTION_ADD, 35 | layer: 'foo', 36 | OID: 3, 37 | }; 38 | expect(actions.selectionAdd('foo', 3)).toEqual(expectedAction); 39 | }); 40 | 41 | 42 | it('should create an action to remove from the selection', () => { 43 | const expectedAction = { 44 | type: types.SELECTION_REMOVE, 45 | layer: 'foo', 46 | OID: 3, 47 | }; 48 | expect(actions.selectionRemove('foo', 3)).toEqual(expectedAction); 49 | }); 50 | 51 | 52 | it('should create an action to reset the selection', () => { 53 | const expectedAction = { 54 | type: types.SELECTION_RESET, 55 | }; 56 | expect(actions.selectionReset()).toEqual(expectedAction); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/reducer/selection/__tests__/selection.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import reducer from '../index'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('selection reducer', () => { 22 | it('should return the initial state', () => { 23 | expect(reducer(undefined, {})).toEqual([]); 24 | }); 25 | 26 | it('should handle SELECTION_SET', () => { 27 | expect(reducer([], { 28 | type: types.SELECTION_SET, 29 | layer: 'foo', 30 | OID: 1, 31 | })).toEqual([{ 32 | layer: 'foo', 33 | OID: 1, 34 | }]); 35 | }); 36 | 37 | it('should handle SELECTION_ADD', () => { 38 | expect(reducer([], { 39 | type: types.SELECTION_ADD, 40 | layer: 'foo', 41 | OID: 1, 42 | })).toEqual([{ 43 | layer: 'foo', 44 | OID: 1, 45 | }]); 46 | }); 47 | 48 | it('should handle SELECTION_REMOVE', () => { 49 | expect(reducer([{ 50 | layer: 'foo', 51 | OID: 1, 52 | }], { 53 | type: types.SELECTION_REMOVE, 54 | layer: 'foo', 55 | OID: 1, 56 | })).toEqual([]); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/reducer/selection/actions.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import * as types from '../../constants/action-types'; 18 | 19 | 20 | export function selectionSet(layer, OID) { 21 | return { 22 | type: types.SELECTION_SET, 23 | layer, 24 | OID, 25 | }; 26 | } 27 | 28 | 29 | export function selectionAdd(layer, OID) { 30 | return { 31 | type: types.SELECTION_ADD, 32 | layer, 33 | OID, 34 | }; 35 | } 36 | 37 | 38 | export function selectionRemove(layer, OID) { 39 | return { 40 | type: types.SELECTION_REMOVE, 41 | layer, 42 | OID, 43 | }; 44 | } 45 | 46 | 47 | export function selectionReset() { 48 | return { 49 | type: types.SELECTION_RESET, 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /src/reducer/selection/index.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import itemReducer from './item'; 18 | 19 | import { 20 | SELECTION_SET, 21 | SELECTION_ADD, 22 | SELECTION_REMOVE, 23 | SELECTION_RESET, 24 | } from '../../constants/action-types'; 25 | 26 | const initialState = []; 27 | 28 | export const isEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b); 29 | 30 | export default (state = initialState, action) => { 31 | switch (action.type) { 32 | case SELECTION_SET: 33 | return [itemReducer({}, action)]; 34 | case SELECTION_ADD: 35 | return [ 36 | ...state.filter(item => !isEqual(item, itemReducer({}, action))), 37 | itemReducer({}, action), 38 | ]; 39 | case SELECTION_REMOVE: 40 | return state.filter(item => !isEqual(item, itemReducer({}, action))); 41 | case SELECTION_RESET: 42 | return initialState; 43 | default: 44 | return state; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /src/reducer/selection/item/index.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { combineReducers } from 'redux'; 18 | 19 | import layer from './layer'; 20 | import OID from './oid'; 21 | 22 | export default combineReducers({ 23 | layer, 24 | OID, 25 | }); 26 | -------------------------------------------------------------------------------- /src/reducer/selection/item/layer.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { 18 | SELECTION_SET, 19 | SELECTION_ADD, 20 | SELECTION_REMOVE, 21 | } from '../../../constants/action-types'; 22 | 23 | export const INITIAL_STATE = null; 24 | 25 | const reducer = (state = INITIAL_STATE, action) => { 26 | switch (action.type) { 27 | case SELECTION_SET: 28 | case SELECTION_ADD: 29 | case SELECTION_REMOVE: 30 | return action.layer || INITIAL_STATE; 31 | default: 32 | break; 33 | } 34 | return state; 35 | }; 36 | 37 | export default reducer; 38 | -------------------------------------------------------------------------------- /src/reducer/selection/item/oid.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { 18 | SELECTION_SET, 19 | SELECTION_ADD, 20 | SELECTION_REMOVE, 21 | } from '../../../constants/action-types'; 22 | 23 | export const INITIAL_STATE = null; 24 | 25 | const reducer = (state = INITIAL_STATE, action) => { 26 | switch (action.type) { 27 | case SELECTION_SET: 28 | case SELECTION_ADD: 29 | case SELECTION_REMOVE: 30 | return action.OID || INITIAL_STATE; 31 | default: 32 | break; 33 | } 34 | return state; 35 | }; 36 | 37 | export default reducer; 38 | -------------------------------------------------------------------------------- /src/reducer/user/__tests__/actions.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import * as actions from '../actions'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('actions', () => { 22 | it('should create an action to get the user identity', () => { 23 | const expectedAction = { type: types.GET_IDENTITY }; 24 | expect(actions.getIdentity()).toEqual(expectedAction); 25 | }); 26 | 27 | it('should create an action to set the user identity', () => { 28 | const expectedAction = { 29 | type: types.SET_IDENTITY, 30 | username: 'user123', 31 | fullname: 'John', 32 | email: 'john@doe.com', 33 | thumbnailurl: 'http://bla', 34 | }; 35 | expect(actions.setIdentity('user123', 'John', 'john@doe.com', 'http://bla')) 36 | .toEqual(expectedAction); 37 | }); 38 | 39 | it('should create an action to sign in', () => { 40 | const expectedAction = { type: types.SIGN_IN }; 41 | expect(actions.signIn()).toEqual(expectedAction); 42 | }); 43 | 44 | it('should create an action to sign out', () => { 45 | const expectedAction = { type: types.SIGN_OUT }; 46 | expect(actions.signOut()).toEqual(expectedAction); 47 | }); 48 | 49 | it('should create an action to get the user\'s webscenes', () => { 50 | const expectedAction = { type: types.GET_USER_WEBSCENES }; 51 | expect(actions.getUserWebscenes()).toEqual(expectedAction); 52 | }); 53 | 54 | it('should create an action to set the user\'s webscenes', () => { 55 | const expectedAction = { 56 | type: types.SET_USER_WEBSCENES, 57 | websceneItems: ['1', '2'], 58 | }; 59 | expect(actions.setUserWebscenes(['1', '2'])).toEqual(expectedAction); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/reducer/user/__tests__/email.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import reducer from '../email'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('email reducer', () => { 22 | it('should return the initial state', () => { 23 | expect(reducer(undefined, {})).toEqual(null); 24 | }); 25 | 26 | it('should handle SET_IDENTITY', () => { 27 | expect(reducer(null, { 28 | type: types.SET_IDENTITY, 29 | email: 'test@test.com', 30 | })).toEqual('test@test.com'); 31 | 32 | expect(reducer('bla@bla.com', { 33 | type: types.SET_IDENTITY, 34 | email: 'test@test.com', 35 | })).toEqual('test@test.com'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/reducer/user/__tests__/fullname.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import reducer from '../fullname'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('fullname reducer', () => { 22 | it('should return the initial state', () => { 23 | expect(reducer(undefined, {})).toEqual(null); 24 | }); 25 | 26 | it('should handle SET_IDENTITY', () => { 27 | expect(reducer(null, { 28 | type: types.SET_IDENTITY, 29 | fullname: 'John', 30 | })).toEqual('John'); 31 | 32 | expect(reducer('John', { 33 | type: types.SET_IDENTITY, 34 | fullname: 'Ronald', 35 | })).toEqual('Ronald'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/reducer/user/__tests__/thumbnailurl.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import reducer from '../thumbnailurl'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('thumbnailurl reducer', () => { 22 | it('should return the initial state', () => { 23 | expect(reducer(undefined, {})).toEqual(null); 24 | }); 25 | 26 | it('should handle SET_IDENTITY', () => { 27 | expect(reducer(null, { 28 | type: types.SET_IDENTITY, 29 | thumbnailurl: 'http://bla', 30 | })).toEqual('http://bla'); 31 | 32 | expect(reducer('http://bla', { 33 | type: types.SET_IDENTITY, 34 | thumbnailurl: 'http://bla2', 35 | })).toEqual('http://bla2'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/reducer/user/__tests__/username.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import reducer from '../username'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('username reducer', () => { 22 | it('should return the initial state', () => { 23 | expect(reducer(undefined, {})).toEqual(null); 24 | }); 25 | 26 | it('should handle SET_IDENTITY', () => { 27 | expect(reducer(null, { 28 | type: types.SET_IDENTITY, 29 | username: 'user123', 30 | })).toEqual('user123'); 31 | 32 | expect(reducer('user123', { 33 | type: types.SET_IDENTITY, 34 | username: 'john321', 35 | })).toEqual('john321'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/reducer/user/__tests__/websceneitems.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import reducer from '../websceneitems'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('websceneItems reducer', () => { 22 | it('should return the initial state', () => { 23 | expect(reducer(undefined, {})).toEqual([]); 24 | }); 25 | 26 | it('should handle SET_USER_WEBSCENES', () => { 27 | expect(reducer([], { 28 | type: types.SET_USER_WEBSCENES, 29 | websceneItems: [{ id: 0, title: 'bla' }], 30 | })).toEqual([{ id: 0, title: 'bla' }]); 31 | 32 | expect(reducer([{ id: 0, title: 'bla' }], { 33 | type: types.SET_USER_WEBSCENES, 34 | websceneItems: [{ id: 0, title: 'bla' }, { id: 1, title: 'bla2' }], 35 | })).toEqual([{ id: 0, title: 'bla' }, { id: 1, title: 'bla2' }]); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/reducer/user/actions.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import * as types from '../../constants/action-types'; 18 | 19 | 20 | export function getIdentity() { 21 | return { 22 | type: types.GET_IDENTITY, 23 | }; 24 | } 25 | 26 | export function setIdentity(username, fullname, email, thumbnailurl) { 27 | return { 28 | type: types.SET_IDENTITY, 29 | username, 30 | fullname, 31 | email, 32 | thumbnailurl, 33 | }; 34 | } 35 | 36 | export function signIn() { 37 | return { 38 | type: types.SIGN_IN, 39 | }; 40 | } 41 | 42 | export function signOut() { 43 | return { 44 | type: types.SIGN_OUT, 45 | }; 46 | } 47 | 48 | export function getUserWebscenes() { 49 | return { 50 | type: types.GET_USER_WEBSCENES, 51 | }; 52 | } 53 | 54 | export function setUserWebscenes(websceneItems) { 55 | return { 56 | type: types.SET_USER_WEBSCENES, 57 | websceneItems, 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /src/reducer/user/email.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { SET_IDENTITY } from '../../constants/action-types'; 18 | 19 | const initialState = null; 20 | 21 | export default (state = initialState, action) => { 22 | switch (action.type) { 23 | case SET_IDENTITY: 24 | return action.email || initialState; 25 | default: 26 | return state; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/reducer/user/fullname.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { SET_IDENTITY } from '../../constants/action-types'; 18 | 19 | const initialState = null; 20 | 21 | export default (state = initialState, action) => { 22 | switch (action.type) { 23 | case SET_IDENTITY: 24 | return action.fullname || initialState; 25 | default: 26 | return state; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/reducer/user/index.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { combineReducers } from 'redux'; 18 | import username from './username'; 19 | import fullname from './fullname'; 20 | import email from './email'; 21 | import thumbnailurl from './thumbnailurl'; 22 | import websceneItems from './websceneitems'; 23 | 24 | export default combineReducers({ 25 | username, 26 | fullname, 27 | email, 28 | thumbnailurl, 29 | websceneItems, 30 | }); 31 | -------------------------------------------------------------------------------- /src/reducer/user/thumbnailurl.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { SET_IDENTITY } from '../../constants/action-types'; 18 | 19 | const initialState = null; 20 | 21 | export default (state = initialState, action) => { 22 | switch (action.type) { 23 | case SET_IDENTITY: 24 | return action.thumbnailurl || initialState; 25 | default: 26 | return state; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/reducer/user/username.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { SET_IDENTITY } from '../../constants/action-types'; 18 | 19 | const initialState = null; 20 | 21 | export default (state = initialState, action) => { 22 | switch (action.type) { 23 | case SET_IDENTITY: 24 | return action.username || initialState; 25 | default: 26 | return state; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/reducer/user/websceneitems.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { SET_USER_WEBSCENES } from '../../constants/action-types'; 18 | 19 | const initialState = []; 20 | 21 | export default (state = initialState, action) => { 22 | switch (action.type) { 23 | case SET_USER_WEBSCENES: 24 | return action.websceneItems.map(item => ({ id: item.id, title: item.title })); 25 | default: 26 | return state; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/reducer/webscene/__tests__/actions.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import * as actions from '../actions'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('actions', () => { 22 | it('should create an action to initialize the scene view container', () => { 23 | const expectedAction = { 24 | type: types.INIT_SCENE, 25 | container: 'foo', 26 | id: '123', 27 | }; 28 | expect(actions.initScene('foo', '123')).toEqual(expectedAction); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/reducer/webscene/__tests__/id.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import reducer from '../id'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('webscene id reducer', () => { 22 | it('should return the initial state', () => { 23 | expect(reducer(undefined, {})).toEqual(null); 24 | }); 25 | 26 | it('should handle INIT_SCENE', () => { 27 | expect(reducer(false, { 28 | type: types.INIT_SCENE, 29 | id: 123, 30 | name: 'foo', 31 | })).toEqual(123); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/reducer/webscene/__tests__/name.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import reducer from '../name'; 18 | import * as types from '../../../constants/action-types'; 19 | 20 | 21 | describe('webscene name reducer', () => { 22 | it('should return the initial state', () => { 23 | expect(reducer(undefined, {})).toEqual(null); 24 | }); 25 | 26 | it('should handle INIT_SCENE', () => { 27 | expect(reducer(false, { 28 | type: types.INIT_SCENE, 29 | id: 123, 30 | name: 'foo', 31 | })).toEqual('foo'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/reducer/webscene/actions.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import * as types from '../../constants/action-types'; 18 | 19 | 20 | // eslint-disable-next-line 21 | export function initScene(container, id, name) { 22 | return { 23 | type: types.INIT_SCENE, 24 | container, 25 | id, 26 | name, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/reducer/webscene/id.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { INIT_SCENE } from '../../constants/action-types'; 18 | 19 | const initialState = null; 20 | 21 | export default (state = initialState, action) => { 22 | switch (action.type) { 23 | case INIT_SCENE: 24 | return action.id || initialState; 25 | default: 26 | return state; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/reducer/webscene/index.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { combineReducers } from 'redux'; 18 | import name from './name'; 19 | import id from './id'; 20 | 21 | export default combineReducers({ 22 | name, 23 | id, 24 | }); 25 | -------------------------------------------------------------------------------- /src/reducer/webscene/name.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { INIT_SCENE } from '../../constants/action-types'; 18 | 19 | const initialState = null; 20 | 21 | export default (state = initialState, action) => { 22 | switch (action.type) { 23 | case INIT_SCENE: 24 | return action.name || initialState; 25 | default: 26 | return state; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/store/store.jsx: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | import { createStore, applyMiddleware, compose } from 'redux'; 18 | import thunk from 'redux-thunk'; 19 | 20 | import authentication from '../middleware/arcgis-authentication'; 21 | import sceneview from '../middleware/arcgis-sceneview'; 22 | 23 | import reducer from '../reducer/app'; 24 | 25 | import { getIdentity } from '../reducer/user/actions'; 26 | 27 | 28 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; // eslint-disable-line 29 | 30 | const store = createStore( 31 | reducer, 32 | composeEnhancers(applyMiddleware(thunk, authentication, sceneview)), 33 | ); 34 | 35 | store.dispatch(getIdentity()); 36 | 37 | 38 | if (module.hot) { 39 | module.hot.accept('../reducer/app', () => { 40 | // Quite ugly: this piece of code must use AMD because it will be run in the built 41 | // environment. 42 | // eslint-disable-next-line 43 | require(["../reducer/app"], function (nextReducer) { 44 | store.replaceReducer(nextReducer.default); 45 | }); 46 | }); 47 | } 48 | 49 | export default store; 50 | -------------------------------------------------------------------------------- /src/styles/main.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | html { 18 | overflow: hidden !important; 19 | } 20 | 21 | div#app-container > div.container { 22 | overflow: hidden; 23 | width: 100vw; 24 | height: 100vh; 25 | display: flex; 26 | flex-direction: column; 27 | } 28 | 29 | .hidden { 30 | display: none !important; 31 | } 32 | 33 | .top-nav { 34 | padding: 0 20px 0 20px; 35 | flex: 0 0 auto; 36 | } 37 | 38 | .app-main { 39 | position: relative; 40 | flex: 1 0 auto; 41 | } 42 | 43 | .websceneview { 44 | position: absolute; 45 | height: 100%; 46 | width: 100%; 47 | } 48 | 49 | .websceneview > div { 50 | width: 100%; 51 | height: 100%; 52 | } 53 | 54 | .app-widgets { 55 | margin: 15px; 56 | float: right; 57 | } 58 | 59 | .time-nav, 60 | .shadow-nav { 61 | width: 300px; 62 | margin-bottom: 5px; 63 | } 64 | 65 | .time-nav > div> label, 66 | .shadow-nav > div > fieldset { 67 | margin: 0; 68 | } 69 | 70 | .identity { 71 | display: flex; 72 | align-items: center; 73 | } 74 | 75 | .identity > .dropdown > button { 76 | display: flex; 77 | align-items: center; 78 | padding: 13px 0 12px 0; 79 | } 80 | 81 | .identity > .dropdown > button > img { 82 | width: 36px; 83 | height: 36px; 84 | margin-right: 10px; 85 | } 86 | 87 | .identity > .dropdown > button > i { 88 | margin-left: 10px; 89 | } 90 | 91 | 92 | /* Calcite fixes */ 93 | 94 | button.top-nav-link { 95 | cursor: pointer; 96 | background: inherit; 97 | border-left: none; 98 | border-right: none; 99 | border-top: none; 100 | box-sizing: border-box; 101 | margin: 0; /* Safari adds a 2px margin to buttons for no reason */ 102 | } 103 | 104 | button.dropdown-link { 105 | cursor: pointer; 106 | background: inherit; 107 | border: none; 108 | } 109 | -------------------------------------------------------------------------------- /src/styles/range-slider.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Esri 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | /* Range slider overrides */ 18 | 19 | input[type=range] { 20 | -webkit-appearance: none; /* Hides the slider so that custom slider can be made */ 21 | width: 100%; /* Specific width is required for Firefox. */ 22 | background: transparent; /* Otherwise white in Chrome */ 23 | border: none; 24 | box-shadow: none; 25 | } 26 | 27 | input[type=range]::-webkit-slider-thumb { 28 | -webkit-appearance: none; 29 | } 30 | 31 | input[type=range]:focus, 32 | input[type=range]::-moz-focus-outer { 33 | outline: none; 34 | box-shadow: none; 35 | border: 0; 36 | } 37 | 38 | input[type=range]::-ms-track { 39 | width: 100%; 40 | cursor: pointer; 41 | 42 | /* Hides the slider so custom styles can be added */ 43 | background: transparent; 44 | border-color: transparent; 45 | color: transparent; 46 | } 47 | 48 | /* Special styling for WebKit/Blink */ 49 | input[type=range]::-webkit-slider-thumb { 50 | -webkit-appearance: none; 51 | margin-top: -8px; 52 | border: 1px solid #0079c1; 53 | height: 21px; 54 | width: 21px; 55 | border-radius: 50%; 56 | background: #ffffff; 57 | cursor: pointer; 58 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); 59 | } 60 | 61 | input[type=range]:hover::-webkit-slider-thumb { 62 | background: #0079c1; 63 | } 64 | 65 | /* All the same stuff for Firefox */ 66 | input[type=range]::-moz-range-thumb { 67 | border: 1px solid #0079c1; 68 | height: 21px; 69 | width: 21px; 70 | border-radius: 50%; 71 | background: #ffffff; 72 | cursor: pointer; 73 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); 74 | } 75 | 76 | /* All the same stuff for IE */ 77 | input[type=range]::-ms-thumb { 78 | border: 1px solid #0079c1; 79 | height: 21px; 80 | width: 21px; 81 | border-radius: 50%; 82 | background: #ffffff; 83 | cursor: pointer; 84 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); 85 | } 86 | 87 | input[type=range]::-webkit-slider-runnable-track { 88 | width: 100%; 89 | height: 8px; 90 | cursor: pointer; 91 | background-color: #f8f8f8; 92 | border: 1px solid #efefef; 93 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); 94 | border-radius: 2px; 95 | } 96 | 97 | input[type=range]::-moz-range-track { 98 | width: 100%; 99 | height: 8px; 100 | cursor: pointer; 101 | background-color: #f8f8f8; 102 | border: 1px solid #efefef; 103 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); 104 | border-radius: 2px; 105 | } 106 | 107 | /* more IE stuff */ 108 | 109 | input[type=range]::-ms-track { 110 | width: 100%; 111 | height: 8.4px; 112 | cursor: pointer; 113 | background: transparent; 114 | border-color: transparent; 115 | border-width: 16px 0; 116 | color: transparent; 117 | } 118 | input[type=range]::-ms-fill-lower { 119 | background: #2a6495; 120 | border: 0.2px solid #010101; 121 | border-radius: 2.6px; 122 | box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; 123 | } 124 | input[type=range]:focus::-ms-fill-lower { 125 | background: #3071a9; 126 | } 127 | input[type=range]::-ms-fill-upper { 128 | background: #3071a9; 129 | border: 0.2px solid #010101; 130 | border-radius: 2.6px; 131 | box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; 132 | } 133 | input[type=range]:focus::-ms-fill-upper { 134 | background: #367ebd; 135 | } 136 | 137 | /** 138 | * Time Range Slider 139 | */ 140 | 141 | input[type=range].time-range-slider::-webkit-slider-thumb { 142 | -webkit-appearance: none; 143 | border: 1px solid #ffcc00; 144 | height: 21px; 145 | width: 21px; 146 | border-radius: 50%; 147 | background: #FFEEAC; 148 | cursor: pointer; 149 | margin-top: -8px; /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */ 150 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 0 10px #ffcc00; 151 | } 152 | 153 | input[type=range].time-range-slider:hover::-webkit-slider-thumb { 154 | background: #FFDC52; 155 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 0 10px 3px #ffcc00; 156 | } 157 | 158 | input[type=range].time-range-slider::-moz-range-thumb { 159 | border: 1px solid #ffcc00; 160 | height: 21px; 161 | width: 21px; 162 | border-radius: 50%; 163 | background: #FFEEAC; 164 | cursor: pointer; 165 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 0 10px #ffcc00; 166 | } 167 | 168 | input[type=range].time-range-slider:hover::-moz-range-thumb { 169 | background: #FFDC52; 170 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 0 10px 3px #ffcc00; 171 | } 172 | -------------------------------------------------------------------------------- /webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: [ 6 | 'react-hot-loader/patch', 7 | 'webpack-dev-server/client?http://localhost:8080/', // WebpackDevServer host and port 8 | 'webpack/hot/only-dev-server', // "only" prevents reload on syntax errors 9 | './src/main.jsx', 10 | ], 11 | output: { 12 | path: path.join(__dirname, 'dist'), 13 | publicPath: '/dist/', 14 | filename: 'bundle.js', 15 | chunkFilename: '[id].bundle.js', 16 | library: 'app/bundle', 17 | libraryTarget: 'amd', 18 | }, 19 | resolve: { 20 | modules: [path.resolve(__dirname, '/src'), 'node_modules/'], 21 | descriptionFiles: ['package.json'], 22 | extensions: ['.js', '.jsx'], 23 | }, 24 | externals: /^esri/, 25 | devtool: '#inline-source-map', 26 | devServer: { 27 | inline: true, 28 | port: 8080, 29 | }, 30 | plugins: [ 31 | new webpack.HotModuleReplacementPlugin(), 32 | new webpack.NamedModulesPlugin(), 33 | ], 34 | module: { 35 | loaders: [ 36 | { 37 | enforce: 'pre', 38 | test: /\.jsx?$/, 39 | loader: 'eslint-loader', 40 | exclude: /node_modules/, 41 | }, 42 | { 43 | test: /\.css$/, 44 | loaders: ['style-loader', 'css-loader'], 45 | }, 46 | { 47 | test: /\.jsx?$/, 48 | loader: 'babel-loader', 49 | exclude: /node_modules/, 50 | query: { 51 | presets: ['es2015', 'react'], 52 | }, 53 | }, 54 | ], 55 | }, 56 | }; 57 | --------------------------------------------------------------------------------